接口的安全控制 (JWT) JSON Web Tokens

79 篇文章 1 订阅
12 篇文章 1 订阅

前言

如果你的接口是开放的,谁都可以成功调用,那么会非常危险。因此除非你真的想做开放式服务,否则要对用户的请求做权限控制


举例:假如我想自己写一个“张三版新浪微博”的APP。


新浪微博开放了微博的接口,所有人可以调用这些接口“发微博”、“看微博”等。当然不是随便调用,而是要到新浪微博的开放平台后台申请一个AppKey(或者AppId),申请成功后,新浪微博就会分配一个AppKey和一个App、AppSecret给你。这个AppKey就是供“张三版新浪微博”这个App用的,新浪微博的用户也可以用同样的用户名密码登录“张山版新浪微博”这个App发微博、看微博,和官方版本的新浪微博是互通的。

无论是认证还是授权,都需要传递认证信息,对于认证来讲就是AppKey、AppSecret,对于授权来讲就是用户的账号、密码。

接口传输这些认证信息方法有很多,常用的有:

1> 每次请求,直接把“用户名/AppKey”、“密码/AppSecret”通过表单、QueryString或者报文头传递;这种安全性比较差,因为随时可以被截获。

2> 首次先使用“用户名/AppKey”、“密码/AppSecret”获取Access_Token(相当于SessionId,在服务器端生成guid,用guid做key,用用户名做value保存到redis、memcached等地方),以后的请求都带着Access_Token,Access_Token存在有效期,过时后要重新获取Access_Token。缺点是Access_Token有过期重新登录的问题,而移动端app经常需要一段时间不用打开还要能直接用。需要有一个类似于Session的中心回话服务器。WebAPI也可以使用asp.net 的Session,但是不建议使用。

3> 登录时,服务器端把“用户名/AppKey”、“密码/AppSecret”采用JWT等的加密后返回给客户端,客户端以后发送请求的时候把JWT加密的内容放到请求中,服务端再解密获取用户名。优点是不需要会话状态服务器,有利于分布式部署,还有可以避免Session的过期问题,可以一直能用;缺点是一旦加密解密算法泄露,会带来安全性问题。(推荐)

为什么使用JWT(JSON Web Tokens)

在传统的基于session的用户登录认证中,因为http是无状态的,所以都是采用session方式。用户登录成功,服务端会创建一个session,当然会给客户端一个sessionId,客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId。
cookie+session这种模式通常是保存在内存中,而且服务从单服务到多服务会面临的session共享问题,随着用户量的增多,开销就会越大。而JWT不是这样的,只需要服务端生成token,客户端保存这个token,每次请求携带这个token,服务端认证解析就可。
 

JWT 是一种无状态的分布式的身份验证方式,与 Session 相反,Jwt 将用户信息存放在 Token 的 payload 字段保存在客户端,通过 RSA 加密的方式,保证数据不会被篡改,验证数据有效性。

我们需要做的第一件事就是让客户端通过他们的账号密码交换token。这里有2种可能的方法在RESTful API里面。第一种是使用POST请求来通过验证,使服务端发送带有token的响应。除此之外,你可以使用GET请求,这需要他们使用参数提供凭证(指URL),或者更好的使用请求头。

JWT(Json Web Token)是现在流行的一种对Restful接口进行验证的机制的基础。JWT的特点:把用户信息放到一个JWT字符串中,用户信息部分是明文的,再加上一部分签名区域,签名部分是服务器对于“明文部分+秘钥”加密的,这个加密信息只有服务器端才能解析。用户端只是存储、转发这个JWT字符串。如果客户端篡改了明文部分,那么服务器端解密时候会报错。

JWT由三块组成,可以把用户名、用户Id等保存到Payload部分

 

头:Header

JWT第一部分是header,header主要包含两个部分,alg指加密类型,可选值为HS256RSA等等,typ=JWT为固定值,表示token的类型。

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

载体:Payload

JWT第二部分是Payload,Payload 部分也是一个 JSON 对象,它是token的详细内容,用来存放实际需要传递的数据,一般包括:
iss  (发行者      即:JWT签发者),
exp  (过期时间   即:JWT的过期时间,这个过期时间必须要大于签发时间), 
sub (用户信息)
aud  (接收者       即:接收JWT的一方),
iat (签发时间   即:JWT的签发时间)
nbf  (生效时间   即:定义在什么时间之前,该jwt都是不可用的)
 jti (编号    即:JWT的唯一身份标识【值一般是GUID】,主要用来作为一次性token,从而回避重放攻击:具体做法就是用户请求一次,然后我们获取这个jti的值,然后给这个值加入黑名单【在Redis中设定一个黑名单表】用户第二次用这个Token来请求,我们在得到这个jti的值,去黑名单中查询下是否有这个值,如果有值则表示是重放请求)
以及其他信息,详细介绍请参考官网,也可以包含自定义字段。JWT 规定了以上7个官方字段,供选用

例如:我们可以自定义一个Payload

{
 "sub": "招商银行",
 "name": "张三",
 "admin": true,
 "jti": "2ab2dc85-0589-4174-b86e-b23dc9ae82a0",
 "iat": 1542037098, //签发日期的时间戳
 "exp": 1542040698  //过期日期的时间戳
}

签名:Signature

JWT第三部分是Signature,Signature 部分是对前两部分的签名,防止数据篡改。

这部分的内容是这样计算得来的:

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

 

Token案例

using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Microsoft.AspNetCore.Mvc;

namespace JwtApp.Controllers
{
    [Route("api/[controller]")]
    public class HomeController : Controller
    {
        //创建Token
        [Route(nameof(CreateToken))]
        public IActionResult CreateToken()
        {
            var jti = Guid.NewGuid();
            var exp = (DateTime.UtcNow.AddSeconds(100) - new DateTime(1970, 1, 1)).TotalSeconds;
            var payload = new Dictionary<string, object>
            {
                { "sub", "招商银行" },
                { "userName", "张三" },
                { "admin",true},
                { "jti", jti},
                { "exp",exp}
            };
            var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";//秘钥:不要泄露
            IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
            IJsonSerializer serializer = new JsonNetSerializer();
            IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
            IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
            var token = encoder.Encode(payload, secret);

            return Content(token);
        }

        //解密Token
        [Route(nameof(DecryptToken))]
        public IActionResult DecryptToken(string token)
        {
            var secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";//秘钥:不要泄露
            try
            {
                IJsonSerializer serializer = new JsonNetSerializer();
                IDateTimeProvider provider = new UtcDateTimeProvider();
                IJwtValidator validator = new JwtValidator(serializer, provider);
                IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
                IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder);
                var json = decoder.Decode(token, secret, verify: true);
                return Content(json);
            }
            catch (FormatException)
            {
                return Content("Token格式无效");
            }
            catch (TokenExpiredException)
            {
                return Content("Token已经过期");
            }
            catch (SignatureVerificationException)
            {
                return Content("Token无效");
            }
        }
    }
}

安装JWT:如果你的环境是.NET FrameWork4.5.2  这建议使用

 Install-Package JWT -Version 3.0.1 这个版本

 

但是也有疑问,客户端在拿到服务器签发的 JWT 后,如果客户端被拦截,Head 里面的 JWT 不修改,篡改 Body 里面的数据,服务端也会通过。

如果保证这点呢?

WT 是方便授权的,只要保证不能被伪造就行了。防劫持那是另外的问题。防中间人劫持可以使用 https。防客户端劫持可以缩短有效时间,可以使用加密硬件,这个需要分情况。支付宝类就可以把时间限制的很短,比如一次交易时间内。v2ex 这种就随便了,设置个一年都没问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值