关于cookies、session、token(JWT)的浅薄理解
无状态的HTTP
Web应用程序是使用HTTP协议传输数据的,而HTTP是无状态的,在一次数据通信完成后,客户端和服务器端的连接就会关闭,再一次需要通信交换数据就需要重新建立新的连接。也就是说,当前HTTP通信无从得知历史的HTTP通信交换过什么数据,这就是无状态。
Cookies
由于现在大部分的应用中需要前后的操作具有关联性,比如用户先登录了淘宝,这是一次HTTP交换,再点击购物车,这又是一次HTTP通信,但是在第二次通信的时候应用程序并不知道是谁登录了购物车,于是Cookies就出场了。当用户发起第一次HTTP请求的时候,服务器把用户的信息写进Cookie里面返回给客户端,只要这个Cookie没过期,之后每次访问这个网站都会在HTTP的头部携带这个Cookie,这样服务器接收到这个Cookie验证用户信息成功就可以获取用户登录信息而不用再次登录。Cookie是保存在客户端的,并且是不可跨域的,不安全的。
Session
Session则是另一种保存客户状态的机制,与Cookie不同的是,Session保存在服务器。客户端第一次发送HTTP请求到服务器,服务器就会创建一个Session对象,这个对象可以保存K-V组,然后服务器返回一个session_id(这个session_id是写进cookies的)给客户端。之后客户端每次请求这个网站都会通过Cookie携带这个session_id,服务器验证session_id通过就可以获取用户信息而不用再次登录。由于Session会占用服务器资源,因此设置Session过期时间很重要。
JWT(toekn)
Session-Cookies机制存在以下问题
- 使用服务器集群时,session要同步共享到每台服务器,占用大量服务器资源
- 前后端分离、跨域访问时,每次请求的session_id不一致
- 如果是多端共享后端API服务,由于移动端无Cookie,这是短板
token就没有这些问题,并且更轻量级。
JWT解析
在客户端第一次发送HTTP后,服务器把用户的信息和自定义的秘钥加密生成token,然后把token返回给客户端,客户端最好把这个JWT放在HTTP请求头的Authorization字段或者放在POST的请求体中,每次访问服务器都携带这个JWT,服务器接收到JWT后,通过使用秘钥解密JWT,就能得到用户信息。
-
JWT的组成
JWT由三部分组成:
- 头部(header,包含类型和算法等信息)
- 负载(payload,存放有效信息,比如用户信息,不要存放重要信息比如密码)
- 签证(signature,由BASE64加密后的header和payload连接起来,通过header中指定的加密方式,组合秘钥进行加密形成)
-
Spring Boot的实现
pom.xml
<!-- JWT --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <!-- JWT需要另外添加的 --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency>
JwiUtils.java
package com.example.test_spring_boot_1.utils; import com.example.test_spring_boot_1.domain.User; import io.jsonwebtoken.*; import org.bouncycastle.util.encoders.Base64; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; public class JwtUtils { /** * 主题 */ private static final String SUBJECT = "testJWT"; /** * 生成秘钥的字符串 */ private static final String KEYSTRING = "keyItself"; /** * 过期毫秒数 */ private static final long EXPIRE = 1000*60*60*24*7; /** * 生成秘钥的方法 * @return */ public static SecretKey generalKey(){ byte[] encodedKey = Base64.decode(KEYSTRING); //base64编码的字符数组 SecretKey key = new SecretKeySpec(encodedKey,0,encodedKey.length,"AES"); //加密 return key; } /** * 签发jwt * @return */ public static String createJWT(User user){ //加密算法 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //当前时间毫秒数 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); Date expDate = new Date(nowMillis+EXPIRE); SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder() .setSubject(SUBJECT) //主题 .claim("name",user.getName()) //用K-V方式保存负载的Json数据 .setIssuedAt(now) //签发时间 .setExpiration(expDate) //过期时间 .signWith(signatureAlgorithm,secretKey); //签名算法及秘钥 return builder.compact(); } /** * 验证jwt * @param jwtStr */ public static Claims checkJWT(String jwtStr) throws JwtException { SecretKey key = generalKey(); try{ final Claims claims= Jwts.parser() .setSigningKey(key) //设置秘钥 .parseClaimsJws(jwtStr) //指定要解密的字符串 .getBody(); //获取负载的内容 return claims; }catch (JwtException ex){ System.out.println("token验证失败"); } return null; } }
CommonText.java
package com.example.test_spring_boot_1; import com.example.test_spring_boot_1.domain.User; import com.example.test_spring_boot_1.utils.JwtUtils; import io.jsonwebtoken.Claims; import org.junit.Test; public class CommonText { @Test public void testGeneJwt(){ User user = new User(); user.setId(999); user.setHeadImg("www.xdclass.net"); user.setName("xd"); String token = JwtUtils.createJWT(user); System.out.println(token); } @Test public void testToken(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0SldUIiwibmFtZSI6InhkIiwiaWF0IjoxNTY1MDMwMDIzLCJleHAiOjE1NjU2MzQ4MjN9.y2SuulgO88tE-Ets7PkIyiIQF1WjAmai7sVDdkgaAf0"; Claims claims = JwtUtils.checkJWT(token); if(claims != null){ String name = (String)claims.get("name"); System.out.println(name); } } }