• .net MVC下鉴权认证(三)


    上次讲了.net core下的自带的鉴权认证中间件来实现权限的管理,不过只是针对普通mvc项目,客户端信息时通过cookie来实现,如果是前后端分离的项目那是用不了的,一般我们是需要通过JWT来实现。

    那在自带的鉴权中间件如何使用JWT呢,这里的环境使用的是vs2019 + .net core3.1

    需要安装System.IdentityModel.Tokens.Jwt和Microsoft.AspNetCore.Authentication.JwtBearer

     在startup中配置认证模式

    1. public void ConfigureServices(IServiceCollection services) {
    2. services.AddControllers();
    3. //注入JWT服务
    4. services.AddScoped();
    5. services.AddSingleton();
    6. //jwt认证服务配置
    7. var issuer = Configuration["issuer"];
    8. var audience = Configuration["audience"];
    9. var securityKey = Configuration["SecurityKey"];
    10. services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)//jwt的授权机制名称
    11. .AddJwtBearer(op => {
    12. op.TokenValidationParameters = new TokenValidationParameters {
    13. ValidateIssuer = true,//是否进行相关参数验证
    14. ValidateAudience = true,
    15. ValidateLifetime = true,
    16. ValidateIssuerSigningKey = true,
    17. ValidAudience = audience,
    18. ValidIssuer = issuer,
    19. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
    20. ClockSkew = TimeSpan.Zero,//这个是缓冲过期时间,也就是说,即使我们配置了过期时间,这里也要考虑进去,过期时间+缓冲,默认好像是7分钟,你可以直接设置为0
    21. RequireExpirationTime = true,
    22. };
    23. });
    24. services.AddAuthorization(op => {
    25. op.AddPolicy("policy1", p => {
    26. p.AddRequirements(new CustomAuthorizatinRequirement());
    27. });
    28. });
    29. //Swagger文档配置使用http://localhost:5001/swagger
    30. //注册Swagger生成器,定义一个和多个Swagger 文档
    31. services.AddSwaggerGen(b =>
    32. {
    33. b.SwaggerDoc("v1", new OpenApiInfo() {
    34. Title = "api接口调用文档",
    35. Version = "v1",
    36. Description = "系统相关Api文档"
    37. });
    38. //为Swagger Json and UI设置XML文档的路径
    39. var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序的路径
    40. var xmlPath = Path.Combine(basePath, "Swagger.xml");
    41. b.IncludeXmlComments(xmlPath);
    42. });
    43. }

    定义jwt服务接口

    1. public interface IJwtService {
    2. string GetToken(string account);
    3. }

     实现此接口

    1. using Microsoft.Extensions.Configuration;
    2. using Microsoft.IdentityModel.Tokens;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.IdentityModel.Tokens.Jwt;
    6. using System.Linq;
    7. using System.Security.Claims;
    8. using System.Text;
    9. using System.Threading.Tasks;
    10. namespace JwtDemo.Service {
    11. public class JwtService : IJwtService {
    12. private readonly IConfiguration _configuration;
    13. public JwtService(IConfiguration configuration) {
    14. _configuration = configuration;
    15. }
    16. public string GetToken(string account) {
    17. /**
    18. * Claims(Payload)
    19. * Claims包含了一些跟这个token有关的重要信息。JWT标准规定的字段:
    20. *
    21. * iss: The issuer of the token, 签发人
    22. * sub: The subject of the token, 主题
    23. * exp: Expiration Time. 过期时间(Unix时间戳格式)
    24. * iat: Issued At. 签发时间(Unix时间戳格式)
    25. * jti: JWT ID. 编号
    26. * aud: audience. 受众
    27. * nbf: Not Before. 生效时间
    28. *
    29. * 除了规定的字段外,可以包含其他任何JSON兼容的字段。
    30. * */
    31. var roleList = new List<string>() {
    32. "admin",
    33. "test"
    34. };
    35. var claims = new List()
    36. {
    37. new Claim(ClaimTypes.Name, account),
    38. new Claim("account", account),
    39. //new Claim(ClaimTypes.Role, "admin")
    40. };
    41. //填充角色
    42. foreach (var item in roleList) {
    43. claims.Add(new Claim(ClaimTypes.Role, item));
    44. }
    45. var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
    46. var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    47. var token = new JwtSecurityToken(
    48. issuer: _configuration["issuer"], //签发人
    49. audience: _configuration["audience"],//接受方
    50. claims: claims,
    51. expires: DateTime.Now.AddMinutes(20), //20分钟有效期
    52. signingCredentials: credentials);
    53. var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
    54. return tokenStr;
    55. }
    56. }
    57. }

     启用鉴权中间件,这个和之前的方法一样

                app.UseAuthentication();//启用鉴权中间件

    在appsettings.json中配置jwt信息

    1. "SecurityKey": "58CAA81E-1511-EF1F-8C09-4425F7362F11",
    2. "issuer": "http://localhost:5000/",
    3. "audience": "http://localhost:5000/"

    编写登录webapi

    1. using JwtDemo.DTO;
    2. using JwtDemo.Service;
    3. using Microsoft.AspNetCore.Http;
    4. using Microsoft.AspNetCore.Mvc;
    5. using System;
    6. using System.Collections.Generic;
    7. using System.Linq;
    8. using System.Threading.Tasks;
    9. namespace JwtDemo.Controllers {
    10. [Route("api/[controller]/[action]")]
    11. [ApiController]
    12. public class LoginController : Controller {
    13. private readonly IJwtService _jwtService;
    14. public LoginController(IJwtService jwtService) {
    15. _jwtService = jwtService;
    16. }
    17. ///
    18. /// 获取登录的token
    19. ///
    20. ///
    21. ///
    22. [HttpPost]
    23. public IActionResult DoLogin([FromBody] LoginRequestDTO dto) {
    24. bool success = false;
    25. string token;
    26. if(dto.Account=="1646" && dto.Password == "123") {
    27. success = true;
    28. token = _jwtService.GetToken(dto.Account);
    29. }
    30. else {
    31. token = "";
    32. }
    33. return Json(new {
    34. success,
    35. token
    36. });
    37. }
    38. }
    39. }

    通过此DoLogin的webapi我们可以获取到token,那就说明我们可以取到token了。

     然后我们来添加一个测试访问的webapi

    1. ///
    2. /// 测试
    3. ///
    4. ///
    5. [HttpGet]
    6. [Authorize(Roles ="admin")]
    7. public IActionResult Test() {
    8. var claims = HttpContext.AuthenticateAsync().Result.Principal.Claims;
    9. var name = claims.FirstOrDefault(b => b.Type.Equals(ClaimTypes.Name))?.Value;
    10. var account = claims.FirstOrDefault(b => b.Type.Equals("Account"))?.Value;//null检查运算符 不为空时执行
    11. var role = claims.FirstOrDefault(b => b.Type.Equals("Role"))?.Value;
    12. var exp = claims.FirstOrDefault(b => b.Type.Equals("exp"))?.Value;
    13. var expDateTime = DateTime.Now;
    14. if (!string.IsNullOrWhiteSpace(exp)) {
    15. long expValue;
    16. if (long.TryParse(exp, out expValue)) {
    17. expDateTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), TimeZoneInfo.Local);
    18. expDateTime = expDateTime.AddSeconds(expValue);
    19. }
    20. }
    21. return Json(new {
    22. ExpDateTime = expDateTime,
    23. Name = name,
    24. Data = "已授权",
    25. Type = "GetAuthorizeData"
    26. });
    27. }

    我们通过postman来测试,直接访问Test这个webapi,会提示401错误,然后我们通过设置authorization的type选择bear token,然后将我们刚才获取到的token输入到里面,然后再发起请求就可以访问成功了。

     

    角色管理 

    使用jwt同样也存在角色管理这个问题,我们可以在控制器或action上加上特性[Authorize(Roles ="admin")]来设置可以访问的角色,如果刚才我的测试登录的用户未给到admin角色,那么我访问就会报403没有权限的错误。加上admin角色就可以访问成功了。

    自定义策略授权进阶

    使用jwt同样也存在,如果角色写在代码里也不好控制,同样我们也可以通过自定义策略来实现。方法其实跟之前是一样的。

     1.添加CustomAuthorizatinRequirement.cs这个类实现接口IAuthorizationRequirement,这个IAuthorizationRequirement是个空接口,我们直接实现不用写什么代码。

    1. public class CustomAuthorizatinRequirement : IAuthorizationRequirement {
    2. }

    2.添加CustomAuthorizationHandler.cs这个认证处理类,这个类需要实现IAuthorizationRequirement这个接口,不过一般我们都是通过继承AuthorizationHandler泛型抽象类来实现。

    1. public class CustomAuthorizationHandler: AuthorizationHandler<CustomAuthorizatinRequirement> {
    2. private IHttpContextAccessor _httpContext;
    3. public CustomAuthorizationHandler(IHttpContextAccessor httpContext) {
    4. _httpContext = httpContext;
    5. }
    6. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizatinRequirement requirement) {
    7. bool flag=false;
    8. var httpContext = _httpContext.HttpContext;
    9. RouteEndpoint res = context.Resource as RouteEndpoint;
    10. if (res != null) {
    11. if (res.RoutePattern.RequiredValues.Keys.Count() == 2){
    12. var action = res.RoutePattern.RequiredValues.ToList().FirstOrDefault(b => b.Key == "action").Value;
    13. var ctr = res.RoutePattern.RequiredValues.ToList().FirstOrDefault(b => b.Key == "controller").Value;
    14. }
    15. }
    16. var user = context.User.Claims.FirstOrDefault(b => b.Type == "account").Value;
    17. if (user == "1646") {
    18. flag = true;
    19. }
    20. if (flag) {
    21. context.Succeed(requirement);
    22. }
    23. return Task.CompletedTask;
    24. }
    25. }

    3.让自定义策略生效,需要在服务注册services.AddSingleton();还有策略的注册

    1. public void ConfigureServices(IServiceCollection services) {
    2. services.AddControllersWithViews();
    3. services.AddSingleton();
    4. //自定义认证处理的服务注册
    5. services.AddSingleton();
    6. services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    7. .AddCookie(op => {
    8. op.LoginPath = new PathString("/Login");//登录路径
    9. op.AccessDeniedPath = new PathString("/Login/Denied");//无权限地址
    10. });
    11. //策略的注册
    12. services.AddAuthorization(op => {
    13. op.AddPolicy("policy1", p => {
    14. p.AddRequirements(new CustomAuthorizatinRequirement());
    15. });
    16. });
    17. }

    然后在控制器上加上

    1. [HttpGet]
    2. [Authorize(policy:"policy1")]
    3. public IActionResult TestA() {
    4. var claims = HttpContext.AuthenticateAsync().Result.Principal.Claims;
    5. var name = claims.FirstOrDefault(b => b.Type.Equals(ClaimTypes.Name))?.Value;
    6. var account = claims.FirstOrDefault(b => b.Type.Equals("Account"))?.Value;//null检查运算符 不为空时执行
    7. var role = claims.FirstOrDefault(b => b.Type.Equals("Role"))?.Value;
    8. var exp = claims.FirstOrDefault(b => b.Type.Equals("exp"))?.Value;
    9. var expDateTime = DateTime.Now;
    10. if (!string.IsNullOrWhiteSpace(exp)) {
    11. long expValue;
    12. if (long.TryParse(exp, out expValue)) {
    13. expDateTime = TimeZoneInfo.ConvertTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), TimeZoneInfo.Local);
    14. expDateTime = expDateTime.AddSeconds(expValue);
    15. }
    16. }
    17. return Json(new {
    18. ExpDateTime = expDateTime,
    19. Name = name,
    20. Data = "已授权",
    21. Type = "GetAuthorizeData"
    22. });
    23. }

    这样我访问TestA这个webapi的时候会根据CustomAuthorizationHandler这个处理类来进行授权判断,具体判断业务逻辑当然你可以自己组织了。

  • 相关阅读:
    【JAVA】为什么要使用封装以及如何封装
    Redis学习笔记(四)Redis订阅发布&主从复制&哨兵模式
    神经网络 01(介绍)
    设计模式:代理模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)
    新的iLeakage攻击从Apple Safari窃取电子邮件和密码
    【JavaWeb】JSP系列——EL表达式
    技术实践|大模型内容安全蓝军的道与术
    【云原生持续交付和自动化测试】5.2 自动化测试和集成测试
    华为 OD 一面算法原题
    基于FPGA的图像高斯滤波实现,包括tb测试文件和MATLAB辅助验证
  • 原文地址:https://blog.csdn.net/sammy520/article/details/126089299