.net core JwtBearer 认证

原文地址:https://www.cnblogs.com/RainingNight/p/jwtbearer-authentication-in-asp-net-core.html

在现代Web应用程序中,通常会使用Web, WebApp, NativeApp等多种呈现方式,而后端也由以前的Razor渲染HTML,转变为Stateless的RESTFulAPI,因此,我们需要一种标准的,通用的,无状态的,与语言无关的认证方式,也就是本文要介绍的JwtBearer认证。

JwtBearer是一种认证方式

Bearer认证

在这里插入图片描述

在HTTP标准验证方案中,我们比较熟悉的是"Basic"和"Digest",前者将用户名密码使用BASE64编码后作为验证凭证,后者是Basic的升级版,更加安全。

本文要介绍的Bearer验证也属于HTTP协议标准验证,它随着OAuth协议而开始流行,详细定义见: RFC 6570。
Bearer验证中的凭证称为BEARER_TOKEN,或者是access_token,Bearer验证的标准请求方式如下:
Authorization: Bearer [BEARER_TOKEN]

JWT(JSON WEB TOKEN)

上面介绍的Bearer认证,其核心便是BEARER_TOKEN,而最流行的Token编码方式便是:JSON WEB TOKEN(JWT)。
JWT是由.分割的如下三部分组成:

头部(Header)
Header 一般由两个部分组成:
alg是是所使用的hash算法,如:HMAC SHA256或RSA
typ是Token的类型,在这里就是:JWT。
{
“alg”: “HS256”,
“typ”: “JWT”
}
然后使用Base64Url编码成第一部分:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<second part>.<third part>

载荷(Payload)
这一部分是JWT主要的信息存储部分,其中包含了许多种的声明(claims)。
一个简单的Pyload可以是这样子的:
{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}
这部分同样使用Base64Url编码成第二部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.

签名(Signature)
Signature是用来验证发送者的JWT的同时也能确保在期间不被篡改。
在创建该部分时候你应该已经有了编码后的Header和Payload,然后使用保存在服务端的秘钥对其签名,一个完整的JWT如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

示例

模拟Token
ASP.NET Core 内置的JwtBearer验证,并不包含Token的发放,我们先模拟一个简单的实现:

[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]UserDto userDto)
{
    var user = _store.FindUser(userDto.UserName, userDto.Password);
	if (user == null) return Unauthorized();

    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(Consts.Secret);
    var authTime = DateTime.UtcNow;
    var expiresAt = authTime.AddDays(7);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new Claim[]
        {
            new Claim(JwtClaimTypes.Audience,"api"),
            new Claim(JwtClaimTypes.Issuer,"http://localhost:5200"),
            new Claim(JwtClaimTypes.Id, user.Id.ToString()),
            new Claim(JwtClaimTypes.Name, user.Name),
            new Claim(JwtClaimTypes.Email, user.Email),
            new Claim(JwtClaimTypes.PhoneNumber, user.PhoneNumber)
        }),
        Expires = expiresAt,
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
	};

    var token = tokenHandler.CreateToken(tokenDescriptor);
	var tokenString = tokenHandler.WriteToken(token);

    return Ok(new
    {
        access_token = tokenString,
        token_type = "Bearer",
        profile = new
        {
            sid = user.Id,
            name = user.Name,
            auth_time = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
            expires_at = new DateTimeOffset(expiresAt).ToUnixTimeSeconds()
        }
    });
}

如上,使用微软提供的Microsoft.IdentityModel.Tokens帮助类(源码地址:azure-activedirectory-identitymodel-extensions-for-dotnet),可以很容易的创建出JwtToen,就不再多说。

注册JwtBearer认证
首先添加JwtBearer包引用:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 2.0.0

然后在Startup类中添加如下配置:

public void ConfigureServices(IServiceCollection services){
    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = JwtClaimTypes.Name,
            RoleClaimType = JwtClaimTypes.Role, 

            ValidIssuer = "http://localhost:5200",
            ValidAudience = "api",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Consts.Secret))

            /***********************************TokenValidationParameters的参数默认值***********************************/
            // RequireSignedTokens = true,
            // SaveSigninToken = false,
            // ValidateActor = false,
            // 将下面两个参数设置为false,可以不验证Issuer和Audience,但是不建议这样做。
            // ValidateAudience = true,
            // ValidateIssuer = true, 
            // ValidateIssuerSigningKey = false,
            // 是否要求Token的Claims中必须包含Expires
            // RequireExpirationTime = true,
            // 允许的服务器时间偏移量
            // ClockSkew = TimeSpan.FromSeconds(300),
            // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
            // ValidateLifetime = true
        };
    });
}
public void Configure(IApplicationBuilder app){
    app.UseAuthentication();
}

在JwtBearerOptions的配置中,通常IssuerSigningKey(签名秘钥), ValidIssuer(Token颁发机构), ValidAudience(颁发给谁) 三个参数是必须的,后两者用于与TokenClaims中的Issuer和Audience进行对比,不一致则验证失败(与上面发放Token中的Claims对应)。

而NameClaimType和RoleClaimType需与Token中的ClaimType一致,在IdentityServer中也是使用的JwtClaimTypes,否则会造成User.Identity.Name为空等问题。

添加受保护资源
创建一个需要授权的控制器,直接使用Authorize即可:

[Authorize]
[Route("api/[controller]")]
public class SampleDataController : Controller
{
    [HttpGet("[action]")]
    public IEnumerable<WeatherForecast> WeatherForecasts()
    {
        return ...
    }
}

运行
最后运行,直接访问/api/SampleData/WeatherForecasts,将返回一个401:
HTTP/1.1 401 Unauthorized
Server: Kestrel
Content-Length: 0
WWW-Authenticate: Bearer

让我们调用api/oauth/authenticate,获取一个JWT:
请求:

POST http://localhost:5200/api/oauth/authenticate HTTP/1.1
content-type: application/json
{
  "username": "alice",
  "password": "alice"
}

响应:

HTTP/1.1 200 OK
{
"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.Y1TDz8KjLRh_vjQ_3iYP4oJw-fmhoboiAGPqIZ-ooNc",
"token_type":"Bearer",
"profile":{"sid":1,"name":"alice","auth_time":1509464340,"expires_at":1510069140}
}

最后使用该Token,再次调用受保护资源:
GET http://localhost:5200/api/SampleData/WeatherForecasts HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoiYWxpY2UiLCJlbWFpbCI6ImFsaWNlQGdtYWlsLmNvbSIsInBob25lX251bWJlciI6IjE4ODAwMDAwMDAxIiwibmJmIjoxNTA5NDY0MzQwLCJleHAiOjE1MTAwNjkxNDAsImlhdCI6MTUwOTQ2NDM0MH0.Y1TDz8KjLRh_vjQ_3iYP4oJw-fmhoboiAGPqIZ-ooNc

授权成功,返回了预期的数据:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

[{“dateFormatted”:“2017/11/3”,“temperatureC”:35,“summary”:“Chilly”,“temperatureF”:94}]

扩展

自定义Token获取方式
JwtBearer认证中,默认是通过Http的Authorization头来获取的,这也是最推荐的做法,但是在某些场景下,我们可能会使用Url或者是Cookie来传递Token,那要怎么来实现呢?

.AddJwtBearer(o =>
{
    o.Events = new JwtBearerEvents()
    {
        OnMessageReceived = context =>
        {
            context.Token = context.Request.Query["access_token"];
            return Task.CompletedTask;
        }
    };
    o.TokenValidationParameters = new TokenValidationParameters
    {
        ...
    };

然后在Url中添加access_token=[token],直接在浏览器中访问:
在这里插入图片描述

同样的,我们也可以很容易的在Cookie中读取Token,就不再演示。
除了OnMessageReceived外,还提供了如下几个事件:
TokenValidated:在Token验证通过后调用。
AuthenticationFailed: 认证失败时调用。
Challenge: 未授权时调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值