一、背景

传统的单体应用基于cookie-session的身份验证流程一般是这样的:

  1. 用户向服务器发送账户和密码。
  2. 服务器验证账号密码成功后,相关数据(用户角色、登录时间等)都保存到当前会话中。
  3. 服务器会生成一个sessionid返回浏览器,浏览器把这个sessionid存储到cookie当中。
  4. 以后每次发起请求都会在请求头cookie中带上这个sessionid信息,所以服务器就是根据这个sessionid作为索引获取到具体session。

但是这种模式存在如下几个问题:

  1. 没有分布式架构无法支持横向扩展,例如站点A和站点B提供同一公司的相关服务。现在要求用户只需要登录其中一个网站,然后它就会自动登录到另一个网站,满足不了这种需求。
  2. 如果用户很多,这些信息存储在服务器内存中会给服务器增加负担。
  3. 还有就是CSRF(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

针对问题1,有两种解决方案,第一种就是使用session共享,比如使用redis实现,第二种解决方案就是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

二、什么是JWT

JSON WEB TOKEN(以下称JWT),JWT也是一种token,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。

2.1JWT的特点

简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。

自包含(Self-contained): 负载中包含了所有用户所需要的信息,避免了多次查询数据库或缓存。

2.2JWT的消息结构

实际的JWT数据结构就像这样。

enter description here

它是一个很长的字符串,中间用点分隔成三个部分,红色是header,紫色部分是Payload,蓝色部分是Signature。即格式都是Header.Payload.Signature。

Header(头部)

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

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

alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

最后,将上面的JSON对象使用Base64URL算法转成字符串。

Payload(负载)

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

iss (issuer):签发人

exp (expiration time):过期时间

sub (subject):主题

aud (audience):受众

nbf (Not Before):生效时间

iat (Issued At):签发时间

jti (JWT ID):编号

除了官方字段我们还可以自定义字段,比如:

{
"userId": 1234567890,
"name": "John Doe"
}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。然后这个JSON对象也要使用Base64URL算法转成字符串。

Signature(签名)

Signature是对前两个部分的签名,作用是防止数据被篡改。

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

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

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

Base64URL算法

Base64URL算法与Base64算法有一些区别,作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_" 替换,这就是Base64URL算法。

三、JWT的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信都要带上JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

还可以将JWT放在POST请求的数据体中,或者跟在URL后面。

所以使用JWT实现单点登录,也可以放在Cookie中或者Header中,基于Cookie的实现可以参考这篇文章《八幅漫画理解使用 JWT设计的单点登录系统》,之前公司的项目是基于Header的Authorization字段字段实现的。

四、JWT的缺点

当然,JWT并不是完美的,它也有一些缺点。

1.JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

2.JWT本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。所以为了避免盗用,可以将有效期设置的短一些,使用HTTPS协议加密传输。

理解JWT的使用场景和优劣》这篇文章介绍的很详细,但是我不太认同他说的使用jwt做单点登录+会话管理没有传统的cookie-session 机制工作得更好。

五、代码示例

JWT一般是在网关服务配合Filter来实现认证的,考虑再弄个Zuul网关比较麻烦这里就简化了下。

JwtTestController

/**
* Created by 2YSP on 2019/8/26.
*/
@Slf4j
@RestController
@RequestMapping("/jwt")
public class JwtTestController { @GetMapping("/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password, HttpServletResponse response) throws Exception {
if (!checkUserNameAndPwd(username, password)) {
return "login error:invalid username or password";
}
// 过期时间
Long exp = System.currentTimeMillis() + 20 * 1000;
PayLoad payLoad = new PayLoad(1L, username, exp);
String token = JwtUtils.generateToken(payLoad);
Cookie cookie = new Cookie("token", token);
// HttpOnly属性来防止Cookie被JavaScript读取,从而避免跨站脚本攻击
cookie.setHttpOnly(true);
// 30秒
cookie.setMaxAge(30);
response.addCookie(cookie);
return token;
} @GetMapping("/verify")
public Boolean verifyToken(HttpServletRequest request) {
String token = getTokenFromCookie(request);
if (StringUtils.isBlank(token)){
return false;
}
// 验证签名
if (!JwtUtils.checkSignature(token)){
return false;
}
PayLoad payLoad = JwtUtils.getPayLoad(token);
if (payLoad.getExp() < System.currentTimeMillis()){
// 已过期
return false;
}
Gson gson = new Gson();
log.info("verify successfully ,payLoad:{} ", gson.toJson(payLoad));
return true;
} private String getTokenFromCookie(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies){
if (cookie.getName().equals("token")){
return cookie.getValue();
}
}
return null;
} private boolean checkUserNameAndPwd(String username, String pwd) {
if (StringUtils.isBlank(username)) {
return false;
}
if (StringUtils.isBlank(pwd)) {
return false;
}
if (username.equals("admin") && pwd.equals("1234")) {
return true;
}
return false;
}
}

这里提供了两个接口,一个是模拟登录(登录一般是POST的这里方便测试就改为GET方式了),登录成功后返回一个token同时将token保存在cookie中。另一个是校验token的接口,依次进行验签、过期时间等校验。

PayLoad

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayLoad { private Long userId; private String name; private Long exp; }

保存一些用户信息和过期时间的实体类。

JwtUtils

  1. public class JwtUtils { 


  2. /** 

  3. * 加密算法 

  4. */ 

  5. public static final String HEADER_ALG = "HS256"; 


  6. public static final String HEADER_TYP = "JWT"; 

  7. /** 

  8. * 加密串 

  9. */ 

  10. public static final String SECRET = "d5ec0a02"; 


  11. public static String generateToken(PayLoad payLoad) throws Exception { 

  12. Gson gson = new Gson(); 

  13. Header header = new Header(HEADER_ALG, HEADER_TYP); 

  14. String encodedHeader = Base64Utils.encodeToUrlSafeString(gson.toJson(header).getBytes( 

  15. Charsets.UTF_8)); 

  16. String encodePayLoad = Base64Utils.encodeToUrlSafeString(gson.toJson(payLoad).getBytes( 

  17. Charsets.UTF_8)); 

  18. String signature = HMACSHA256(encodedHeader + "." + encodePayLoad, SECRET); 

  19. return encodedHeader + "." + encodePayLoad + "." + signature; 




  20. public static boolean checkSignature(String token) { 

  21. String[] array = token.split("\\."); 

  22. if (array.length != 3) { 

  23. throw new IllegalArgumentException("token error"); 



  24. try { 

  25. String signature = HMACSHA256(array[0] + "." + array[1], SECRET); 

  26. return signature.equals(array[2]); 

  27. } catch (Exception e) { 



  28. return false; 




  29. public static PayLoad getPayLoad(String token) { 

  30. String[] array = token.split("\\."); 

  31. if (array.length != 3) { 

  32. throw new IllegalArgumentException("token error"); 



  33. String payLoad = new String(Base64Utils.decodeFromUrlSafeString(array[1]), Charsets.UTF_8); 

  34. Gson gson = new Gson(); 

  35. return gson.fromJson(payLoad, PayLoad.class); 





  36. public static String HMACSHA256(String data, String key) throws Exception { 


  37. Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); 


  38. SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); 


  39. sha256_HMAC.init(secret_key); 


  40. byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); 


  41. StringBuilder sb = new StringBuilder(); 


  42. for (byte item : array) { 


  43. sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); 





  44. return sb.toString().toUpperCase(); 





  45. @Data 

  46. @AllArgsConstructor 

  47. static class Header { 


  48. private String alg; 

  49. private String typ; 







JWT的工具类,提供了一些生成token,验证签名等方法。代码地址

漫谈JSON Web Token(JWT)的更多相关文章

  1. JSON WEB Token(JWT)

    最近面试被问及单点登陆怎么解决?自己的项目前后端分离,自己实现token认证,token有失效时间,token中包含用户基本的信息.且一个当用户重新登陆后,原来的token就会失效,这么安全的一个to ...

  2. JSON Web Token (JWT) 简介

    JSON Web Token (JWT) 是一种基于 token 的认证方案. JSON Web Token 的结构 一个 JWT token 看起来是这样的: eyJhbGciOiJIUzI1NiI ...

  3. JSON Web Token (JWT) 实现与使用方法

    1. JSON Web Token是什么 JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的.自包含的方式,用于作为JSON对象在各方之间安全地传输信息.该 ...

  4. Json Web Token(JWT)详解

    什么是Json Web Token Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的 ...

  5. JSON Web Token (JWT),服务端信息传输安全解决方案。

    JWT介绍 JSON Web Token(JWT)是一种开放标准(RFC 7519),它定义了一种紧凑独立的基于JSON对象在各方之间安全地传输信息的方式.这些信息可以被验证和信任,因为它是数字签名的 ...

  6. 基于 Token 的身份验证:JSON Web Token(JWT)

    1.传统身份验证和JWT的身份验证 传统身份验证:       HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用.这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过 ...

  7. JSON Web Token (JWT) - Introduction

    To validate the challenge, connect as admin.------------以admin登陆 https://jwt.io/introduction/        ...

  8. json web token JWT实现TP5创建和验证

    根据博客进行jwt初始化配置 https://blog.csdn.net/weixin_43389208/article/details/117442266?spm=1001.2014.3001.55 ...

  9. JSON Web Token (JWT) - Weak secret

    This API with its /hello endpoint (accessible with GET) seems rather welcoming at first glance but i ...

随机推荐

  1. 4、Spring Cloud Eureka

    1.Eureka简介 (1).CAP简介 CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性). Availability(可用性).Partition toleran ...

  2. Jenkins Job间传递参数的一种方法

    场景: Jenkins 中可以建多个Job,一般是主编译Job,多个子Job. 子Job要用主Job中的版本号,编译号. 1)  在主Job里面添加脚本命令: echo set MainVersion ...

  3. 题解-CF1307G Cow and Exercise

    CF1307G Cow and Exercise 给 \(n\) 点 \(m\) 边的带权有向图,边 \(i\) 为 \((u_i,v_i,w_i)\).\(q\) 次询问,每次给 \(x_i\),问 ...

  4. 【python接口自动化】- 使用json及jsonpath转换和提取数据

    前言 ​ JSON(JavaScript Object Notation)是一种轻量级的数据交换格式.它可以让人们很容易的进行阅读和编写,同时也方便了机器进行解析和生成,适用于进行数据交互的场景,比如 ...

  5. Pytorch系列之常用基础操作

    各种张量初始化 创建特殊类型的tensor a = torch.FloatTensor(2,3) a = torch.DoubleTensor(2,3) ... 设置pytorch中tensor的默认 ...

  6. Git:git常用命令

    1.版本控制工具     一个可以管理和追踪软件代码的工具.     分类:       集中式版本控制工具:SVN       分布式版本控制工具:Git 2.Git 的概念:     工作区:就是 ...

  7. 使用纯js 不导包实现 table 导出 Excel

    1.将js粘贴到项目 2.设置table标签 id3.定义按钮,调用方法即可 1 <!DOCTYPE html> 2 <html lang="zh_CN"> ...

  8. 【ubantu下安装python3.6】

    Ubuntu16.04默认安装了Python2.7和3.5 请注意,系统自带的python千万不能卸载! 输入命令python

  9. Day1 数据类型

    整数 十六进制和八进制使用0作为前缀,如 0x12f , 010浮点数 可以用科学计数法来表示很大或者很小的浮点数,如 1.23x10^9 可以写作 1.23e9 或者12.3e8 ,0.000012 ...

  10. Goldengate搭建

    OGG进程 捕获进程(源端):捕获online redo log或者archived log中增量事务日志 传输进程(源端):把目标端落地的trail文件通过配置的路由信息传输到目标端 网络传输:tc ...