在上一篇中https://blog.csdn.net/liwan09/article/details/80903729讲到了WebAPI的JWT身份验证,在本篇问文章中,讲述一下Cookie+JWT+自定义权限策略的实现。
首先,在Startup.cs文件中添加 using Microsoft.AspNetCore.Authentication.Cookies;引用
一、将ConfigureServices中的代码修改为以下代码
public void ConfigureServices(IServiceCollection services)
{
//get JwtSettings from appsettings.json
services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings"));
// set JwtSettings model
var jwtSettings = new JwtSettings();
Configuration.Bind("JwtSettings", jwtSettings);
#region " add CookieAuthentication "
//自定义权限策略
services.AddAuthorization(options =>
{
options.AddPolicy("Permission", policy => policy.Requirements.Add(new PermissionRequirement("OkRole")));
})//services.AddAuthentication
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(o =>
{
var tokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidIssuer = jwtSettings.Issuer,//Issuer
ValidAudience = jwtSettings.Audience,//Audience
//SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),
ValidateIssuer = true, //Whether or not validate Issuer
ValidateAudience = true, //Whether or not validate Audience
ValidateLifetime = true, //Whether or not validate Failure time
ValidateIssuerSigningKey = true, //Whether or not validate SecurityKey
ClockSkew = TimeSpan.Zero//Allowed server time offset
};
o.Cookie.Name = "auth_app_token";
o.TicketDataFormat = new CustomJwtDataFormat(SecurityAlgorithms.HmacSha256, tokenValidationParameters);
o.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/api") && ctx.Response.StatusCode == (int)HttpStatusCode.OK)
{
ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
ctx.Response.Redirect(ctx.RedirectUri);
}
return Task.FromResult(0);
}
};
});
#endregion
//注入授权Handler
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
services.AddDistributedMemoryCache(); // Adds a default in-memory implementation of IDistributedCache
services.AddSession();
services.AddMvc();
}
代码中需要用到的相关类的代码:
1、必要参数类:
public class PermissionRequirement : IAuthorizationRequirement
{
/// <summary>
/// 构造
/// </summary>
/// <param name="roleid">角色id</param>
public PermissionRequirement(string roleid)
{
RoleID=roleid;
}
/// <summary>
/// 用户角色ID
/// </summary>
public string RoleID { get; set; }
}
2、在cookie中验证JWT口令的相关处理类CustomJwtDataFormat 相关代码
/// <summary>
/// validate jwt in cookie
/// </summary>
public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText)=> Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new Microsoft.AspNetCore.Authentication.AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
3、验证方案提供对象相关数据处理类PermissionHandler.cs相关代码
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// 构造
/// </summary>
/// <param name="schemes"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes)
{
Schemes = schemes;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
//请求Url
var questUrl = httpContext.Request.Path.Value.ToLower();
// get session user info
var sessionuser=httpContext.Request.Cookies[".AspNetCore.Session"];
//判断请求是否停止
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
context.Fail();
return;
}
}
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
//result?.Principal不为空即登录成功
if (result?.Principal != null)
{
httpContext.User = result.Principal;
//权限中是否存在请求的url
// if (requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0)
// {
// var name = httpContext.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType).Value;
// //验证权限
// if (requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() <= 0)
// {
// //无权限跳转到拒绝页面
// httpContext.Response.Redirect(requirement.DeniedAction);
// }
// }
//获取session 中的用户信息,判断用户是否有当前路径的操作权限
}
else
{
return;
}
}
//判断没有登录时,是否访问登录的url,并且是Post请求,并且是form表单提交类型,否则为失败
// if (!questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST")
// || !httpContext.Request.HasFormContentType))
// {
// context.Fail();
// return;
// }
context.Succeed(requirement);
}
}
二、将Configure中的代码修改为
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseSession();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
三、在TokenColler文件中添加生成CookieToken代码
///<summary>
///create cookie Token
///</summary>
///<param name="Loginuser">login user info<param>
///<returns></returns>
[HttpPost]
[Route("CreateCookieToken")]
public async Task CreateCookieToken([FromBody] LoginInfo loginuser)
{
string strResult = "";
List<UserInfo> userlist = UserinfoBLL.Login(loginuser.UserName, loginuser.PassWord);
if (userlist != null)
{
if (userlist.Count == 1)
{
string accesstoken = MakeAccessToken(userlist[0]);
string refreshtoken = MakeRefreshToken(userlist[0]);
//add cookie
CookieOptions accesscookie = new CookieOptions();
//commented out for client demo purpose, please uncomment this in real development environment
//accesscookie.HttpOnly = true;
Response.Cookies.Append("auth_app_token", accesstoken, accesscookie);
CookieOptions refreshcookie = new CookieOptions();
//commented out for client demo purpose, please uncomment this in real development environment
//refreshcookie.HttpOnly = true;
Response.Cookies.Append("auth_app_refresh_token", refreshtoken, refreshcookie);
//add session
var sessionuser=new
{
UserID=userlist[0].UserID,
RoleID=userlist[0].RoleID
};
HttpContext.Session.SetString("user_session",JsonHelperT.ToJson(sessionuser));
strResult = RequestReturn.ReturnInfo("0000", "", "success").ToString();
}
if (userlist.Count > 1)
{
strResult = RequestReturn.ReturnInfo("00002", "The user is not unique, please contact the administrator", "").ToString();
}
if (userlist.Count == 0)
{
strResult = RequestReturn.ReturnInfo("00001", "error Incorrect username or password", "").ToString();
}
}
else
{
strResult = RequestReturn.ReturnInfo("00001", "error Incorrect username or password", "").ToString();
}
Response.ContentType = "application/json";
await Response.WriteAsync(strResult);
}