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

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

    一、JWT

    JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

    JWT是什么,看上面这段网上抄来的话。

    关于JWT以及优缺点,网上有很多详细的说法,我这儿就不重复了。

    我们只需要知道以下的事实:

    在一般的系统中,我们有时候会做个用户登录。用户登录完成进到系统后,需要根据用户的权限,来控制一些功能可用,而另一些功能不可用。

    在SOA/AOP架构中,做为最重要的API端,其实也需要有类似登录或认证的内容,用来区分哪些用户可以使用某个API,哪些用户不行。

    同时,我们希望这个登录或类似登录的过程,只发生在一个固定位置。这样,在我们写代码时,建立好这样一个过程后,在我们后边写代码时,简单引用即可,而不需要每个API程序都开发一次认证。这个需求,其实就是OAuth的由来。

    最重要的是,这样的代码写出来,显得高大上

    下面进入正题。

    认证这个操作,就像我们最近的日子。

    首先,我们要有一个出入证,或者绿码。这个证,我们称作令牌(Token)。我们去领这个证,这个操作称为发行(Issue)。

    我们拿着这个证,去到一个地方。有专人会检查这个证,这称为用户身份验证(Authentication)。验证通过放行,称为授权(Authorization),验证不通过,叫作未授权错误(Unauthorized)。

    如果这个证过期了,你就需要去重新办一个证。这个过程叫做刷新(RefreshToken)。

    简言之,这就是认证的全部流程。

    下面,我用一个Demo项目,来逐步完成这个过程。

    二、开发环境&基础项目

    这个Demo的开发环境是:Mac + VS Code + Dotnet Core 3.1.2。

    $ dotnet --info
    .NET Core SDK (reflecting any global.json):
     Version:   3.1.201
     Commit:    b1768b4ae7

    Runtime Environment:
     OS Name:     Mac OS X
     OS Version:  10.15
     OS Platform: Darwin
     RID:         osx.10.15-x64
     Base Path:   /usr/local/share/dotnet/sdk/3.1.201/

    Host (useful for support):
      Version: 3.1.3
      Commit:  4a9f85e9f8

    .NET Core SDKs installed:
      3.1.201 [/usr/local/share/dotnet/sdk]

    .NET Core runtimes installed:
      Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
      Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

    首先,在这个环境下建立工程:

    1. 创建Solution
    % dotnet new sln -o demo
    The template "Solution File" was created successfully.
    1. 用Webapi模板创建项目
    cd demo
    % dotnet new webapi -o demo
    The template "ASP.NET Core Web API" was created successfully.

    Processing post-creation actions...
    Running 'dotnet restore' on demo/demo.csproj...
      Restore completed in 179.13 ms for demo/demo.csproj.

    Restore succeeded.
    1. 把Demo项目加到Solution中
    % dotnet sln add demo/demo.csproj
    Project `demo/demo.csproj` added to the solution.
    1. 安装Swagger(这步非必须,我习惯用Swagger,不习惯用Postman)
    % dotnet add package Swashbuckle.AspNetCore
    log  : Restore completed in 2.75 sec for demo/demo.csproj.
    1. 安装JWT认证支持库(必须引入)
    % dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
    log  : Restore completed in 3.09 sec for demo/demo.csproj.

    五步做完,基础项目就建完了。

    看一下整个的目录结构:

    % tree .
    .
    ├── demo
    │   ├── Controllers
    │   │   └── WeatherForecastController.cs
    │   ├── Program.cs
    │   ├── Properties
    │   │   └── launchSettings.json
    │   ├── Startup.cs
    │   ├── WeatherForecast.cs
    │   ├── appsettings.Development.json
    │   ├── appsettings.json
    │   ├── demo.csproj
    │   └── obj
    │       ├── demo.csproj.nuget.dgspec.json
    │       ├── demo.csproj.nuget.g.props
    │       ├── demo.csproj.nuget.g.targets
    │       ├── project.assets.json
    │       └── project.nuget.cache
    └── demo.sln

    1. 在Startup.cs中补充代码,以启用Swagger

    在ConfigureServices方法中加入以下代码:

    services.AddSwaggerGen(c =>
    {
            c.SwaggerDoc("v1"new OpenApiInfo { Title = "Demo", Version = "V1" });

            c.AddSecurityDefinition("Bearer"new OpenApiSecurityScheme
            {
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    Description = "",
            });

            c.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                    {
                            new OpenApiSecurityScheme
                            {
                                    Reference = new OpenApiReference
                                    {
                                            Type = ReferenceType.SecurityScheme,
                                            Id = "Bearer"
                                    }
                            },
                            new string[] {}
                    }
            });
    });

    在Configure方法中加入以下代码

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
            c.SwaggerEndpoint("/swagger/v1/swagger.json""Demo V1");
    });

    关于Swagger的详细配置,这里不做说明,留着以后写。

    三、签发Token

    签发Token是认证的第一步。

    用户进到系统,在验证用户帐号密码后,需要根据用户的数据,把Token返回给用户。

    这个过程其实跟认证没什么关系,只是一个普通的API功能。

    1. 工程下加一个目录DTOModels,创建一个LoginRequestDTO的类,用于定义API的输入参数。
    using System;

    namespace demo.DTOModels
    {
        public class LoginRequestDTO
        {

            public string username { get; set; }
            public string password { get; set; }
        }
    }
    1. 创建一个控制器AuthenticationController,并在控制器里创建一个API方法RequestToken。
    using Microsoft.AspNetCore.Mvc;
    using demo.DTOModels;

    namespace demo.Controllers
    {
        public class AuthenticationController : ControllerBase
        {
            [HttpPost, Route("requesttoken")]
            public ActionResult RequestToken([FromBody] LoginRequestDTO request)
            
    {
                  //这儿待完善
                return Ok();
            }
        }
    }
    1. 生成JWT Token需要预设一些参数。我们在appsetting.json里先设置好。
    {
      "Logging": {
        "LogLevel": {
          "Default""Information",
          "Microsoft""Warning",
          "Microsoft.Hosting.Lifetime""Information"
        }
      },
      "AllowedHosts""*",
      "tokenParameter": {
        "secret""123456123456123456",
        "issuer""WangPlus",
        "accessExpiration"120,
        "refreshExpiration"1440
      }
    }

    这里,tokenParameter节是我们设置的参数。一般来说,是这几个:

    secret: JWT加密的密钥。现在主流用SHA256加密,需要256位以上的密钥,unicode是16个字符以上,尽量复杂一些。密钥泄露,Token就会被破解,所以,你懂的。

    issuer: 签发人的名称,如果没人注意,你可以把大名写在上面。

    accessExpiration: Token的有效分钟数。过了这个时间,这个Token会过期。

    refreshExpiration: refreshToken的有效分钟数。过了这个时间,用户需要重新登录。

    Token过期后,可以让用户重新登录认证拿Token。但这个方式会比较Low。高大上的方式是签发Token的时候,同时也签发一个refreshToken给用户。用户Token过期后,可以拿refreshToken去申请新的Token,同时刷新refreshToken。如果用户长时间未使用系统,refreshToken也过期了,才让用户重新登录认证。

    refreshToken可以用JWT生成,也可以自己生成,不影响认证。

    1. 建一个Models目录,创建一个映射tokenParameter的类。这个类不是必须,只是为了写着方便。不想这样写,也可以直接读配置,再转成数据。
    using System;

    namespace demo.Models
    {
        public class tokenParameter
        {

            public string Secret { get; set; }
            public string Issuer { get; set; }
            public int AccessExpiration { get; set; }
            public int RefreshExpiration { get; set; }
        }
    }
    1. 在前边建好的API - RequestToken中,完成Token和refreshToken的生成和返回。
    using Microsoft.AspNetCore.Mvc;
    using demo.DTOModels;
    using Microsoft.Extensions.Configuration;
    using System;
    using System.Text;
    using demo.Models;
    using Microsoft.IdentityModel.Tokens;
    using System.Security.Claims;
    using System.IdentityModel.Tokens.Jwt;

    namespace demo.Controllers
    {
        public class AuthenticationController : ControllerBase
        {
            private tokenParameter _tokenParameter = new tokenParameter();
            public AuthenticationController()
            
    {
                var config = new ConfigurationBuilder()
                    .SetBasePath(AppContext.BaseDirectory)
                    .AddJsonFile("appsettings.json")
                    .Build();

                _tokenParameter = config.GetSection("tokenParameter").Get<tokenParameter>();
            }

            [HttpPost, Route("requestToken")]
            public ActionResult RequestToken([FromBody] LoginRequestDTO request)
            
    {
                //这儿在做用户的帐号密码校验。我这儿略过了。
                if (request.username == null && request.password == null)
                    return BadRequest("Invalid Request");

                  //生成Token和RefreshToken
                var token = GenUserToken(request.username, "testUser");
                var refreshToken = "123456";

                return Ok(new[] { token, refreshToken });
            }


              //这儿是真正的生成Token代码
              private string GenUserToken(string username, string role)
            
    {
                var claims = new[]
                {
                    new Claim(ClaimTypes.Name, username),
                    new Claim(ClaimTypes.Role, role),
                };

                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenParameter.Secret));
                var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                var jwtToken = new JwtSecurityToken(_tokenParameter.Issuer, null, claims, expires: DateTime.UtcNow.AddMinutes(_tokenParameter.AccessExpiration), signingCredentials: credentials);

                var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

                return token;
            }
        }
    }

    这个类里,验证帐号密码的代码我略过了。还有,refreshToken给了一个固定串。真实项目这儿就按需要做就好。

    (未完待续)


    微信公众号:老王Plus

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

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

  • 相关阅读:
    pytest简介
    python @property的用法及含义全面解析
    python的各种推导式(列表推导式、字典推导式、集合推导式)
    python--random库基本介绍
    整理一下python中with的用法
    Python之路:进程、线程
    Python代码风格的良好养成
    Ubuntu 部署Python开发环境
    Python面向对象编程
    Python文件操作
  • 原文地址:https://www.cnblogs.com/tiger-wang/p/12892383.html
Copyright © 2011-2022 走看看