上次讲了.net core下的自带的鉴权认证中间件来实现权限的管理,不过只是针对普通mvc项目,客户端信息时通过cookie来实现,如果是前后端分离的项目那是用不了的,一般我们是需要通过JWT来实现。
那在自带的鉴权中间件如何使用JWT呢,这里的环境使用的是vs2019 + .net core3.1
需要安装System.IdentityModel.Tokens.Jwt和Microsoft.AspNetCore.Authentication.JwtBearer
在startup中配置认证模式
- public void ConfigureServices(IServiceCollection services) {
- services.AddControllers();
-
- //注入JWT服务
- services.AddScoped
(); - services.AddSingleton
(); -
- //jwt认证服务配置
- var issuer = Configuration["issuer"];
- var audience = Configuration["audience"];
- var securityKey = Configuration["SecurityKey"];
- services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)//jwt的授权机制名称
- .AddJwtBearer(op => {
- op.TokenValidationParameters = new TokenValidationParameters {
- ValidateIssuer = true,//是否进行相关参数验证
- ValidateAudience = true,
- ValidateLifetime = true,
- ValidateIssuerSigningKey = true,
- ValidAudience = audience,
- ValidIssuer = issuer,
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
- ClockSkew = TimeSpan.Zero,//这个是缓冲过期时间,也就是说,即使我们配置了过期时间,这里也要考虑进去,过期时间+缓冲,默认好像是7分钟,你可以直接设置为0
- RequireExpirationTime = true,
- };
- });
- services.AddAuthorization(op => {
- op.AddPolicy("policy1", p => {
- p.AddRequirements(new CustomAuthorizatinRequirement());
- });
- });
-
- //Swagger文档配置使用http://localhost:5001/swagger
- //注册Swagger生成器,定义一个和多个Swagger 文档
- services.AddSwaggerGen(b =>
- {
- b.SwaggerDoc("v1", new OpenApiInfo() {
- Title = "api接口调用文档",
- Version = "v1",
- Description = "系统相关Api文档"
- });
- //为Swagger Json and UI设置XML文档的路径
- var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序的路径
- var xmlPath = Path.Combine(basePath, "Swagger.xml");
- b.IncludeXmlComments(xmlPath);
- });
-
- }
定义jwt服务接口
- public interface IJwtService {
- string GetToken(string account);
- }
实现此接口
- using Microsoft.Extensions.Configuration;
- using Microsoft.IdentityModel.Tokens;
- using System;
- using System.Collections.Generic;
- using System.IdentityModel.Tokens.Jwt;
- using System.Linq;
- using System.Security.Claims;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace JwtDemo.Service {
- public class JwtService : IJwtService {
- private readonly IConfiguration _configuration;
- public JwtService(IConfiguration configuration) {
- _configuration = configuration;
- }
-
- public string GetToken(string account) {
- /**
- * Claims(Payload)
- * Claims包含了一些跟这个token有关的重要信息。JWT标准规定的字段:
- *
- * iss: The issuer of the token, 签发人
- * sub: The subject of the token, 主题
- * exp: Expiration Time. 过期时间(Unix时间戳格式)
- * iat: Issued At. 签发时间(Unix时间戳格式)
- * jti: JWT ID. 编号
- * aud: audience. 受众
- * nbf: Not Before. 生效时间
- *
- * 除了规定的字段外,可以包含其他任何JSON兼容的字段。
- * */
- var roleList = new List<string>() {
- "admin",
- "test"
- };
-
- var claims = new List
() - {
- new Claim(ClaimTypes.Name, account),
- new Claim("account", account),
- //new Claim(ClaimTypes.Role, "admin")
- };
- //填充角色
- foreach (var item in roleList) {
- claims.Add(new Claim(ClaimTypes.Role, item));
- }
- var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
- var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
-
- var token = new JwtSecurityToken(
- issuer: _configuration["issuer"], //签发人
- audience: _configuration["audience"],//接受方
- claims: claims,
- expires: DateTime.Now.AddMinutes(20), //20分钟有效期
- signingCredentials: credentials);
-
- var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
- return tokenStr;
- }
- }
- }
启用鉴权中间件,这个和之前的方法一样
app.UseAuthentication();//启用鉴权中间件
在appsettings.json中配置jwt信息
- "SecurityKey": "58CAA81E-1511-EF1F-8C09-4425F7362F11",
- "issuer": "http://localhost:5000/",
- "audience": "http://localhost:5000/"
编写登录webapi
- using JwtDemo.DTO;
- using JwtDemo.Service;
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.Mvc;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
-
- namespace JwtDemo.Controllers {
- [Route("api/[controller]/[action]")]
- [ApiController]
- public class LoginController : Controller {
- private readonly IJwtService _jwtService;
- public LoginController(IJwtService jwtService) {
- _jwtService = jwtService;
- }
-
- ///
- /// 获取登录的token
- ///
- ///
- ///
- [HttpPost]
- public IActionResult DoLogin([FromBody] LoginRequestDTO dto) {
- bool success = false;
- string token;
- if(dto.Account=="1646" && dto.Password == "123") {
- success = true;
- token = _jwtService.GetToken(dto.Account);
- }
- else {
- token = "";
- }
- return Json(new {
- success,
- token
- });
- }
- }
- }
通过此DoLogin的webapi我们可以获取到token,那就说明我们可以取到token了。
然后我们来添加一个测试访问的webapi
- ///
- /// 测试
- ///
- ///
- [HttpGet]
- [Authorize(Roles ="admin")]
- public IActionResult Test() {
- var claims = HttpContext.AuthenticateAsync().Result.Principal.Claims;
- var name = claims.FirstOrDefault(b => b.Type.Equals(ClaimTypes.Name))?.Value;
- var account = claims.FirstOrDefault(b => b.Type.Equals("Account"))?.Value;//null检查运算符 不为空时执行
- var role = claims.FirstOrDefault(b => b.Type.Equals("Role"))?.Value;
- var exp = claims.FirstOrDefault(b => b.Type.Equals("exp"))?.Value;
-
- var expDateTime = DateTime.Now;
- if (!string.IsNullOrWhiteSpace(exp)) {
- long expValue;
- if (long.TryParse(exp, out expValue)) {
- expDateTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), TimeZoneInfo.Local);
- expDateTime = expDateTime.AddSeconds(expValue);
- }
- }
-
- return Json(new {
- ExpDateTime = expDateTime,
- Name = name,
- Data = "已授权",
- Type = "GetAuthorizeData"
- });
- }
我们通过postman来测试,直接访问Test这个webapi,会提示401错误,然后我们通过设置authorization的type选择bear token,然后将我们刚才获取到的token输入到里面,然后再发起请求就可以访问成功了。
角色管理
使用jwt同样也存在角色管理这个问题,我们可以在控制器或action上加上特性[Authorize(Roles ="admin")]来设置可以访问的角色,如果刚才我的测试登录的用户未给到admin角色,那么我访问就会报403没有权限的错误。加上admin角色就可以访问成功了。
自定义策略授权进阶
使用jwt同样也存在,如果角色写在代码里也不好控制,同样我们也可以通过自定义策略来实现。方法其实跟之前是一样的。
1.添加CustomAuthorizatinRequirement.cs这个类实现接口IAuthorizationRequirement,这个IAuthorizationRequirement是个空接口,我们直接实现不用写什么代码。
- public class CustomAuthorizatinRequirement : IAuthorizationRequirement {
-
- }
2.添加CustomAuthorizationHandler.cs这个认证处理类,这个类需要实现IAuthorizationRequirement这个接口,不过一般我们都是通过继承AuthorizationHandler
- public class CustomAuthorizationHandler: AuthorizationHandler<CustomAuthorizatinRequirement> {
- private IHttpContextAccessor _httpContext;
- public CustomAuthorizationHandler(IHttpContextAccessor httpContext) {
- _httpContext = httpContext;
- }
-
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizatinRequirement requirement) {
- bool flag=false;
- var httpContext = _httpContext.HttpContext;
- RouteEndpoint res = context.Resource as RouteEndpoint;
- if (res != null) {
- if (res.RoutePattern.RequiredValues.Keys.Count() == 2){
- var action = res.RoutePattern.RequiredValues.ToList().FirstOrDefault(b => b.Key == "action").Value;
- var ctr = res.RoutePattern.RequiredValues.ToList().FirstOrDefault(b => b.Key == "controller").Value;
- }
- }
-
- var user = context.User.Claims.FirstOrDefault(b => b.Type == "account").Value;
-
- if (user == "1646") {
- flag = true;
- }
- if (flag) {
- context.Succeed(requirement);
- }
- return Task.CompletedTask;
- }
- }
3.让自定义策略生效,需要在服务注册services.AddSingleton
- public void ConfigureServices(IServiceCollection services) {
- services.AddControllersWithViews();
-
- services.AddSingleton
(); - //自定义认证处理的服务注册
- services.AddSingleton
(); - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
- .AddCookie(op => {
- op.LoginPath = new PathString("/Login");//登录路径
- op.AccessDeniedPath = new PathString("/Login/Denied");//无权限地址
- });
- //策略的注册
- services.AddAuthorization(op => {
- op.AddPolicy("policy1", p => {
- p.AddRequirements(new CustomAuthorizatinRequirement());
- });
- });
- }
然后在控制器上加上
- [HttpGet]
- [Authorize(policy:"policy1")]
- public IActionResult TestA() {
- var claims = HttpContext.AuthenticateAsync().Result.Principal.Claims;
- var name = claims.FirstOrDefault(b => b.Type.Equals(ClaimTypes.Name))?.Value;
- var account = claims.FirstOrDefault(b => b.Type.Equals("Account"))?.Value;//null检查运算符 不为空时执行
- var role = claims.FirstOrDefault(b => b.Type.Equals("Role"))?.Value;
- var exp = claims.FirstOrDefault(b => b.Type.Equals("exp"))?.Value;
-
- var expDateTime = DateTime.Now;
- if (!string.IsNullOrWhiteSpace(exp)) {
- long expValue;
- if (long.TryParse(exp, out expValue)) {
- expDateTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), TimeZoneInfo.Local);
- expDateTime = expDateTime.AddSeconds(expValue);
- }
- }
-
- return Json(new {
- ExpDateTime = expDateTime,
- Name = name,
- Data = "已授权",
- Type = "GetAuthorizeData"
- });
- }
这样我访问TestA这个webapi的时候会根据CustomAuthorizationHandler这个处理类来进行授权判断,具体判断业务逻辑当然你可以自己组织了。