JwtBearer认证

1、JwtBearer认证

1)Bearer认证

HTTP提供了一套标准的身份验证框架:服务器可以用来针对客户端的请求发送质询(challenge),客户端根据质询提供身份验证凭证。质询与应答的工作流程如下:服务器端向客户端返回401(Unauthorized,未授权)状态码,并在WWW-Authenticate头中添加如何进行验证的信息,其中至少包含有一种质询方式。然后客户端可以在请求中添加Authorization头进行验证,其Value为身份验证的凭证信息。

在HTTP标准验证方案中,我们比较熟悉的是"Basic"和"Digest",前者将用户名密码使用BASE64编码后作为验证凭证,后者是Basic的升级版,更加安全,因为Basic是明文传输密码信息,而Digest是加密后传输。在前文介绍的Cookie认证属于Form认证,并不属于HTTP标准验证。

Bearer验证中的凭证称为BEARER_TOKEN,或者是access_token,它的颁发和验证完全由我们自己的应用程序来控制,而不依赖于系统和Web服务器,Bearer验证的标准请求方式如下:

Authorization: Bearer [BEARER_TOKEN] 

那么使用Bearer验证有什么好处呢?

  • CORS: cookies + CORS 并不能跨不同的域名。而Bearer验证在任何域名下都可以使用HTTP header头部来传输用户信息。
  • 对移动端友好: 当你在一个原生平台(iOS, Android, WindowsPhone等)时,使用Cookie验证并不是一个好主意,因为你得和Cookie容器打交道,而使用Bearer验证则简单的多。
  • CSRF: 因为Bearer验证不再依赖于cookies, 也就避免了跨站请求攻击。
  • 标准:在Cookie认证中,用户未登录时,返回一个302到登录页面,这在非浏览器情况下很难处理,而Bearer验证则返回的是标准的401 challenge
2)JWT(JSON WEB TOKEN)

上面介绍的Bearer认证,其核心便是BEARER_TOKEN,而最流行的Token编码方式便是:JSON WEB TOKEN。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

如何保证WebAPI的安全?

1、JWT加密解密。token
2、使用https传输协议。
3、把用户所有请求的参数信息加上一个只有服务器端知道的secret,做个散列运算,然后到了服务器端,服务器端也做一个散列运算。如果散列值是一样的,那就表示没被篡改。
4、在业务逻辑上进行保护。(检查访问者是否有权限来实现一些操作。这点是最主要的,前面3的未必能够100%保证安全)

JWT由三部分组成(Header,Payload,Signature),可以把用户名、角色等无关紧要的信息保存到Payload部分。

Header:base64enc({ "alg":"HS256","TYPE":"JWT"})  // eyAiYWxnIjoiSFMyNTYiLCJUWVBFIjoiSldUIn0=

Payload:base64enc({"user":"vichin","pwd":"weichen123"})  //用户的关键信息 eyJ1c2VyIjoidmljaGluIiwicHdkIjoid2VpY2hlbjEyMyJ9

Signature:HMACSHA256(base64enc(header)+","+base64enc(payload),secretKey)

Header和Payload部分使用的是Base64编码,几乎等于明文,Signature部分是根据header+payload+secretKey进行加密算出来的,如果Payload被篡改,就可以根据Signature解密时候校验。

其中:

头部(Header)

Header一般由两个部分组成:

  • alg
  • typ

alg是是所使用的hash算法,如:HMAC SHA256或RSA,typ是Token的类型,在这里就是:JWT

{
  "alg": "HS256",
  "typ": "JWT"
}

然后使用Base64Url编码成第一部分:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.<second part>.<third part>
载荷(Payload)

这一部分是JWT主要的信息存储部分,其中包含了许多种的声明(claims)。

Claims的实体一般包含用户和一些元数据,这些claims分成三种类型:

  • reserved claims:预定义的 一些声明,并不是强制的但是推荐,它们包括 iss (issuer), exp (expiration time), sub (subject),aud(audience) 等(这里都使用三个字母的原因是保证 JWT 的紧凑)。
  • public claims: 公有声明,这个部分可以随便定义,但是要注意和 IANA JSON Web Token 冲突。
  • private claims: 私有声明,这个部分是共享被认定信息中自定义部分。

一个简单的Pyload可以是这样子的:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

这部分同样使用Base64Url编码成第二部分:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.<third part>

签名(Signature)

Signature是用来验证发送者的JWT的同时也能确保在期间不被篡改。

在创建该部分时候你应该已经有了编码后的Header和Payload,然后使用保存在服务端的秘钥对其签名,一个完整的JWT如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

因此使用JWT具有如下好处:

  • 通用:因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • 紧凑:JWT的构成非常简单,字节占用很小,可以通过 GET、POST 等放在 HTTP 的 header 中,非常便于传输。
  • 扩展:JWT是自我包涵的,包含了必要的所有信息,不需要在服务端保存会话信息, 非常易于应用的扩展。

关于更多JWT的介绍,网上非常多,这里就不再多做介绍。下面,演示一下 ASP.NET Core 中 JwtBearer 认证的使用方式。

Token:

public string Token(string name, string role)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Convert.FromBase64String(this._configuration["JWT:SecurityKey"]);
            var expiresAt = DateTime.Now.AddDays(1);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(JwtClaimTypes.Audience,this._configuration["JWT:audience"]),
                    new Claim(JwtClaimTypes.Issuer,this._configuration["JWT:issuer"]),
                    new Claim(JwtClaimTypes.Name,name),
                    new Claim(JwtClaimTypes.Role,role),

                }),
                Expires = expiresAt,
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

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

            return tokenString;
        }

配置文件部分:

"JWT": {
    "audience": "http://localhost:7777",
    "issuer": "http://localhost:7777",
    "SecurityKey": "3dbe00a167653a1aaee01d93e77e730e"
  },

startup:

services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
                .AddJwtBearer(o =>
                {
                    o.TokenValidationParameters = new TokenValidationParameters
                    {   
                        NameClaimType = JwtClaimTypes.Name,//Role不要写,他没有转译,转译后是一串地址加role格式,直接是Role,加上就有问题导致你的验证不通过
                        
                        ValidAudience = this.Configuration["JWT:audience"],
                        ValidIssuer  =this.Configuration["Jwt:issuer"],
                        IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(this.Configuration["JWT:SecurityKey"]))
                    };
                });

在控制层中:

[Authorize(Roles = "Director")]//加上这个注解

策略授权:

Policy:

public string Token(string name, string role,int id)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Convert.FromBase64String(this._configuration["JWT:SecurityKey"]);
            var expiresAt = DateTime.Now.AddDays(1);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(JwtClaimTypes.Audience,this._configuration["JWT:audience"]),
                    new Claim(JwtClaimTypes.Issuer,this._configuration["JWT:issuer"]),
                    new Claim(JwtClaimTypes.Id,id.ToString()),
                    new Claim(JwtClaimTypes.Name,role),
                    new Claim(JwtClaimTypes.Role,role),

                }),
                Expires = expiresAt,
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

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

            return tokenString;
        }
services.AddAuthorization(options =>
            {
                //注意是后面拥有前面的权限,其中我以name来做识别,因为role他就只是一个role字段,
                options.AddPolicy("Director", policy => policy.RequireClaim(JwtClaimTypes.Name, "Director"));
                options.AddPolicy("Teacher", policy => policy.RequireClaim(JwtClaimTypes.Name, "Director", "Teacher"));
            });
app.UseAuthentication();//身份认证
app.UseAuthorization();

当然在管道里面也要use,还有他们的顺序。管道机制是从上往下的。

心得:

这个鉴权相当于一套服务,和策略授权不一样。策略是授权是把权限写死,而这个比较灵活,在卖业务上是卖分区,策略授权卖的是分等级的。

在前后端分离上,前端使用的是代理,通过用户请求客户端,客户端请求后台服务器,服务器再返回客户端,客户端再展现给用户。这就是反向代理。正向代理就是对用户进行服务,这个反向代理是对服务器进行服务。

在分布式中,鉴权不再是这样,是对节点进行授权的,所以以后要注意授权的方式。

其中还有授权,分为角色(Role)授权和策略授权(Policy)

posted @ 2020-12-28 14:23  Soliloquy1  阅读(481)  评论(0)    收藏  举报