.net core webapi 的 JWT 授权认证
传统的授权认证:
传统验证基于Cookies和Session;但是由于http是无状态的(第一次和第二次发送的http请求没有任何区别),所以这种模式有很多局限,比如在多服务器集群时就无法使用;
哪些方法可以实现有状态连接
cookie,session,application
有人将web应用中有无状态的情况,比着顾客逛商店的情景。
顾客:浏览器访问方;
商店:web服务器;
一次购买:一次http访问
我们知道,上一次顾客购买,并不代表顾客下一个小时一定会买(当然也不能代表不会)。也就是说同一个顾客的不同购买之间的关系是不定的。所以说实在的,这种情况下,让商店保存所有的顾客购买的信息,等到下一次购买可以知道这个顾客以前购买的内容代价非常大的。所以商店为了避免这个代价,索性就认为每次的购买都是一次独立的新的购买。浅台词:商店不区分对待老顾客和新过客。这就是无状态的。
但是,商店为了提高收益。她是想鼓励顾客购买的。所以告诉你,只要你在一个月内购买了5瓶以上的啤酒,就送你一个酒杯。
我们看看这种情况我们怎么去实现呢?
A,给顾客发放一个磁卡,里面放有顾客过去的购买信息。
这样商店就可以知道了。这就是cookie.
B,给顾客发放一个唯一号码,号码制定的顾客的消费信息,存储在商店的服务器中。这就是session。
最后,商店可以全局的决定,是5瓶为送酒杯还是6瓶。这就是application。
其实,这些机制都是在无状态的传统购买过程中加入了一点东西,使整个过程变得有状态。Web应用就是这样的。
和asp.net相比,core的不同,是在实例化类之前,就先进行身份认证,身份认证完之后,再进入类的实例里面;
基于Token的授权认证
什么是JWT web Token
是为了在网络应用环境传递声明而执行的一种基于JSON的开放标准,该token被设计为紧凑而安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其他业务逻辑所必须的声明信息,该token也可直接被用于认证,也可以被加密。
基于token的鉴权机制的流程
基于token的鉴权机制类似于http协议也是无状态的,他不需要在服务端保留用户的认证信息或者会话信息。这就意味着基于token的认证机制的应用不需要考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
- 用户使用用户名和密码来请求服务器
- 服务器来进行验证用户的信息
- 服务器通过验证了,发送给用户一个token
- 客户端存储token,并在每次请求时附送这个token值
- 服务端验证token值,并返回数据
下边是基于token验证的流程图:
- 授权服务器发送的token由私钥加密;
- API得到的token,用公钥解密;
- secret是保存在服务端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去,一旦客户端得知这个secret,那就意味着客户端可以自我签发jwt了
Token令牌的组成:
最终API如何能确定这个token就是授权服务器发来的呢?这个就是看签名。对得到的token,可以通过jwt.io网站来进行解读。
标准中注册的声明:
- iss:jwt签发者
- sub:jwt所面向的用户
- aud:接收jwt的一方
- exp:jwt的过期时间,这个过期时间必须要大于签发时间
- nbf:定义在什么时间之前,该jwt都是不可用的
- iat:jwt的签发时间
- jti:jwt的唯一身份标识,主要用来作为一次性的token,从而回避重放攻击。
具体完成JWT授权
1.新建一个授权API项目,然后新建一个授权控制器;
2.为了实现解耦,新建一个服务类,完成token的颁发:
3.控制器的代码如下
[Route("api")]
[ApiController]
public class AuthenticationController : ControllerBase
{
#region MyRegion //region是为了方便折叠
private ILogger<AuthenticationController> _logger = null;
private IJWTService _iJWTService = null;
private readonly IConfiguration _iConfiguration;
public AuthenticationController(ILoggerFactory factory,//此处做了三个依赖注入;
ILogger<AuthenticationController> logger,
IConfiguration configuration
, IJWTService service)
{
this._logger = logger;
this._iConfiguration = configuration;
this._iJWTService = service;
}
#endregion
[Route("Get")]
[HttpGet]
public IEnumerable<int> Get()
{
return new List<int>() { 1, 2, 3, 4, 6, 7 };
}
[Route("Login")] //log是日志,login是注册;
[HttpGet]
public string Login(string name, string password)
{
///这里肯定是需要去连接数据库做数据校验
if ("Richard".Equals(name) && "123456".Equals(password))//应该数据库
{
string token = this._iJWTService.GetToken(name);//调用后台的个体Token方法
//,这样就可以获取token了。
return JsonConvert.SerializeObject(new //把结果转化为Json格式返回。
{
result = true,
token
});
}
else
{
return JsonConvert.SerializeObject(new
{
result = false,
token = ""
});
}}}
4.获取token的类的代码如下:首先声明一个接口,然后再实现这个接口
声明接口
public interface IJWTService
{
string GetToken(string UserName);
}
实现这个接口
public class JWTService : IJWTService
{
private readonly IConfiguration _configuration;
public JWTService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GetToken(string UserName)
{
Claim[] claims = new[] //claim这里面包含的就是Token中的载荷的信息;
{
new Claim(ClaimTypes.Name, UserName),
new Claim("NickName","Richard"),
new Claim("Role","Administrator"),//传递其他信息
new Claim("abc","abccc")
//new Claim
};
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SecurityKey"]));
//从配置文件中读去key(养成良好的编码习惯,把配置文件,尽量写在配置文件里)
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//对key进行加密;
/**
* Claims (Payload)
Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,
下面节选一些字段:
iss: The issuer of the token,token 是给谁的
sub: The subject of the token,token 主题
exp: Expiration Time。 token 过期时间,Unix 时间戳格式
iat: Issued At。 token 创建时间, Unix 时间戳格式
jti: JWT ID。针对当前 token 的唯一标识
除了规定的字段外,可以包含其他任何 JSON 兼容的字段。
* */
var token = new JwtSecurityToken( //创建新的token
issuer: _configuration["issuer"],
audience: _configuration["audience"],
claims: claims,
expires: DateTime.Now.AddMinutes(5),//5分钟有效期
signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
//生成token需要引用Tokens.jwt的安装包
return returnToken;
}
}
配置文件的声明:
5.新建受保护的API
在startup里面配置授权服务:
#region JWT鉴权授权
//1.Nuget引入程序包:Microsoft.AspNetCore.Authentication.JwtBearer
//services.AddAuthentication();//禁用
var ValidAudience = this.Configuration["audience"];
var ValidIssuer = this.Configuration["issuer"];
var SecurityKey = this.Configuration["SecurityKey"];
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //默认授权机制名称;
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,//是否验证Issuer
ValidateAudience = true,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
ValidAudience = ValidAudience,//Audience
ValidIssuer = ValidIssuer,//Issuer,这两项和前面签发jwt的设置一致 表示谁签发的Token
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey))//拿到SecurityKey
//AudienceValidator = (m, n, z) =>
//{
// return m != null && m.FirstOrDefault().Equals(this.Configuration["audience"]);
//},//自定义校验规则,可以新登录后将之前的无效
};});
#endregion
6.在中间件管道里面添加身份认证和授权的中间件:
app.UseAuthentication();//身份认证
app.UseAuthorization();//授权
7.在需要保护的API上添加 [Authorize] 属性
[Authorize] //Microsoft.AspNetCore.Authorization
public IActionResult GetAuthorizeData()
{
var Name = base.HttpContext.AuthenticateAsync().Result.Principal.Claims.FirstOrDefault(a=>a.Type.Equals("Name"))?.Value;
Console.WriteLine($"this is Name {Name}");
return new JsonResult(new
{
Data = "已授权",
Type = "GetAuthorizeData"
//Claims=Newtonsoft.Json.JsonConvert.SerializeObject(Claims)
});
}
8.也可以直接添加到类上,对命名空间里,所有的方法都授权认证,多例外不需要的,添加 [AllowAnonymous] 允许匿名属性;
题外话:
#region这个语法,是用来方便折叠的。
参考资料联系侵删;