在前后端分离的架构中,前端需要通过 API 接口的方式获取数据,但 API 是无状态的,没有办法知道每次请求的身份,也就没有办法做权限的控制。
如果不做控制,API 就对任何人敞开了大门,只要拿到了接口地址就可以进行调用,这是非常危险的。
本文主要介绍下在 dotNET Core Web API 中使用 Jwt 来实现接口的认证。
Jwt 简介
Jwt 的全称是 JSON Web Token,是目前比较流行的接口认证解决方案。有了 Jwt 后,从客户端请求接口的流程如下图:
- 客户端发送用户名密码信息到认证服务器获取 token;
- 客户端请求 API 获取数据时带上 token;
- 服务器端验证 token,合法则返回正确的数据。
有一个网站叫:https://jwt.io/ ,我们在这个站点上对 Jwt 产生的 token 做验证:
从上图可以看出 Jwt 生产的 token 由三个部分组成:
- Header(红色):头
- Playload(紫色):负载
- Verify Sigantuer(蓝色):签名
这三个部分由英文的点进行分隔开
|
Header
头信息是一个 Json 格式的数据
|
- alg:表示加密的算法
- typ:表示 token 的类型
Playload
Playload 是 token 的主体内容部分,我们可以用来传递一些信息给客户端,比如过期时间就是通过 Playload 来进行传递的。 但因为默认情况下 Playload 的数据是明文的,所以敏感信息不要放在这里。
Verify Sigantuer
Verify Sigantuer 是对前面两个部分的签名,防止数据篡改。
使用 Jwt
下面一步步介绍在 dotNET Core Web API 项目中使用 Jwt:
1、添加 Jwt 的包引用
在 Web API 项目中添加对 Microsoft.AspNetCore.Authentication.JwtBearer
包的引用
2、修改 Starup
在Starup 类中的ConfigureServices 方法添加如下代码:
//解决跨域问题
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
// jwt 认证
JwtSettings jwtSettings = new JwtSettings();
services.Configure<JwtSettings>(Configuration.GetSection("JwtSettings"));
Configuration.GetSection("JwtSettings").Bind(jwtSettings);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.Issuer,
ValidAudience = jwtSettings.Audience,
//用于签名验证
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.SecretKey)),
ValidateIssuer = false,
ValidateAudience = false
};
});
3、添加 JwtSettings 对象
public class JwtSettings
{
public string Issuer { get; set; }
public string Audience { get; set; }
public string SecretKey { get; set; }
}
4、appsettings.json 文件中添加 JwtSettings 配置:
SecretKey 字段必须是16的倍数。
"JwtSettings": {
"Audience": "123456",
"Issuer": "123456",
"SecretKey": "ihGpquvKl4POannM"
}
5、Startup 类中的 Configure 方法添加如下代码:
//解决跨域问题
app.UseCors("CorsPolicy");
IdentityModelEventSource.ShowPII = true;
app.UseRouting();
//顺序不能颠倒
app.UseAuthentication(); app.UseAuthorization();
6、添加AuthorizeController 控制器
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using coreTest.Common.Model;
using coreTest.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace coreTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthorizeController : ControllerBase
{
private readonly JwtSettings _jwtSettings;
public AuthorizeController(IOptions<JwtSettings> options)
{
_jwtSettings = options.Value;
}
[HttpPost]
[Route("Token")]
public Result<string> Token(User user)
{
Result<string> result = new Result<string>();
if (user == null) {
result.Data = string.Empty;
return result;
}
if (user.UserName == "admin" && user.Pwd == "123456")
{
var claims = new Claim[]
{
new Claim(ClaimTypes.Name,user.UserName)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_jwtSettings.Issuer,
_jwtSettings.Audience,
claims,
DateTime.Now,
DateTime.Now.AddMinutes(10),
creds);
string res = new JwtSecurityTokenHandler().WriteToken(token);
result.Data = res;
return result;
}
return result;
}
}
}
Result 类:
public class Result<T>
{
public Result()
{
ReturnCode = ReturnCode.Fail;
ReturnMsg = "未知错误";
}
public ReturnCode ReturnCode { get; set; }
public string ReturnMsg { get; set; }
public T Data { get; set; }
}
7、在需要进行认证的控制器或接口方法上添加 [Authorize]
标记。 添加 SysUserController 控制器 ,如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using coreTest.IService;
using coreTest.Model;
using Dapper.Extension.AspNetCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace coreTest.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class SysUserController : ControllerBase
{
private readonly ILogger<SysUserController> logger;
private readonly IDapper dapper;
private readonly ISysUserService sysUserService;
public SysUserController(ILogger<SysUserController> _logger, IDapper _dapper, ISysUserService _sysUserService)
{
logger = _logger;
dapper = _dapper;
sysUserService = _sysUserService;
}
[HttpGet]
[Route("Index")]
public JsonResult Index(int pageIndex = 1, int pageSize = 10)
{
var result = sysUserService.GetList(pageIndex, pageSize);
return new JsonResult(result);
}
}
}
8、使用 Postman 测试
1)、调用接口 http://localhost:5000/api/Authorize/token
获取 token。
2)、在请求接口时使用 Authorization 的方式使用 token,token 的类型为 Bearer Token ,可以看到带上 token 后,数据正常返回。
9、前端ajax调用案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.5.1.js"></script>
</head>
<body>
<input type="button" value="登录" onclick="submit()" />
<input type="button" value="获取数据" onclick="getData()" />
<script>
let Authorization = '';
function submit(){
console.log(111);
let url = "http://localhost:4213/api/Authorize/Token";
let p = {
"userName": "admin",
"pwd": "123456"
};
$.ajax({
type: 'post',
url: url,
dataType: 'json',
cache: false,
headers : {
'Content-Type' : 'application/json;charset=utf-8',
},
data: JSON.stringify(p),
success: function (data) {
console.log('success');
console.log(data);
console.log(data.data);
Authorization = 'Bearer '+data.data;
},
error: function(er){
console.log('error');
console.log(er);
console.log(er.responseText);
}
});
}
function getData(){
let getUrl = "http://localhost:4213/api/SysUser/Index?pageIndex=1&pageSize=10";
$.ajax({
type: 'get',
url: getUrl,
dataType: 'json',
cache: false,
headers : {
'Content-Type' : 'application/json;charset=utf-8',
'Authorization':Authorization
},
// data: JSON.stringify(paramData),
success: function (data) {
console.log(data);
},
error: function(er){
}
});
}
</script>
</body>
</html>