zoukankan      html  css  js  c++  java
  • Dotnet core使用JWT认证授权最佳实践(二)

    最近,团队的小伙伴们在做项目时,需要用到JWT认证。遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作。

    第一部分:Dotnet core使用JWT认证授权最佳实践(一)

    (接上文)

    1. 测试运行
    % dotnet run

    等程序运行起来后,在浏览器输入:http://localhost:5000/swagger/,会进到Swagger的API界面。选择requestToken,点击按钮”Try it out“->”Execute“,可以看到运行结果:

    ["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec","123456"]

    好吧,不要在意这个返回的格式。返回的两个串中,第一个就是Token,第二个是refreshToken。

    到这儿,我们成功拿到了用户的Token。

        为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/12894021.html

    四、Token认证

    拿到Token后,我们就可以进行认证操作了。

    既然是认证,那应该在每个API上进行。所以,认证的过程不会放到控制器里,而应该以MiddleWare的方式,放到主流程中。

    这个MiddleWare,Microsoft.AspNetCore.Authentication.JwtBearer库已经帮我们做好了。我们只需要配置就好。

    1. 在Startup.cs中,ConfigureServices方法里,添加以下内容
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(option =>
    {
        option.RequireHttpsMetadata = false;
            option.SaveToken = true;

            var token = Configuration.GetSection("tokenParameter").Get<tokenParameter>();

            option.TokenValidationParameters = new TokenValidationParameters
            {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
                    ValidIssuer = token.Issuer,
                    ValidateIssuer = true,
                    ValidateAudience = false,
            };
    });

    这里面,有几个参数需要注意:

    RequireHttpsMetadata: 限定认证操作是否必须通过https来做,这个要跟随项目在生产环境中的运行情况来定。如果WebServer是我前文15分钟从零开始搭建支持10w+用户的生产环境(三)中介绍的Jexus,采用对外https,对内http的方式,那这儿可以设为false。

    SaveToken: 决定Token在认证完成后,是否需要保存到上下文里并向后传。这个设置也要看应用。我们Token生成后,用户的相关信息已经包含在里面了。API里如果有涉及用户的操作,按理可以不用往API里传相关用户的参数。一方面不安全,另一方面代码也不好看。这时就可以把这个参数设为True,然后API从上下文中直接取用户信息。

    1. 在Startup.cs里,Configure方法中,打开认证
    app.UseAuthentication();
    app.UseAuthorization();

    这两步完成,我们就完成的认证的开发工具。

    用别人的轮子还是很爽的,虽然轮子的挑选工作很复杂很费力。

    1. 设置API认证。

    在这个Demo里,我们选代码生成时给的WeatherForecastController下的Get方法来测试。

    在方法前边,我们加上Authorize:

    [HttpGet]
    [Authorize]
    public IEnumerable<WeatherForecast> Get()
    ...
    1. 测试运行。

    启动程序,跟上一章的方式一样。

    程序运行后,打开:http://localhost:5000/swagger/,进入WeatherForecast,点”Try it out“->”Execute“,我们会得到一个401 - Error: Unauthorized的返回,因为我们没有做认证。

    下面测试做认证后的访问。

    先去requestToken拿一个Token(refreshToken这章不用),在前边加“Bearer ”,拼成一个串

    Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoic3RyaW5nIiwiZXhwIjoxNTg5MzgxMzQ4LCJpc3MiOiJXYW5nUGx1cyJ9.ojGuWUk9i2Vp5qu3s2UZSLC64Sm95Cao2eGF3GDVvec

    要注意,Bearer后边要跟一个空格。这个串的格式是:Bearer + 空格 + Token。

    在页面的右上角,有一个“Authorize”,点进去,在Value输入框中粘贴上面拼好的串,然后点按钮“Authorize”,保存认证信息。

    下面进入WeatherForecast,点”Try it out“->”Execute“,这时候,我们就能拿到正确的返回数据。

    五、扩展:用户角色认证

    在上一章中,我们实现了用户的认证。但这个认证有个不漂亮的地方:用户只简单的被认证系统分成了通过认证的和不通过认证的。

    在实际项目中,我们有时候会有这样的需求:对于某个API,我们希望只允许具有某种角色权限的用户去访问。

    下面,我们对这个项目进行小量的修改,以完成这个需求。

    1. 在给用户签发Token的过程中,加入用户的角色数据。

    在AuthenticationController的RequestToken中,我们构建了一个用户的Claims:

    var claims = new[]
    {
        new Claim(ClaimTypes.Name,request.username),
    };

    就是这儿。我们在这儿加入用户的角色:

    var claims = new[]
    {
        new Claim(ClaimTypes.Name,request.username),
          new Claim(ClaimTypes.Role, "testUser"),
    };

    实际应用中,这个角色的名称,可以根据需要,从用户系统中拿来。

    在这个Demo里,就直接写成个字符串了。就是说,有一个角色,叫testUser。

    1. 给API增加认证的角色要求
    [HttpGet]
    [Authorize(Roles="testUser")]
    public IEnumerable<WeatherForecast> Get()
    ...

    在这里,这个Roles="testUser"里的testUser,就是这个方法授权所对应的角色名称。

    1. 测试运行

    按正常的步骤,取Token,拼串,保存认证信息,然后去运行WeatherForecast,API能正常返回。

    我们可以把代码中的testUser改成别的字符串进行测试,会返回403 - Error: Forbidden错误。

    增加角色认证成功。

    六、刷新Token

    Token过期后,就需要刷新。

    当然我们可以把Token设成永远不过期,但这不是个安全的做法。还可以在Token过期后重新请求一个新Token,但这样做会显得Low。

    赏心悦目的做法是:用refreshToken来刷新Token。设置refreshToken的过期时间长于Token。Token过期后,让用户提交Token和refreshToken到服务器,服务器验证Token是否合法,并从中提取用户信息,根据用户信息和refreshToken核验是否匹配。如果匹配,就重新生成Token给用户。

    至于refreshToken的过期时长,和是否需要在刷新Token时也刷新refreshToken,就看心情了,没有固定的做法。我自己的项目中,Token是2小时过期,refreshToken是24小时过期。在Token刷新时,如果refreshToken的过期时间少于6小时,则刷新refreshToken。供参考。

    下面,按这个方式,做一下刷新Token。

    1. 在DTOModels下建一个RefreshTokenDTO,用作API的输入参数
    using System;

    namespace demo.DTOModels
    {
        public class RefreshTokenDTO
        {

            public string Token { get; set; } 
            public string refreshToken { get; set; }
        }
    1. 在AuthenticationController里,创建一个RefreshToken的API,并补齐验证代码
    [HttpPost, Route("refreshToken")]
    public ActionResult RefreshToken([FromBody]RefreshTokenDTO request)
    {
        if(request.Token == null && request.refreshToken == null)
            return BadRequest("Invalid Request");

            //这儿是验证Token的代码
        var handler = new JwtSecurityTokenHandler();
        try
        {
            ClaimsPrincipal claim = handler.ValidateToken(request.Token, new TokenValidationParameters{
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_tokenParameter.Secret)),
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = false,
            }, out SecurityToken securityToken);

            var username = claim.Identity.Name;

            //这儿是生成Token的代码
            var token = GenUserToken(username, "testUser");

            var refreshToken = "654321";

            return Ok(new[] { token, refreshToken });
        }
        catch(Exception)
        {
            return BadRequest("Invalid Request");
        }
    }

    这样,Token刷新就完成了。可以用生成Token运行测试,能正常认证通过。

    1. 单独说一下refreshToken

    refreshToken,名义上是为了刷新Token,实际上用处主要是给用户重新登录做计时。refreshToken过期了,用户就必须重新登录。就是这么个作用。要不然,Token自己刷新岂不更好?

    refreshToken可以采用跟Token一样的生成方式。但是,我们也看到,Token生成出来的串就很长,如果refreshToken也那样生成,那就也会是一个很长的串。这样会加大前端到API的传输量。因此,这不算是一个好主意。

    一般来说,refreshToken会换一种生成方式。唯一序列、Hash,都是可以选择的,可以减少很多传输。

    至于持久化和过期,依托数据库就好了。

    七、彩蛋

    最后,送大家一个彩蛋。

    在生成Token时,我们把过期时间设置成少于五分钟的时长,比方3分钟。但这时,实测会发现,Token的过期失效了。

    为什么呢?

    TokenValidationParameters有一个属性叫ClockSkew,这个参数有个默认值是TimeSpan.FromMinutes(5)。

    这个参数的意义是:考虑到各个服务器之间的时间不一定完全同步,系统给了个5分钟的误差时间。

    这个误差时间导致的结果是:少于五分钟的过期时间,会在实际认证检查时被忽略。

    这个情况,Microsoft上有N多人在讨论,可以自己去查。

    所以,当Token的过期小于5分钟时,想要让认证对这个时间生效,可以把这个值设为TimeSpan.Zero。

    option.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
        ValidIssuer = token.Issuer,
        ValidateIssuer = true,
        ValidateAudience = false,
        ClockSkew = TimeSpan.Zero,        //就是这一行
    };

    我把上面的代码,传到了Github上,需要了可以拉下来直接测试。

    代码地址:https://github.com/humornif/Demo-Code/tree/master/0007/demo

    (全文完)


    微信公众号:老王Plus

    扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

    本文版权归作者所有,转载请保留此声明和原文链接

  • 相关阅读:
    mfc crc校验工具
    MFC 配置附加目录
    多线程中如何使用gdb精确定位死锁问题
    符号冲突
    动态库之间单例模式出现多个实例(Linux)
    c++普通函数在头文件定义报重复定义的错误。而class定义不会
    static初始化顺序及延伸
    tcmalloc使用中出现崩溃问题记录
    shell脚本—判断***是否安装
    【1080TI驱动+CUDA10.1+cudnn】安装记录
  • 原文地址:https://www.cnblogs.com/tiger-wang/p/12894021.html
Copyright © 2011-2022 走看看