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

0、引言

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

1、关于Authentication与Authorization

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

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

整个HTTP请求流程
从图中可以看到整个认证、授权的流程,先进行身份验证 ,验证通过后将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"))
    };
});

说明

配置项类型说明
ValidateIssuerSigningKeybool是否调用对签名securityToken的SecurityKey进行验证。
ValidIssuerstring将用于检查令牌的发行者是否与此发行者相同。
ValidateIssuerbool是否验证发行者
ValidAudiencestring检查令牌的受众群体是否与此受众群体相同。
ValidateAudiencebool在令牌验证期间验证受众 。
ValidateLifetimebool验证生命周期。
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加入身份认证后出现了认证按钮。
在这里插入图片描述

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,因为没有进行身份认证
在这里插入图片描述

4.2 调用登录获取token

在这里插入图片描述

4.3 将token添加到Header中

在这里插入图片描述

4.4 再次请求

这时返回403,是因为使用的jonny账户登录的没有admin权限。
在这里插入图片描述

4.5 切换admin账户登录

在这里插入图片描述
重复上面的4.3、4.4步骤 。再次测试。这时就能正常访问。
在这里插入图片描述

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)、自定义策略授权。

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
使用 JWT 进行身份验证时,通常会设置一个过期时间,以确保用户在一段时间内保持登录状态。当 JWT 过期时,用户需要重新登录并获取新的 JWT 令牌。为了避免用户频繁重新登录,我们可以使用刷新令牌的方式来延长用户的登录状态。 在 .NET Core 3.1 ,我们可以使用 JwtSecurityTokenHandler 类来生成和验证 JWT 令牌。下面是一个简单的 JwtHelper 类的实现,它支持生成、刷新和验证 JWT 令牌: ```csharp using Microsoft.IdentityModel.Tokens; using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; public class JwtHelper { private static readonly string secret = "your_secret_key_here"; private static readonly string issuer = "your_issuer_here"; private static readonly int expireMinutes = 30; private static readonly int refreshExpireMinutes = 60; public static string GenerateToken(string userId) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId) }; var tokenDescriptor = new SecurityTokenDescriptor { Issuer = issuer, Audience = issuer, Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddMinutes(expireMinutes), SigningCredentials = credentials }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } public static string RefreshToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); try { var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidIssuer = issuer, ValidAudience = issuer, IssuerSigningKey = securityKey, ValidateLifetime = false }, out SecurityToken validatedToken); var jwtToken = validatedToken as JwtSecurityToken; if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) { throw new SecurityTokenException("Invalid token"); } var userId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier).Value; return GenerateToken(userId); } catch(Exception ex) { throw new SecurityTokenException("Invalid token", ex); } } public static bool ValidateToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)); try { var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidIssuer = issuer, ValidAudience = issuer, IssuerSigningKey = securityKey, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); var jwtToken = validatedToken as JwtSecurityToken; if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) { throw new SecurityTokenException("Invalid token"); } return true; } catch(Exception) { return false; } } } ``` 在上面的代码,我们定义了一个静态的 secret 字符串来存储 JWT 的密钥,一个 issuer 字符串来存储 JWT 的发行者,以及一个 expireMinutes 和 refreshExpireMinutes 来分别设置 JWT 令牌和刷新令牌的过期时间。在 GenerateToken 方法,我们使用 JwtSecurityTokenHandler 类来创建一个 JWT 令牌,并设置其过期时间和签名凭证。在 RefreshToken 方法,我们首先验证传入的 JWT 令牌是否有效,并提取其的用户 ID。然后,我们使用 GenerateToken 方法来生成一个新的 JWT 令牌。在 ValidateToken 方法,我们验证传入的 JWT 令牌是否有效,并返回一个布尔值表示验证结果。 使用 JwtHelper 类非常简单,只需要调用其的 GenerateToken、RefreshToken 和 ValidateToken 方法即可。下面是一个示例: ```csharp var token = JwtHelper.GenerateToken("user_id"); var isValid = JwtHelper.ValidateToken(token); var refreshedToken = JwtHelper.RefreshToken(token); ``` 需要注意的是,由于 JWT 令牌是无状态的,因此在刷新令牌时,我们需要使用一些外部存储机制(如数据库或缓存)来存储每个用户的刷新令牌。当用户请求刷新令牌时,我们可以从外部存储检索出用户的刷新令牌,并验证其有效性后生成新的 JWT 令牌。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值