由于多终端的出现,很多的站点通过 web api restful
的形式对外提供服务,采用了前后端分离模式进行开发,因而在身份验证的方式上可能与传统的基于 cookie
的 Session Id
的做法有所不同,除了面临跨域提交 cookie
的问题外,更重要的是,有些终端可能根本不支持 cookie
。
JWT(JSON Web Token)
是一种身份验证及授权方案,简单的说就是调用端调用 api
时,附带上一个由 api
端颁发的 token
,以此来验证调用者的授权信息。
这种模式的问题在于扩展性不好。单机没有问题,如果是服务器集群、跨域的服务导向架构或者用户禁用了 cookie
,就不行了。
单机和多节点 tomcat
应用登录检验
tomcat
应用登录,
sesssion
保存在浏览器和应用服务器会话之间,用户登录成功后,服务端会保证一个
session
,也会给客户端一个
sessionId
,客户端会把
sessionId
保存在
cookie
中,用户每次请求都会携带这个
sessionId
。
②、多节点 tomcat
应用登录,开启 session
数据共享后,每台服务器都能够读取 session
。缺点是每个 session
都是占用内存和资源的,每个服务器节点都需要同步用户的数据,即一个数据需要存储多份到每个服务器,当用户量到达百万、千万级别的时,占用资源就严重,用户体验特别不好!!
分布式应用中 session
共享
session
共享的问题需要解决。
tomcat
支持
session
共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐
②、 Reids
集群,存储登陆的 token
,向外提供服务接口, Redis
可设置过期时间(服务端使用 UUID
生成随机 64
位或者 128
位 token
,放入 Redis
中,然后返回给客户端并存储)。
③、用户第一次登录成功时,需要先自行生成 token
,然后将 token
返回到浏览器并存储在 cookie
中, 并在 Redis
服务器上以 token
为 key
,用户信息作为 value
保存。后续用户再操作,可以通过 HttpServletRequest
对象直接读取 cookie
中的 token
,并在 Redis
中取得相对应的用户数据进行比较(用户每次访问都携带此 token
,服务端去 Redis
中校验是否有此用户即可)。
④、 缺点:必须部署 Redis
,每次必须访问 Redis
, IO
开销特别大。
JWT 的原理
简单来说,就是通过一定规范来生成 token,然后可以通过解密算法逆向解密 token,这样就可以获取用户信息</pre>
优点和缺点
缺点:token 是经过 base64 编码,所以可以解码,因此 token 加密前的对象不应该包含敏感信息(如用户权限,密码等)
JWT 格式组成:头部+负载+签名 ( header + payload + signature )
头部:主要是描述签名算法。
负载:主要描述是加密对象的信息,如用户的 id 等,也可以加些规范里面的东西,如 iss 签发者,exp 过期时间,sub 面向的用户。
签名:主要是把前面两部分进行加密,防止别人拿到 token 进行base 解密后篡改 token。
<!-- 依赖可以减少实体类 getter/setter等方法书写 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- JWT相关 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> ========================================================= @Getter @Setter @ToString @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private Integer id; private String openid; private String name; private String headImg; private String phone; private String sign; private Integer sex; private String city; private Date createTime; }
public class JwtUtil { // 主题 public static final String SUBJECT = "RookieLi"; // 秘钥 public static final String SECRETKEY = "Rookie666"; // 过期时间 public static final long EXPIRE = 1000 * 60 * 60 * 24 * 7; //过期时间,毫秒,一周 // 生成 JWT public static String geneJsonWebToken(User user) { if (user == null || user.getId() == null || user.getName() == null || user.getHeadImg() == null) { return null; } String token = Jwts.builder() .setSubject(SUBJECT) .claim("id", user.getId()) .claim("name", user.getName()) .claim("img", user.getHeadImg()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRETKEY).compact(); return token; } // 校验 JWT public static Claims checkJWT(String token) { try { final Claims claims = Jwts.parser().setSigningKey(SECRETKEY). parseClaimsJws(token).getBody(); return claims; } catch (Exception e) { e.printStackTrace(); } return null; } }
public class JwtUtilTest { @Test public void testGeneJwt(){ User user = new User(); user.setId(999); user.setHeadImg("I'm busy"); user.setName("Rookie"); String token = JwtUtil.geneJsonWebToken(user); System.out.println(token); } @Test public void testCheck(){ // 下面此 token 字符串是上面的结果生成的,每次不一样,不是写死的 String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJSb29raWVMaSIsImlkIjo5OTksIm5hbWUiOiJSb29raWUiLCJpbWciOiJJJ20gYnVzeSIsImlhdCI6MTU2NzMxNjk4NywiZXhwIjoxNTY3OTIxNzg3fQ.FJh41VwVh2gh5-_cOG0SOgoO3dR_ZcK9VWNNskWqKl0"; Claims claims = JwtUtil.checkJWT(token); if(claims != null){ String name = (String)claims.get("name"); String img = (String)claims.get("img"); int id =(Integer) claims.get("id"); System.out.println(name); System.out.println(img); System.out.println(id); }else{ System.out.println("非法token"); } } }
参考博客:
http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
https://www.cnblogs.com/jpfss/p/10929458.html