本篇和大家分享 jwt(JSON web token) 的使用, 她主要用来生成接口访问的 token 和验证, 其单独结合 springboot 来开发 API 接口 token 验证很是方便, 由于 jwt 的 token 中存储有用户的信息并且有加密, 所以适用于分布式, 这样直接吧信息存储在用户本地减速了服务端存储 sessiion 或 token 的压力; 如下快速使用:
- <!--jwt-->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>0.9.0</version>
- </dependency>
- <!-- 阿里 FastJson 依赖 -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.44</version>
- </dependency>
一般使用 jwt 来达到 3 种结果:
生成 token
验证 token 是否有效
获取 token 中 jwt 信息 (主要用户信息)
生成 token
引入了 jjwt 依赖后, 要生成 token 很方便; 对于一个 token 来说, 代表的是唯一并且不可逆的, 因此我们在生成时需要增加一些唯一数据进去, 比如下面的 id:
- long currentTime = System.currentTimeMillis();
- return Jwts.builder()
- .setId(UUID.randomUUID().toString())
- .setIssuedAt(new Date(currentTime)) // 签发时间
- .setSubject("system") // 说明
- .setIssuer("shenniu003") // 签发者信息
- .setAudience("custom") // 接收用户
- .compressWith(CompressionCodecs.GZIP) // 数据压缩方式
- .signWith(SignatureAlgorithm.HS256, encryKey) // 加密方式
- .setExpiration(new Date(currentTime + secondTimeOut * 1000)) // 过期时间戳
- .addClaims(claimMaps) //cla 信息
- .compact();
通过 uuid 来标记唯一 id 信息; 当然在对 token 加密时需要用到秘钥, jwt 很是方便她支持了很多中加密方式如: HS256,HS265,Md5 等复杂及常用的加密方式;
jwt 生成的 token 中内容分为 3 个部分: head 信息, payload 信息, sign 信息, 通常我们要做的是往 payload 增加一些用户信息 (比如: 账号, 昵称, 权限等, 但不包含密码); 在对 jwt 的 token 有一定了解后, 我们来看下真实生成的 token 值:
1 eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAFWMTQ7CIBSE7_LWkPDzaEsP4QnYINCIptX4INE0vbtg4sLlfPPN7HAtGWbwg1BKL4GrcbEcIwpujZF8iiEpjXFapAAG2ReYpUEcR2VxYED13Nb0ppLW3hP1eEnblqsQuiFfY0OhUrl3I70evweU_aFSejZhd7DlcDv5NTmYHUilHTD3rf_hAccHRTv--7YAAAA.i4xwoQtaWI0-dwHWN8uZ4DBm-vfli5bavYU9lRYxU5E
验证 token 是否有效
token 生成的时都会伴随者有一个失效的时间, 在这我们可以通过 setExpiration 函数设置过期时间, 记住 jwt 的有效时间不是滑动的, 也就是说不做任何处理时, 当到达第一次设置的失效时间时, 就基本没用了, 要获取 token 是否过期可以使用如下方式:
- public static boolean isExpiration(String token, String encryKey) {
- try {
- return getClaimsBody(token, encryKey)
- .getExpiration()
- .before(new Date());
- } catch (ExpiredJwtException ex) {
- return true;
- }
- }
这里使用了 date 的 before 来用获取的过期时间和当前时间对比, 判断是否继续有效, 需要注意的是如果在 token 失效后再通过 getClaimsBody(token, encryKey) 获取信息, 此时会报 ExpiredJwtException 错误, 我们即可认为过期.
获取 token 中 jwt 信息 (主要用户信息)
通常我们要把登录用户信息存储在 jwt 生成的 token 中, 这里可以通过 addClaims(claimMaps) 传递 map 来设置信息, 反过来要获取 token 中的用户信息, 我们需要这样做:
- return Jwts.parser()
- .setSigningKey(encryKey)
- .parseClaimsJws(token)
- .getBody();
此时 body 获取出来是 Claims 类型, 我们需要从中获取到用户信息, 需要注意的是在 addClaims 存储信息的时候如果存储的 map 值没做过出来, 那完整的实体对象存储进去后会映射成一个 LinkHasMap 类型, 如下:
因此通常会在存储的时候 JSON 化, 如下代码:
- claimMaps.forEach((key, val) -> {
- claimMaps.put(key, JSON.toJSONString(val));
- });
再来就是通过 get 方法获取我们存储进去的信息, 并 JSON 反序列化:
- /**
- * 获取 body 某个值
- *
- * @param token
- * @param encryKey
- * @param key
- * @return
- */
- public static Object getVal(String token, String encryKey, String key) {
- return getJws(token, encryKey).getBody().get(key);
- }
- /**
- * 获取 body 某个值, JSON 字符转实体
- *
- * @param token
- * @param encryKey
- * @param key
- * @param tClass
- * @param <T>
- * @return
- */
- public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {
- try {
- String strJson = getVal(token, encryKey, key).toString();
- return JSON.parseObject(strJson, tClass);
- } catch (Exception ex) {
- return null;
- }
- }
来到这里一个 Jwt 的 Util 代码基本就完成了, 下面给出完整的代码例子, 仅供参考:
- public class JwtUtil {
- /**
- * 获取 token - JSON 化 map 信息
- *
- * @param claimMaps
- * @param encryKey
- * @param secondTimeOut
- * @return
- */
- public static String getTokenByJson(Map<String, Object> claimMaps, String encryKey, int secondTimeOut) {
- return getToken(claimMaps, true, encryKey, secondTimeOut);
- }
- /**
- * 获取 token
- *
- * @param claimMaps
- * @param isJsonMpas
- * @param encryKey
- * @param secondTimeOut
- * @return
- */
- public static String getToken(Map<String, Object> claimMaps, boolean isJsonMpas, String encryKey, int secondTimeOut) {
- if (isJsonMpas) {
- claimMaps.forEach((key, val) -> {
- claimMaps.put(key, JSON.toJSONString(val));
- });
- }
- long currentTime = System.currentTimeMillis();
- return Jwts.builder()
- .setId(UUID.randomUUID().toString())
- .setIssuedAt(new Date(currentTime)) // 签发时间
- .setSubject("system") // 说明
- .setIssuer("shenniu003") // 签发者信息
- .setAudience("custom") // 接收用户
- .compressWith(CompressionCodecs.GZIP) // 数据压缩方式
- .signWith(SignatureAlgorithm.HS256, encryKey) // 加密方式
- .setExpiration(new Date(currentTime + secondTimeOut * 1000)) // 过期时间戳
- .addClaims(claimMaps) //cla 信息
- .compact();
- }
- /**
- * 获取 token 中的 claims 信息
- *
- * @param token
- * @param encryKey
- * @return
- */
- private static Jws<Claims> getJws(String token, String encryKey) {
- return Jwts.parser()
- .setSigningKey(encryKey)
- .parseClaimsJws(token);
- }
- public static String getSignature(String token, String encryKey) {
- try {
- return getJws(token, encryKey).getSignature();
- } catch (Exception ex) {
- return "";
- }
- }
- /**
- * 获取 token 中 head 信息
- *
- * @param token
- * @param encryKey
- * @return
- */
- public static JwsHeader getHeader(String token, String encryKey) {
- try {
- return getJws(token, encryKey).getHeader();
- } catch (Exception ex) {
- return null;
- }
- }
- /**
- * 获取 payload body 信息
- *
- * @param token
- * @param encryKey
- * @return
- */
- public static Claims getClaimsBody(String token, String encryKey) {
- return getJws(token, encryKey).getBody();
- }
- /**
- * 获取 body 某个值
- *
- * @param token
- * @param encryKey
- * @param key
- * @return
- */
- public static Object getVal(String token, String encryKey, String key) {
- return getJws(token, encryKey).getBody().get(key);
- }
- /**
- * 获取 body 某个值, JSON 字符转实体
- *
- * @param token
- * @param encryKey
- * @param key
- * @param tClass
- * @param <T>
- * @return
- */
- public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {
- try {
- String strJson = getVal(token, encryKey, key).toString();
- return JSON.parseObject(strJson, tClass);
- } catch (Exception ex) {
- return null;
- }
- }
- /**
- * 是否过期
- *
- * @param token
- * @param encryKey
- * @return
- */
- public static boolean isExpiration(String token, String encryKey) {
- try {
- return getClaimsBody(token, encryKey)
- .getExpiration()
- .before(new Date());
- } catch (ExpiredJwtException ex) {
- return true;
- }
- }
- public static String getSubject(String token, String encryKey) {
- try {
- return getClaimsBody(token, encryKey).getSubject();
- } catch (Exception ex) {
- return "";
- }
- }
- }
- View Code
过滤器验证 token
有了基本的 JwtUtil 工具, 我们需要用到 springboot 项目中, 一般来说对于登录授权 token 验证可以通过过滤器来操作, 这里创建一个 AuthenFilter, 用于对 post 请求过来的 token 做验证:
- public class AuthenFilter implements Filter {
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest rq = (HttpServletRequest) servletRequest;
- HttpServletResponse rp = (HttpServletResponse) servletResponse;
- RpBase rpBase = new RpBase();
- try {
- // 只接受 post
- if (!rq.getMethod().equalsIgnoreCase("post")) {
- filterChain.doFilter(servletRequest, servletResponse);
- return;
- }
- String token = rq.getHeader("token");
- if (StringUtils.isEmpty(token)) {
- rpBase.setMsg("无 token");
- return;
- }
- //jwt 验证
- MoUser moUser = JwtUtil.getValByT(token, WebConfig.Token_EncryKey, WebConfig.Login_User, MoUser.class);
- if (moUser == null) {
- rpBase.setMsg("token 已失效");
- return;
- }
- System.out.println("token 用户:" + moUser.getNickName());
- filterChain.doFilter(servletRequest, servletResponse);
- } catch (Exception ex) {
- } finally {
- if (!StringUtils.isEmpty(rpBase.getMsg())) {
- rp.setCharacterEncoding("utf-8");
- rpBase.setCode(HttpStatus.BAD_REQUEST.value());
- rp.getWriter().write(JSON.toJSONString(rpBase));
- }
- }
- }
- }
要是自定义过滤器 AuthenFilter 生效, 还需要把她注册到容器中, 这里通过编码方式, 当然还可以通过 @WebFilter 注解来加入到容器中:
@Configuration public class WebFilterConfig { @Bean public FilterRegistrationBean setFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new AuthenFilter()); registrationBean.addUrlPatterns("/api/*"); registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); return registrationBean; } }
注意 addUrlPatterns 匹配的是过滤器作用的 url 连接, 根据需求而定; 为了验证效果, 这里我创建了两个接口 getToken 和 t0, 分别是获取 token 和 post 查询接口, 代码如是:
@RestController public class TestController { @PostMapping("/api/t0") public String t0() throws MyException { return UUID.randomUUID().toString(); } @GetMapping("/token/{userName}") public String getToken(@PathVariable String userName) { MoUser moUser = new MoUser(); moUser.setUserName(userName); moUser.setNickName(userName); Map<String, Object> map = new HashMap<>(); map.put(WebConfig.Login_User, moUser); return JwtUtil.getTokenByJson(map, WebConfig.Token_EncryKey, WebConfig.Token_SecondTimeOut); } }
最终要获通过 head 传递 token 值来访问 t01 接口, 得到如下结果:
token 在有效时间后访问直接失败, 从新获取 token 并访问 t01 接口, 得到成功的信息:
来源: https://www.cnblogs.com/wangrudong003/p/10122706.html