Spring Boot REST 风格 API 接口 JWT Token 认证

Stella981
• 阅读 775

Spring Boot REST 风格 API 接口 JWT Token 认证

需求分析

接口认证需求:

1 能够有选择地过滤没有权限(Token)的请求 2 Token 具有时效性 3 如果用户连续操作,Token 能够自动刷新(自动延长有效期)

核心依赖

  1. <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->

  2. <dependency>

  3. <groupId>com.auth0</groupId>

  4. <artifactId>java-jwt</artifactId>

  5. <version>3.8.3</version>

  6. </dependency>

本文使用目前最新版本的依赖

核心代码

JwtUtil 生成和解密Token

  1. package com.example.util;

  2. import com.auth0.jwt.JWT;

  3. import com.auth0.jwt.JWTVerifier;

  4. import com.auth0.jwt.algorithms.Algorithm;

  5. import java.util.Date;

  6. /**

  7. * @Author: lty

  8. * @Date: 2019/12/17 09:33

  9. */

  10. public class JwtUtil {

  11. private final static String DEFAULT_SECRET = "TASDASDF9823K4JH29S8D2H349SDFH14";

  12. //region 加密区

  13. public static String encode(String k, String v) {

  14. return encode(k, v, 0);

  15. }

  16. private static String encode(String k, String v, long expireTime) {

  17. return encode(null, k, v, expireTime);

  18. }

  19. private static String encode(String secret, String k, String v, long expireTime) {

  20. if (secret == null || secret.length() < 1) {

  21. secret = DEFAULT_SECRET;

  22. }

  23. Date expDate = null;

  24. if (expireTime > 1) {

  25. expDate = new Date(System.currentTimeMillis() + expireTime);

  26. }

  27. //创建加密的token

  28. Algorithm algorithm = Algorithm.HMAC256(secret);

  29. String token =JWT.create().withIssuer("lty").withClaim(k,v)

  30. .withExpiresAt(expDate).sign(algorithm);

  31. return token;

  32. }

  33. //endregion

  34. //region 解密区

  35. public static String decode(String key, String encryptedToken) {

  36. return decode(null, key, encryptedToken);

  37. }

  38. public static String decode(String secret, String key, String encryptedToken) {

  39. if ("".equals(secret) || null == secret) {

  40. secret = DEFAULT_SECRET;

  41. }

  42. Algorithm algorithm = Algorithm.HMAC256(secret);

  43. JWTVerifier verifier = JWT.require(algorithm)

  44. .withIssuer("lty")

  45. .build();

  46. String s = verifier.verify(encryptedToken).getClaim(key).asString();

  47. return s;

  48. }

  49. //endregion

  50. public static void main(String[] args) {

  51. String encode = JwtUtil.encode("TokenKey", "843328437@1576551621592");

  52. System.out.println(encode);

  53. String lty = JwtUtil.decode("TokenKey", encode);

  54. System.out.println(lty);

  55. }

  56. }

Token 拦截器

拦截器配置

  1. package com.example.interceptor;

  2. import org.springframework.context.annotation.Configuration;

  3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

  4. import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;

  5. import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

  6. @Configuration

  7. public class InterceptorConfig extends WebMvcConfigurationSupport {

  8. /**

  9. * 多个拦截器组成一个拦截器链

  10. * addPathPatterns 用于添加拦截规则

  11. * excludePathPatterns 用户排除拦截

  12. * @param registry

  13. */

  14. @Override

  15. protected void addInterceptors(InterceptorRegistry registry) {

  16. //拦截

  17. registry.addInterceptor(new TokenInterceptor())

  18. .addPathPatterns("/**")

  19. .excludePathPatterns("/autologon/authorizationLogin");

  20. super.addInterceptors(registry);

  21. }

  22. /**

  23. * 配置静态资源映射

  24. * @param registry

  25. */

  26. @Override

  27. protected void addResourceHandlers(ResourceHandlerRegistry registry) {

  28. ///将所有/static/** 访问都映射到classpath:/static/ 目录下

  29. registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");

  30. super.addResourceHandlers(registry);

  31. }

  32. }

  33. package com.example.interceptor;

  34. import com.example.pojo.Result;

  35. import com.example.util.JwtUtil;

  36. import com.example.util.ResultUtil;

  37. import com.fasterxml.jackson.databind.ObjectMapper;

  38. import org.slf4j.Logger;

  39. import org.slf4j.LoggerFactory;

  40. import org.springframework.web.servlet.HandlerInterceptor;

  41. import org.springframework.web.servlet.ModelAndView;

  42. import javax.servlet.http.HttpServletRequest;

  43. import javax.servlet.http.HttpServletResponse;

  44. import java.io.IOException;

  45. import java.io.PrintWriter;

  46. import java.util.Date;

  47. import java.util.Objects;

  48. public class TokenInterceptor implements HandlerInterceptor {

  49. private final Logger logger = LoggerFactory.getLogger(TokenInterceptor.class);

  50. private final String TOKEN_HEADERS_FIELD = "authCheckCode";

  51. private final String TOKEN_KEY = "TokenKey";

  52. public static final long TOKEN_REFRESH_TIME_MILLIS = 1000 * 60 * 60 * 2L;

  53. public static final long TOKEN_EXPIRE_TIME_MILLIS = 1000 * 60 * 60 * 24 * 30L;

  54. /**

  55. * controller 执行之前调用

  56. * @param httpRequest

  57. * @param httpResponse

  58. * @param o

  59. * @return

  60. * @throws IOException

  61. */

  62. @Override

  63. public boolean preHandle(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Object o) throws Exception {

  64. httpResponse.addHeader("Access-Control-Allow-Origin", "*");

  65. httpResponse.addHeader("Access-Control-Allow-Headers","*");

  66. // 允许跨域的Http方法

  67. httpResponse.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE");

  68. // 允许浏览器访问 Token 认证响应头

  69. httpResponse.addHeader("Access-Control-Expose-Headers", TOKEN_HEADERS_FIELD);

  70. // 默认返回原 Token

  71. httpResponse.setHeader(TOKEN_HEADERS_FIELD, httpRequest.getHeader(TOKEN_HEADERS_FIELD));

  72. // 应对探针模式请求(OPTIONS)

  73. String methodOptions = "OPTIONS";

  74. if (httpRequest.getMethod().equals(methodOptions)) {

  75. httpResponse.setStatus(HttpServletResponse.SC_ACCEPTED);

  76. return true;

  77. }

  78. Result result = checkToken(httpRequest, httpResponse);

  79. if (!"00".equals(result.getCode())) {

  80. logger.warn("{}",result.toString());

  81. httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);

  82. httpResponse.setContentType("application/json; charset=utf-8");

  83. httpResponse.setCharacterEncoding("utf-8");

  84. PrintWriter writer = httpResponse.getWriter();

  85. writer.write(new ObjectMapper().writeValueAsString(result.toString()));

  86. return false;

  87. }

  88. return true;

  89. }

  90. private Result checkToken(HttpServletRequest request, HttpServletResponse response) throws Exception {

  91. try {

  92. String token = request.getHeader(TOKEN_HEADERS_FIELD);

  93. if (token == null || token.length() < 1) {

  94. return ResultUtil.requestFaild("无效请求头");

  95. }

  96. String tokenValue = JwtUtil.decode(TOKEN_KEY, token);

  97. long time = Long.parseLong(tokenValue.substring(tokenValue.indexOf("@") + 1));

  98. String user = tokenValue.substring(0, tokenValue.indexOf("@"));

  99. logger.info("{}, date: {}, user: {}", tokenValue, new Date(time), user);

  100. // 校验 Token 有效性

  101. long subResult = System.currentTimeMillis() - time;

  102. if (subResult >= TOKEN_EXPIRE_TIME_MILLIS) {

  103. return ResultUtil.requestFaild("Token过期");

  104. }

  105. if (subResult > TOKEN_REFRESH_TIME_MILLIS) {

  106. // 刷新 Token

  107. String newToken = JwtUtil.encode(TOKEN_KEY,user + "@" + System.currentTimeMillis());

  108. System.out.println("生成新token "+ newToken+ "刷新时间 "+System.currentTimeMillis());

  109. response.setHeader(TOKEN_HEADERS_FIELD, newToken);

  110. return ResultUtil.requestSuccess(null);

  111. }

  112. } catch (Exception e) {

  113. logger.warn("Token 校验失败,{}:{}", e.getClass().getName(), e.getMessage());

  114. return ResultUtil.requestFaild("Token验证失败");

  115. }

  116. return ResultUtil.requestSuccess(null);

  117. }

  118. /**

  119. * controller 执行之后,且页面渲染之前调用

  120. */

  121. @Override

  122. public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

  123. }

  124. /*

  125. *

  126. * 页面渲染之后调用,一般用于资源清理操作

  127. */

  128. @Override

  129. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

  130. }

  131. }

测试

写一个HTTP接口 测试Token验证

  1. /**

  2. * 功能描述 test

  3. *

  4. * @param

  5. * @return com.example.pojo.Result

  6. * @author lty

  7. * @date 2019/11/28

  8. */

  9. @RequestMapping(value = "/t")

  10. public Result t() throws Exception {

  11. return testService.testList();

  12. }

控制台输出日志

  1. 2019-12-17 13:43:46,095 INFO TokenInterceptor:78 - 843328437@1576551621592, date: Tue Dec 17 11:00:21 CST 2019, user: 843328437

  2. 生成新token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJsdHkiLCJUb2tlbktleSI6Ijg0MzMyODQzN0AxNTc2NTYxNDI2MDk4In0.X-qsFfMAP9ebYIHjkpaD3rgJqqPp3QnTuek7aRKvEVE刷新时间 1576561426111

  3. 2019-12-17 13:43:46,113 INFO HttpAspect:50 - url=http://localhost:8080/example/test/t

  4. 2019-12-17 13:43:46,113 INFO HttpAspect:52 - method=POST

  5. 2019-12-17 13:43:46,114 INFO HttpAspect:54 - ip=0:0:0:0:0:0:0:1

  6. 2019-12-17 13:43:46,114 INFO HttpAspect:56 - class_method=com.example.controller.TestController.t

  7. 2019-12-17 13:43:46,114 INFO HttpAspect:58 - args={}

  8. 2019-12-17 13:43:46,114 INFO HttpAspect:59 - >>>>>>>>>>>>>>

Gtihub 地址: github.com/liangtengyu

公众号 java宝典

本文分享自微信公众号 - java宝典(java_bible)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这