SpringBoot如何集成Token
作者:爱JAVA的少年闰土
SpringBoot集成Token
简介
在项目开发中,Token 是常见且重要的一个功能,目前对于 Token 设计有很多成熟的方案;
比如使用 Redis 存储管理 Token,不过这种方式需要而额外集成 Redis 服务,虽然 Redis 查询效率很高,但是对于普通项目来说,还是增加了开发难度;
本章将介绍一个简单易用的 Token 插件:jjwt,该插件直接与 SpringBoot 集成即可,其原理是:在服务端加密生成一个三段式加密字符串,前端每次请求都要将该 Token 传递给服务端(建议放在 Header 中),后台通过解析该 Token 字符串,判断其是否合法,超时等
基本原理
从这个架构图中可以看出 JWT 主体分为 3 个部分:user,application server,authentication server;
非常常见的一个架构,首先用户需要 通过登录等手段向 authentication server 发送一个认证请求,authentication会返回给用户一个 JWT (这个JWT 的具体内容格式是啥后面会说,先理解成一个简单的字符串好了)
此后用户向application server发送的所有请求都要捎带上这个 JWT,然后application server 会验证这个 JWT 的合法性,验证通过则说明用户请求时来自合法守信的客户端
JWT 结构
这个 JWT 的格式,就是一个由三部分组成的字符串:header.payload.signatue;
其中 header 主要包含了加密算法等信息;
payload 则主要包含后端服务器放入的自定义信息,如:登录用户的信息;signature 就是使用算法生成的能够实现身份认证的字符串
实现步骤
1. 在项目的 pom.xml 配置文件中添加如下依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
2. 添加一个公共类 Token,提供生成 Token,校验等基本方法
/** * @ClassName Token * @Author Andy * @Date 2020/7/14 14:32 * @Description 用于生成, 解析 token 的工具类 **/ public class Token { // 密钥 private static SecretKeySpec key = ""; // 对密钥加密 static { key = new SecretKeySpec(Constant.SECRET_KEY.getBytes(), SignatureAlgorithm.HS512.getJcaName()); } // 生成 token public static String createToken(String subject, Map < String, Object > claims, Date expireDate) { JwtBuilder builder = Jwts.builder() .setClaims(claims) // payload 私有申明,存放一些个人信息,必须放在第一个 .setIssuer(Constant.AUTHOR) // token 的签发人 .setIssuedAt(new Date()) // token 的签发时间 .setSubject(subject) // token 的所有人, 一般放用户的 id 之类的 .setExpiration(expireDate) // 过期时间 .signWith(SignatureAlgorithm.HS512, key); // token 的签名算法 return builder.compact(); } // 生成 token public static String createToken(String subject, Date expireDate) { return createToken(subject, new HashMap < > (), expireDate); } // 解析 token public static Claims parseToken(String token) { try { return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); } catch (Exception e) { throw new CommonException(Exceptions.TOKEN_PARSE_ERROR); } } // 将 token 标记为过期 public static void markTokenExpired(String token) { Date expireDate = CommonUtil.currentTimeAddSeconds(1); Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().setExpiration(expireDate); } // 判断 token 是否过期 public static boolean checkTokenExpired(String token) { Claims claims = parseToken(token); return !CommonUtil.checkExpired(claims.getExpiration()); } // 获取 token 的 subject 信息, 登录成功保存的时用户的主键 public static String getSubject(HttpServletRequest request) { return parseToken(getToken(request)).getSubject(); } // 根据 token 获取 subject 信息 public static String getSubject(String token) { return parseToken(token).getSubject(); } // 根据 HttpServletRequest 获取 token public static String getToken(HttpServletRequest request) { return request.getHeader(Constant.HEADER_TOKEN); } // 获取 token 的签发人 public static String getIssuer(String token) { return parseToken(token).getIssuer(); } }
这里使用到其它关联类的方法或属性如下
- Constant.class
// token 签发者 public static final String AUTHOR = "duzimei"; // 获取 header 中 token 标识 public static final String HEADER_TOKEN = "token"; // token 密钥加密字符串(取自 《肖申克的救赎》经典台词) public static final String SECRET_KEY = "Fear can hold you prisoner.Hope can set you free"; // 设置 token 的过期时间为 1 个小时(单位秒) public static final Integer EXPIRE_DATE = 3600;
- CommonUtil.class
// 在当前时间基础上加 seconds 秒 public static Date currentTimeAddSeconds(int seconds) { Calendar now = Calendar.getInstance(); now.add(Calendar.SECOND, seconds); return now.getTime(); } // 判断于当前日期是否过期 public static boolean checkExpired(Date expiration) { return expiration.after(new Date()); }
异常定义请忽略,根据自己项目而实现
3. 定义一个 UserController.class,添加登录方法,当用户登录成功后,返回一个 Token
@RestController @RequestMapping("/user") public class User { @Autowired private UserService userService; @PostMapping("/login") public Map<Integer, String> login(@RequestBody JSONObject params) { Map<Integer, String> result = new HashMap<>(); String userAccount = params.getString("account"); String password = params.getString("password"); User tempUser = userService.getUserByAccount(userAccount); if(null == tempUser) { result.put(-1, "用户不存在"); } else if(!password.equals(tempUser.getPassword())) { result.put("-2", "密码不正确"); } else { // 用户登录成功, 添加 token 返回给客户端 // 1. 设置 token 过期时间 Date expireDate = CommonUtil.currentTimeAddSeconds(Contant.EXPIRE_DATE); // 2. 这里我们把用户的 id 信息放到 subject 中 String token = Token.createToken(tempUser.getUserId(), expireDate); result.put(0, token); } return result; } }
4. 前端当用户登录成功后,向后台发送其它请求的时候,将 Token 放到 Header 中一起传送给后台
$.ajax({ headers: { "token": 从后台获取到的token }, url: "http://xxx:8080/user/info", method: "GET", data: null, dataType: "json", contentType: "application/json", success: function(data) { console.log(data); }, error: function(x, s, e) { console.log("异常信息: " + x.responseText); } })
5.解析Token
在后台对应方法中,就可以通过解析 Token 获取用户 id,判断请求是否合法;
像下面这种请求,前端根本不用传递用户的 id 到后台,后台通过解析 Token,获取 Token 的 subject 属性就能拿到用户 id,在一定程度上提高了安全性
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/info") public Map<Integer, Object> getUserInfo(HttpServletRequest request) { Map<Integer, Object> result = new HashMap<>(); String token = Token.getToken(request); // 对 token 进行校验 if(StringUtils.isEmpty(token)) { // 如果没有获取到 Token result.put(-1, "没有获取到 Token, 请求被阻止"); } else if(!Constant.AUTHOR.equals(Token.getIssuer(token))) { // 如果 Token 的签发人不正确 result.put(-2, "非法的 Token, 请求被阻止"); } else if(Token.checkTokenExpired(token)) { // 如果 Token 已过期 result.put(-3, "过期的 Token, 请求被阻止"); } else { String userId = Token.getSubject(token); User user = userService.getUserById(userId); result.put(0, user); } return result; } }
这里只是介绍的关于 JWT 实现 Token 的简单用法,在实际开发过程中,建议使用 Spring 的 AOP 技术实现对 Token 的校验;
我们这里只是实现了一个最简单的 Token,这 Token 中还可以放入其它信息,由于是单向加密的,所以数据传输非常安全;
更进一步的实现,应根据实际开发具体实现
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。