Spring Boot REST 风格 API 接口 JWT Token 认证
需求分析
接口认证需求:
1 能够有选择地过滤没有权限(Token)的请求 2 Token 具有时效性 3 如果用户连续操作,Token 能够自动刷新(自动延长有效期)
核心依赖
-
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> -
<dependency> -
<groupId>com.auth0</groupId> -
<artifactId>java-jwt</artifactId> -
<version>3.8.3</version> -
</dependency>
本文使用目前最新版本的依赖
核心代码
JwtUtil 生成和解密Token
-
package com.example.util; -
import com.auth0.jwt.JWT; -
import com.auth0.jwt.JWTVerifier; -
import com.auth0.jwt.algorithms.Algorithm; -
import java.util.Date; -
/** -
* @Author: lty -
* @Date: 2019/12/17 09:33 -
*/ -
public class JwtUtil { -
private final static String DEFAULT_SECRET = "TASDASDF9823K4JH29S8D2H349SDFH14"; -
//region 加密区 -
public static String encode(String k, String v) { -
return encode(k, v, 0); -
} -
private static String encode(String k, String v, long expireTime) { -
return encode(null, k, v, expireTime); -
} -
private static String encode(String secret, String k, String v, long expireTime) { -
if (secret == null || secret.length() < 1) { -
secret = DEFAULT_SECRET; -
} -
Date expDate = null; -
if (expireTime > 1) { -
expDate = new Date(System.currentTimeMillis() + expireTime); -
} -
//创建加密的token -
Algorithm algorithm = Algorithm.HMAC256(secret); -
String token =JWT.create().withIssuer("lty").withClaim(k,v) -
.withExpiresAt(expDate).sign(algorithm); -
return token; -
} -
//endregion -
//region 解密区 -
public static String decode(String key, String encryptedToken) { -
return decode(null, key, encryptedToken); -
} -
public static String decode(String secret, String key, String encryptedToken) { -
if ("".equals(secret) || null == secret) { -
secret = DEFAULT_SECRET; -
} -
Algorithm algorithm = Algorithm.HMAC256(secret); -
JWTVerifier verifier = JWT.require(algorithm) -
.withIssuer("lty") -
.build(); -
String s = verifier.verify(encryptedToken).getClaim(key).asString(); -
return s; -
} -
//endregion -
public static void main(String[] args) { -
String encode = JwtUtil.encode("TokenKey", "843328437@1576551621592"); -
System.out.println(encode); -
String lty = JwtUtil.decode("TokenKey", encode); -
System.out.println(lty); -
} -
}
Token 拦截器
拦截器配置
-
package com.example.interceptor; -
import org.springframework.context.annotation.Configuration; -
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; -
@Configuration -
public class InterceptorConfig extends WebMvcConfigurationSupport { -
/** -
* 多个拦截器组成一个拦截器链 -
* addPathPatterns 用于添加拦截规则 -
* excludePathPatterns 用户排除拦截 -
* @param registry -
*/ -
@Override -
protected void addInterceptors(InterceptorRegistry registry) { -
//拦截 -
registry.addInterceptor(new TokenInterceptor()) -
.addPathPatterns("/**") -
.excludePathPatterns("/autologon/authorizationLogin"); -
super.addInterceptors(registry); -
} -
/** -
* 配置静态资源映射 -
* @param registry -
*/ -
@Override -
protected void addResourceHandlers(ResourceHandlerRegistry registry) { -
///将所有/static/** 访问都映射到classpath:/static/ 目录下 -
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); -
super.addResourceHandlers(registry); -
} -
} -
package com.example.interceptor; -
import com.example.pojo.Result; -
import com.example.util.JwtUtil; -
import com.example.util.ResultUtil; -
import com.fasterxml.jackson.databind.ObjectMapper; -
import org.slf4j.Logger; -
import org.slf4j.LoggerFactory; -
import org.springframework.web.servlet.HandlerInterceptor; -
import org.springframework.web.servlet.ModelAndView; -
import javax.servlet.http.HttpServletRequest; -
import javax.servlet.http.HttpServletResponse; -
import java.io.IOException; -
import java.io.PrintWriter; -
import java.util.Date; -
import java.util.Objects; -
public class TokenInterceptor implements HandlerInterceptor { -
private final Logger logger = LoggerFactory.getLogger(TokenInterceptor.class); -
private final String TOKEN_HEADERS_FIELD = "authCheckCode"; -
private final String TOKEN_KEY = "TokenKey"; -
public static final long TOKEN_REFRESH_TIME_MILLIS = 1000 * 60 * 60 * 2L; -
public static final long TOKEN_EXPIRE_TIME_MILLIS = 1000 * 60 * 60 * 24 * 30L; -
/** -
* controller 执行之前调用 -
* @param httpRequest -
* @param httpResponse -
* @param o -
* @return -
* @throws IOException -
*/ -
@Override -
public boolean preHandle(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Object o) throws Exception { -
httpResponse.addHeader("Access-Control-Allow-Origin", "*"); -
httpResponse.addHeader("Access-Control-Allow-Headers","*"); -
// 允许跨域的Http方法 -
httpResponse.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE"); -
// 允许浏览器访问 Token 认证响应头 -
httpResponse.addHeader("Access-Control-Expose-Headers", TOKEN_HEADERS_FIELD); -
// 默认返回原 Token -
httpResponse.setHeader(TOKEN_HEADERS_FIELD, httpRequest.getHeader(TOKEN_HEADERS_FIELD)); -
// 应对探针模式请求(OPTIONS) -
String methodOptions = "OPTIONS"; -
if (httpRequest.getMethod().equals(methodOptions)) { -
httpResponse.setStatus(HttpServletResponse.SC_ACCEPTED); -
return true; -
} -
Result result = checkToken(httpRequest, httpResponse); -
if (!"00".equals(result.getCode())) { -
logger.warn("{}",result.toString()); -
httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST); -
httpResponse.setContentType("application/json; charset=utf-8"); -
httpResponse.setCharacterEncoding("utf-8"); -
PrintWriter writer = httpResponse.getWriter(); -
writer.write(new ObjectMapper().writeValueAsString(result.toString())); -
return false; -
} -
return true; -
} -
private Result checkToken(HttpServletRequest request, HttpServletResponse response) throws Exception { -
try { -
String token = request.getHeader(TOKEN_HEADERS_FIELD); -
if (token == null || token.length() < 1) { -
return ResultUtil.requestFaild("无效请求头"); -
} -
String tokenValue = JwtUtil.decode(TOKEN_KEY, token); -
long time = Long.parseLong(tokenValue.substring(tokenValue.indexOf("@") + 1)); -
String user = tokenValue.substring(0, tokenValue.indexOf("@")); -
logger.info("{}, date: {}, user: {}", tokenValue, new Date(time), user); -
// 校验 Token 有效性 -
long subResult = System.currentTimeMillis() - time; -
if (subResult >= TOKEN_EXPIRE_TIME_MILLIS) { -
return ResultUtil.requestFaild("Token过期"); -
} -
if (subResult > TOKEN_REFRESH_TIME_MILLIS) { -
// 刷新 Token -
String newToken = JwtUtil.encode(TOKEN_KEY,user + "@" + System.currentTimeMillis()); -
System.out.println("生成新token "+ newToken+ "刷新时间 "+System.currentTimeMillis()); -
response.setHeader(TOKEN_HEADERS_FIELD, newToken); -
return ResultUtil.requestSuccess(null); -
} -
} catch (Exception e) { -
logger.warn("Token 校验失败,{}:{}", e.getClass().getName(), e.getMessage()); -
return ResultUtil.requestFaild("Token验证失败"); -
} -
return ResultUtil.requestSuccess(null); -
} -
/** -
* controller 执行之后,且页面渲染之前调用 -
*/ -
@Override -
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { -
} -
/* -
* -
* 页面渲染之后调用,一般用于资源清理操作 -
*/ -
@Override -
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { -
} -
}
测试
写一个HTTP接口 测试Token验证
-
/** -
* 功能描述 test -
* -
* @param -
* @return com.example.pojo.Result -
* @author lty -
* @date 2019/11/28 -
*/ -
@RequestMapping(value = "/t") -
public Result t() throws Exception { -
return testService.testList(); -
}
控制台输出日志
-
2019-12-17 13:43:46,095 INFO TokenInterceptor:78 - 843328437@1576551621592, date: Tue Dec 17 11:00:21 CST 2019, user: 843328437 -
生成新token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJsdHkiLCJUb2tlbktleSI6Ijg0MzMyODQzN0AxNTc2NTYxNDI2MDk4In0.X-qsFfMAP9ebYIHjkpaD3rgJqqPp3QnTuek7aRKvEVE刷新时间 1576561426111 -
2019-12-17 13:43:46,113 INFO HttpAspect:50 - url=http://localhost:8080/example/test/t -
2019-12-17 13:43:46,113 INFO HttpAspect:52 - method=POST -
2019-12-17 13:43:46,114 INFO HttpAspect:54 - ip=0:0:0:0:0:0:0:1 -
2019-12-17 13:43:46,114 INFO HttpAspect:56 - class_method=com.example.controller.TestController.t -
2019-12-17 13:43:46,114 INFO HttpAspect:58 - args={} -
2019-12-17 13:43:46,114 INFO HttpAspect:59 - >>>>>>>>>>>>>>
Gtihub 地址: github.com/liangtengyu
公众号 java宝典
本文分享自微信公众号 - java宝典(java_bible)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。