.NET Core中配置 JWT 授认证

本文主要介绍如何在 .NET Core 项目中配置基于 JWT 的 Token 验证。

前言

.NET Core 中使用 AuthenticationAuthorization 来进行权限控制。这两个单词长的十分相似,而且还经常一起出现,很多时候容易搞混了。

Authentication 是认证的意思,当用户请求到来时,判断用户是否是合法的,比如用户名密码是否正确,token 是否合法且有效等等。它是 Web 服务的第一道门。

Authorization 是授权的意思,当用户通过认证后,还需要验证是否有访问某个接口的权限。授权使用 Authorize 在 Action(方法) 上进行标标记。

默认情况下,认证通过即认为有权限访问相关的接口。也可以基于角色、策略、自定义的方式来实现接口的授权。

配置 JWT 授权

本文使用的 .NET Core 版本为 6.1

安装包

使用 jwt 验证需要安装两个包:

  1. System.IdentityModel.Tokens.Jwt
  2. Microsoft.AspNetCore.Authentication.JwtBearer

添加 Token 配置

在 appsettings.json 添加如下内容:

1
2
3
4
5
6
"TokenParam": {
"Secret": "15927306782",
"Expire": 43200000,
"Issuer": "wowToolAPI",
"Audience": "everybody"
},

添加 JWT 服务配置

在 Program.cs 中的 var app = builder.Build(); 前添加一个服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 从 appsettings.json 配置中获取 Secret 值
var secret = builder.Configuration["TokenParam:Secret"];

services.AddAuthentication(x =>
{
// 设置验证方式为 Bearer Token
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)),
ValidateIssuer = false,
ValidateAudience = false
// 是否验证令牌有效期
ValidateLifetime = true,
// 每次颁发令牌,令牌有效时间
ClockSkew = TimeSpan.FromMinutes(1440)
};
});

添加验证中间件

在 Program.cs 中添加下列代码:

1
2
3
4
// 添加认证中间件
app.UseAuthentication();
// 添加授权中间件
app.UseAuthorization();

上述两个中间件必须要同时添加,顺序也不能错

在 Controller 中添加授权控制

需要在 Controller 或者 Action 上添加 [Authorize] 特性,该特性表明当前 Controller 或 Action 需要先进行授权验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 不适用特性,可以直接访问
public class AController : ControllerBase
{
public string Get() { return "666"; }
}

/// <summary>
/// 整个控制器都需要授权才能访问
/// </summary>
[Authorize]
public class BController : ControllerBase
{
public string Get() { return "666"; }
}

public class CController : ControllerBase
{
// 只有 Get 需要授权
[Authorize]
public string Get() { return "666"; }
public string GetB() { return "666"; }
}

/// <summary>
/// 整个控制器都需要授权,但 Get 不需要
/// </summary>
[Authorize]
public class DController : ControllerBase
{
[AllowAnonymous]
public string Get() { return "666"; }
}

一次将授权添加到所有的 Controller 上

在开发中,一般只有个别 API 不需要权限认证,如果每添加一个 Controller,都在上面标记 [Authorize],使用起来挺难受的。

有没有办法只在一个地方设置一次,全部 Controller 都添加了呢?

当然,咱们可以先添加一个基类,在这个类上添加 [Authorize] 特性。然后其它的 Controller 只要继承这个基类就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 不适用特性,可以直接访问
public class MyControllerBase : ControllerBase
{
}

/// <summary>
/// 整个控制器都需要授权
/// </summary>
[Authorize]
public class EController : MyControllerBase
{
}

/// <summary>
/// 整个控制器都不需要授权
/// </summary>
[AllowAnonymous]
public class FController : MyControllerBase
{
}

生成 JWT token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// <summary>
/// 创建 token
/// </summary>
/// <param name="payload"></param>
/// <returns></returns>
public static string CreateToken(this TokenParams tokenParam, Dictionary<string, string> payload)
{

// 定义用户信息
var claims = new List<Claim>();
if (payload != null)
{
claims = payload.ToList().ConvertAll(kv =>
{
return new Claim(kv.Key, kv.Value);
});
}

// 和 Startup 中的配置一致
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(tokenParam.Secret));

JwtSecurityToken token = new(
issuer: tokenParam.Issuer,
audience: tokenParam.Audience,
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddMinutes(1440),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);

string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
return jwtToken;
}

从请求中获取 JWT

从请求中获取 token 字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected string GetToken()
{
string tokenHeader = Request.Headers[HeaderNames.Authorization].ToString();
if (string.IsNullOrEmpty(tokenHeader))
throw new ArgumentNullException("缺少token!");

string pattern = "^Bearer (.*?)$";
if (!Regex.IsMatch(tokenHeader, pattern))
throw new Exception("token格式不对!格式为:Bearer {token}");

string? token = Regex.Match(tokenHeader, pattern)?.Groups[1]?.ToString();
if (string.IsNullOrEmpty(token))
throw new Exception("token不能为空!");

return token;
}

解析 token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Result<JObject> GetTokenPayload(this TokenParams tokenParam, string token)
{
//校验token
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = tokenParam.Issuer,
ValidAudience = tokenParam.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenParam.Secret))
};

//校验并解析token
new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out SecurityToken validatedToken);//validatedToken:解密后的对象
var jwtPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); //获取payload中的数据
var jobj = JObject.Parse(jwtPayload);

return jobj;
}

参考

  1. ASP.NET Core 中jwt授权认证的流程原理
  2. UseAuthentication和UseAuthorization
  3. https://zhuanlan.zhihu.com/p/176966592
  4. ASP.NET Core 6.0 添加 JWT 认证和授权