简单学习JWT2

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

  • 体积小,因而传输速度快
  • 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
  • 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访 问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload支持为你的应用而定制化。
  • 支持跨域验证,可以应用于单点登录。
1.3 JWT是什么样子的? JWT是由三个部分组成的,分别是:
1.头部信息(header)
作用:指定该JWT使用的签名
// 包括类别(typ)、加密算法(alg); { "alg": "HS256", "typ": "JWT" }

jwt的头部包含两部分信息:
声明类型,这里是jwt
声明加密的算法 通常直接使用 HMAC SHA256
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

2.消息体(载荷)(playload)
作用:JWT的请求数据,就是存放有效信息的地方。
这些有效信息包含三个部分:
  • 标准中注册声明
  • 公共的声名
  • 私有的声明
公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
下面是一个例子
// 包括需要传递的用户信息; { "iss": "Online JWT Builder", "iat": 1416797419, "exp": 1448333419, "aud": "www.gusibi.com", "sub": "uid", "nickname": "goodspeed", "username": "goodspeed", "scopes": [ "admin", "user" ] }

  • iss: 该JWT的签发者,是否使用是可选的;
  • sub: 该JWT所面向的用户,是否使用是可选的;
  • aud: 接收该JWT的一方,是否使用是可选的;
  • exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
  • iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
其他还有:
  • nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的
  • jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 将上面的JSON对象进行base64编码可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0

信息会暴露:由于这里用的是可逆的base64 编码,所以第二部分的数据实际上是明文的。我们应该避免在这里存放不能公开的隐私信息。

3. 签名( signatrue)
签名是三个部分组合成的
  • header (base64后的)
  • payload (base64后的)
  • secret
// 根据alg算法与私有秘钥进行加密得到的签名字串; // 这一段是最重要的敏感信息,只能在服务端解密; HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), SECREATE_KEY )

将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用 secret 作为密钥的话,那么就可以得到我们加密后的内容:
pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0MTY3OTc0MTksImV4cCI6MTQ0ODMzMzQxOSwiYXVkIjoid3d3Lmd1c2liaS5jb20iLCJzdWIiOiIwMTIzNDU2Nzg5Iiwibmlja25hbWUiOiJnb29kc3BlZWQiLCJ1c2VybmFtZSI6Imdvb2RzcGVlZCIsInNjb3BlcyI6WyJhZG1pbiIsInVzZXIiXX0.pq5IDv-yaktw6XEa5GEv07SzS9ehe6AcVSdTj0Ini4o

签名的目的:签名实际上是对头部以及载荷内容进行签名。所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。 这样就能保证token不会被篡改。
1.4. JWT该怎么用? JWT经常被用来保护服务器的资源,客户端一般通过HTTP/header的Authorzation把JWT发送给服务端
,服务端使用自己保存的Key进行计算,验证签名JWT是否合法,客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的Header Authorization字段中。
当跨域时,也可以将JWT被放置于POST请求的数据主体中。
JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,编码之后的JWT看起来是这样的一串字符:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

2.Auth HTTP的权限
分为两种
1.Authorzation
针对权限认证,如果我们登录成功了,但是我们在执行一些接口,但是我们没有借口权限,就会报错,因为我们权限不足,权限不足就是Authorzation的一种错误,错误码为403
2.Authentication
针对用户名密码方式的登录的身份认证,比如我们登录一个网站,用户名密码错误即Authentication错误,对应的错误码为401
3 JWT 使用场景
JWT的主要优势在于使用无状态、可扩展的方式处理应用中的用户会话。服务端可以通过内嵌的声明信息,很容易地获取用户的会话信息,而不需要去访问用户或会话的数据库。在一个分布式的面向服务的框架中,这一点非常有用。
但是,如果系统中需要使用黑名单实现长期有效的token刷新机制,这种无状态的优势就不明显了。
优点 快速开发 不需要cookie JSON在移动端的广泛应用 不依赖于社交登录 相对简单的概念理解缺点 Token有长度限制 Token不能撤销 需要token有失效时间限制(exp)

4.python 使用JWT实践
这里,我们可以使用 jwt 直接生成 token,不用手动base64加密和拼接。
import jwt import time# 使用 sanic 作为restful api 框架 def create_token(request): grant_type = request.json.get('grant_type') username = request.json['username'] password = request.json['password'] if grant_type == 'password': account = verify_password(username, password) elif grant_type == 'wxapp': account = verify_wxapp(username, password) if not account: return {} payload = { "iss": "gusibi.com", "iat": int(time.time()), "exp": int(time.time()) + 86400 * 7, "aud": "www.gusibi.com", "sub": account['_id'], "username": account['username'], "scopes": ['open'] } token = jwt.encode(payload, 'secret', algorithm='HS256') return True, {'access_token': token, 'account_id': account['_id']}def verify_bearer_token(token): #如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数 payload = jwt.decode(token, 'secret', audience='www.gusibi.com', algorithms=['HS256']) if payload: return True, token return False, token

5.生产项目中JWT的使用
public interface JwtConstants {String AUTH_HEADER = "Authorization"; String SECRET = "defaultSecret"; String AUTH_PATH = "/attackApi/auth"; Long EXPIRATION = 604800L; }//AUTH_HEADER是HTTPHeader请求的参数 //SECRET是具体的加密算法 //AUTH_PATH是提供给客户端获取JWT参数的接口/需要提供正确的用户名以及密码 //EXPIRATION是计算JWT过期时间需要用到的

private boolean check(HttpServletRequest request, HttpServletResponse response) { //此处是指获取Token的接口 if (request.getServletPath().equals(JwtConstants.AUTH_PATH)) { return true; } final String requestHeader = request.getHeader(JwtConstants.AUTH_HEADER); String authToken = null; if (requestHeader != null && requestHeader.startsWith("attackFind ")) { authToken = requestHeader.substring(11); try { //包含了验证jwt是否正确 boolean isExpired = AuthUtil.isTokenExpired(authToken); if (isExpired) { RenderUtil.renderJson(response, new ErrorResponseData(BizExceptionEnum.TOKEN_EXPIRED.getCode(), BizExceptionEnum.TOKEN_EXPIRED.getMessage())); return false; } } catch (JwtException e) { //有异常就是token解析失败 RenderUtil.renderJson(response, new ErrorResponseData(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage())); return false; } } else { RenderUtil.renderJson(response, new ErrorResponseData(BizExceptionEnum.TOKEN_ERROR.getCode(), BizExceptionEnum.TOKEN_ERROR.getMessage())); return false; } return true; } }//preHandle: //首先需要判断请求是否是静态资源 //如果是则不验证该请求,不是则执行check方法 //check中先验证该请求是否为获取JWT参数 //获取请求头中的Authorization参数信息 //调用我们接口的第三方约定好JWT之前追加attackFind ,如果没有这个信息那么JWT就验证失败 //isTokenExpired是检测JWT是否过期 true过期

【简单学习JWT2】原博客
https://blog.csdn.net/haibo0668/article/details/83859138
https://blog.csdn.net/youbitch1/article/details/86248270

    推荐阅读