0、引言

若不清楚什么是JWT的请先了解下什么是JWT

1、关于Authentication与Authorization

我相信在aspnet core中刚接触甚至用了段时间这两个概念的时候都是一头雾水的,傻傻分不清。
认证(Authentication)和授权(Authorization)在概念上比较的相似,且又有一定的联系,因此很容易混淆。
认证(Authentication)是指验证用户身份的过程,即当用户要访问受保护的资源时,将其信息(如用户名和密码)发送给服务器并由服务器验证的过程。
授权(Authorization)是验证一个已通过身份认证的用户是否有权限做某件事情的过程。
有过RBAC的开发经验者来说这里可以这么通俗的来理解:认证是验证一个用户是否“合法”(一般就是检查数据库中是否有这么个用户),授权是验证这个用户是否有做事情的权限(简单理解成RBAC中的用户权限)。

2、整个认证流程是怎样的?

ASP.NET Core 3.1中使用JWT身份认证
从图中可以看到整个认证、授权的流程,先进行身份验证 ,验证通过后将Token放回给客户端,客户端访问资源的时候请求头中添加Token信息,服务器进行验证并于授权是否能够访问该资源。

3、开始JWT身份认证

3.1 安装JwtBearer包

在.csproj项目中添加JWT包(这里添加有很多种方式,自行选择)

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.3" />

3.2 安装Swashbuckle.AspNetCore包

这里便于进行测试,引入Swagger工具。

<PackageReference Include="Swashbuckle.AspNetCore" Version="5.3.1" />

3.3 添加身份认证相关服务到容器中

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = false,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "jonny",
        ValidAudience = "jonny",
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secretsecretsecret"))
    };
});

说明

配置项 类型 说明
ValidateIssuerSigningKey bool 是否调用对签名securityToken的SecurityKey进行验证。
ValidIssuer string 将用于检查令牌的发行者是否与此发行者相同。
ValidateIssuer bool 是否验证发行者
ValidAudience string 检查令牌的受众群体是否与此受众群体相同。
ValidateAudience bool 在令牌验证期间验证受众 。
ValidateLifetime bool 验证生命周期。

3.4 添加Swagger服务到容器中

services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("openapi", new Microsoft.OpenApi.Models.OpenApiInfo
    {
        Title = "统一身份认证API",
        Description = "身份认证和授权详解",
        Version = "v1"
    });
    var scheme = new OpenApiSecurityScheme()
    {
        Scheme = JwtBearerDefaults.AuthenticationScheme,
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        //头名称
        Name = ApiKeyConstants.HeaderName,
        Type = SecuritySchemeType.ApiKey,
        Description = "Bearer Token"
};
options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, scheme);
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
 {
     {
         new OpenApiSecurityScheme
         {
             Reference = new OpenApiReference
             {
                 Type = ReferenceType.SecurityScheme,
                 Id = "Bearer"
             }
         },
         new string[] {}
     }
 });
});

aspnet core 3.x swagger与2.x有细微的差别,例如swagger中加入jwt和以前就有一定的差别。

swagger加入身份认证后出现了认证按钮。
ASP.NET Core 3.1中使用JWT身份认证

3.5 将身份认证加入到管道中

//身份认证中间件(踩坑:授权中间件必须在认证中间件之前)
app.UseAuthentication();

3.x中身份认证一定要在UseRouting和UseEndpoints之间

3.6 将swagger加入到管道中

app.UseSwagger();
app.UseSwaggerUI(options =>
{
     options.RoutePrefix = string.Empty;
     //配置swagger端点
     options.SwaggerEndpoint("swagger/openapi/swagger.json", "openapi v1");
});

3.7 在需要授权的资源上加入Authorize

例如:

[HttpGet("role")]
[Authorize(Roles = "admin")]
public IEnumerable<Claim> GetRole()
{
    return HttpContext.User.FindAll(c => c.Type == ClaimTypes.Role);
}

这里默认使用角色授权机制

4 、测试

4.1 请求资源

这时会返回401,因为没有进行身份认证
ASP.NET Core 3.1中使用JWT身份认证

4.2 调用登录获取token

ASP.NET Core 3.1中使用JWT身份认证

4.3 将token添加到Header中

ASP.NET Core 3.1中使用JWT身份认证

4.4 再次请求

这时返回403,是因为使用的jonny账户登录的没有admin权限。
ASP.NET Core 3.1中使用JWT身份认证

4.5 切换admin账户登录

ASP.NET Core 3.1中使用JWT身份认证
重复上面的4.3、4.4步骤 。再次测试。这时就能正常访问。
ASP.NET Core 3.1中使用JWT身份认证

5、登录逻辑代码

我这里就不做过多的解释 ,直接将相关创建JTW代码等贴出来。

public interface ICustomAuthenticationManager
{
    string Authenticate(string username, string password);

    IDictionary<string, string> Tokens { get; }
}
public class CustomAuthenticationManager : ICustomAuthenticationManager
{
    private readonly IDictionary<string, string> users = new Dictionary<string, string>
    {
        { "admin", "admin" },
        { "jonny", "jonny" },
        { "xhl", "xhl" },
        { "james", "james" }
    };

    public IDictionary<string, string> Tokens { get; } = new Dictionary<string, string>();

    public string Authenticate(string username, string password)
    {
        var claimsIdentity = new ClaimsIdentity(new[]{
            new Claim(ClaimTypes.Name,username)
        });
        if (!users.Any(u => u.Key == username && u.Value == password))
        {
            return null;
        }
        if (username == "admin")
        {
            claimsIdentity.AddClaims(new[]
            {
                new Claim( ClaimTypes.Email, "xhl.jonny@gmail.com"),
                new Claim( "ManageId", "admin"),
                new Claim(ClaimTypes.Role,"admin")
            });
        }
        var handler = new JwtSecurityTokenHandler();
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = claimsIdentity,
            Expires = DateTime.Now.AddMinutes(3),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("secretsecretsecret")), SecurityAlgorithms.HmacSha256),
        };
        var securityToken = handler.CreateToken(tokenDescriptor);
        var token = handler.WriteToken(securityToken);
        Tokens.Add(token, username);
        return token;
    }
}

上面使用内存数据进行逻辑验证 ,实际中需要使用数据库查询验证等。

今天的JWT身份认证就介绍完了 ,下一篇文章将介绍授权。角色授权、身份授权(Claim)、自定义策略授权。

相关文章: