在ASP.Net Core中实现一个Token Base身份认证,使用场景主要就是Web API下,可以调用Web API的不止是浏览器,还有各种各样的客户端,有些客户端没有Cookies,也无法使用Session。这时候就需要Token来救场了,相比Cookies,Token更开放,而安全性也要比Cookies高很多。
下面使用微软JwtSecurityTokenHandler来实现一个基于beare token的身份认证。
1.创建辅助类:
在项目中,新建一个Auth文件夹,在Auth文件夹中添加一个RSAKeyHelper类:
using System.Security.Cryptography;
namespace Biz126.WebAPI.Auth
{
public class RSAKeyHelper
{
public static RSAParameters GenerateKey()
{
using (var key = new RSACryptoServiceProvider(2048))
{
return key.ExportParameters(true);
}
}
}
}
和TokenAuthOption类:
using Microsoft.IdentityModel.Tokens;
namespace Biz126.WebAPI.Auth
{
public class TokenAuthOption
{
public static string Audience { get; } = "ExampleAudience";
public static string Issuer { get; } = "ExampleIssuer";
public static RsaSecurityKey Key { get; } = new RsaSecurityKey(RSAKeyHelper.GenerateKey());
public static SigningCredentials SigningCredentials { get; } = new SigningCredentials(Key, SecurityAlgorithms.RsaSha256Signature);
public static TimeSpan ExpiresSpan { get; } = TimeSpan.FromMinutes(20);
}
}
2.修改Startup.cs文件:
打开Startup.cs文件,在ConfigureServices中添加:
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
完整的ConfigureServices方法:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.Configure<Biz126.Models.ConnectionStrings>(Configuration.GetSection("ConnectionStrings")); //这个是取数据库配置,与本例无关
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
services.AddMvc();
}
修改Configure方法,在其中增加:
app.UseExceptionHandler(appBuilder => {
appBuilder.Use(async (context, next) => {
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
//when authorization has failed, should retrun a json message to client
if (error != null && error.Error is SecurityTokenExpiredException)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
//new { authenticated = false, tokenExpired = true }
new { Status = false, Message = "Token已失效", Code = -401, Result = new { tokenExpired = true } }
));
}
//when orther error, retrun a error message json to client
else if (error != null && error.Error != null)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
//new { success = false, error = error.Error.Message }
new { Status = false, Code = -500, Message = error.Error.Message }
));
}
//when no error, do next.
else await next();
});
});
app.UseExceptionHandler(appBuilder => {
appBuilder.Use(async (context, next) => {
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
//when authorization has failed, should retrun a json message to client
if (error != null && error.Error is SecurityTokenExpiredException)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
new { Status = false, Message = "Token已失效", Code = -401, Result = new { tokenExpired = true } }
));
}
//when orther error, retrun a error message json to client
else if (error != null && error.Error != null)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
new { Status = false, Code = -500, Message = error.Error.Message }
));
}
//when no error, do next.
else await next();
});
});
//应用JwtBearerAuthentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = TokenAuthOption.Key,
ValidAudience = TokenAuthOption.Audience,
ValidIssuer = TokenAuthOption.Issuer,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
}
});
完整的Configure方法:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseExceptionHandler(appBuilder => {
appBuilder.Use(async (context, next) => {
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
//when authorization has failed, should retrun a json message to client
if (error != null && error.Error is SecurityTokenExpiredException)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
//new { authenticated = false, tokenExpired = true }
new { Status = false, Message = "Token已失效", Code = -401, Result = new { tokenExpired = true } }
));
}
//when orther error, retrun a error message json to client
else if (error != null && error.Error != null)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
//new { success = false, error = error.Error.Message }
new { Status = false, Code = -500, Message = error.Error.Message }
));
}
//when no error, do next.
else await next();
});
});
app.UseExceptionHandler(appBuilder => {
appBuilder.Use(async (context, next) => {
var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
//when authorization has failed, should retrun a json message to client
if (error != null && error.Error is SecurityTokenExpiredException)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
new { Status = false, Message = "Token已失效", Code = -401, Result = new { tokenExpired = true } }
));
}
//when orther error, retrun a error message json to client
else if (error != null && error.Error != null)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(
new { Status = false, Code = -500, Message = error.Error.Message }
));
}
//when no error, do next.
else await next();
});
});
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = TokenAuthOption.Key,
ValidAudience = TokenAuthOption.Audience,
ValidIssuer = TokenAuthOption.Issuer,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(0)
}
});
app.UseMvc();
}
3.登录授权接口
在Controllers中,增加TokenAuthController的Web API控制器,在该控制器下添加如下方法:
/// <summary>
/// 生成Token
/// </summary>
/// <param name="user"></param>
/// <param name="serviceid"></param>
/// <param name="device"></param>
/// <param name="expires"></param>
/// <returns></returns>
private string GenerateToken(Biz126.Customer.Models.Detail user,string serviceid,string device, DateTime expires)
{
var handler = new JwtSecurityTokenHandler();
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.UserName, "TokenAuth"),
new[] {
new Claim("ID", user._Id.ToString()),
new Claim("TrueName",user.TrueName),
new Claim("ServiceId",serviceid),
new Claim("Device",device) //绑定硬件号
}
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = TokenAuthOption.Issuer,
Audience = TokenAuthOption.Audience,
SigningCredentials = TokenAuthOption.SigningCredentials,
Subject = identity,
Expires = expires
});
return handler.WriteToken(securityToken);
}
正如注释中所说的,该方法用于生成Token。
还是在该控制器下,继续添加GetAuthorize方法,用于取得授权,代码如下:
[HttpPost]
public Biz126.Models.ResultModel<object> GetAuthorize(Biz126.Authorization.Models.UserAuth user)
{
var result = new Biz126.Models.ResultModel<object>();
var log = new Biz126.Customer.Models.Login() { UserName = user.UserName, Password = user.Password };
var login = _users.Login(log); //登录
if (login.Status)
{
//用户账号状态正常
//1.查看该用户有几个有效的该授权
//2.已经开通了几个
//3.在剩余范围内
//4.查看该机器是否已经有过授权
//5.如果已经有过授权,更新Token,如果没有过授权,增加新授权
var requestAt = DateTime.Now;
var expiresIn = requestAt + TokenAuthOption.ExpiresSpan; //过期时间(正常为授权结束日期)
var token = GenerateToken(login.Result,user.ServiceId,user.Device, expiresIn);
result.Result = new { requestAt = requestAt, expiresIn = TokenAuthOption.ExpiresSpan.TotalSeconds, accessToken = token };
result.Status = true;
result.Message = "success";
result.Code = 0;
}
else
{
result.Status = false;
result.Message = "用户名或密码错误";
result.Code = -403;
}
return result;
}
4.验证授权:
在Controllers中新建一个CheckAuthController的Web API控制器,添加下面的方法:
[Authorize("Bearer")]
public string Post()
{
var claimsIdentity = User.Identity as ClaimsIdentity;
var id = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "ID").Value;
var device= claimsIdentity.Claims.FirstOrDefault(c => c.Type == "Device").Value;
return $"Hello! {HttpContext.User.Identity.Name}, your Device is:{device}";
}
5.测试:
打开Postman,先访问/api/TokenAuth/GetAuthorize,使用用户名和密码进行登录,接口会返回生成的Token,记下返回的Token。继续使用Postman工具,访问接口/api/CheckAuth,在Headers中,添加:Authorization:Bearer 上一步生成的Token,如下图所示
要注意“Bearer”与后面的Token之间有一个空格,之后提交请求,可以看到验证通过并给返回相应的信息。
上面代码只是一个例子,具体可以灵活的运用到自己的项目中。
以上。
本文作者:老徐
本文链接:https://bigger.ee/archives/21.html
转载时须注明出处及本声明
2 comments
UseJwtBearerAuthentication报错是为什么?
看下代码和报错内容