之前写了一篇JWT的随笔, 还没有写springboot 整合 token, 前几天晚上忙着抽空重构代码, 就简单的重构了下方法, 类. 还有if else, + 设计模式, +泛型没整. 等周末有时间再整整. 其实理解了jwt流程, 整合进springboot就相对于来说较简单了. 依赖加下, 拦截器加下, 代码加下. 话不多说, 上代码
pom.xml 依赖
<!-- jwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
application.properties 配置
##jwt配置 # 代表这个JWT的接收对象,存入audience audience.clientId= # 密钥, 经过Base64加密, 可自行替换 audience.base64Secret== # JWT的签发主体,存入issuer audience.name= # 过期时间,时间戳 audience.expiresSecond=
拦截器
import com.spSystem.annotation.JwtIgnore;
import com.spSystem.common.exception.CustomException;
import com.spSystem.common.exception.response.ResultCode;
import com.spSystem.entity.Audience;
import com.spSystem.utils.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
/**
* token验证拦截器
*/
@Slf4j
public class JwtInterceptor extends HandlerInterceptorAdapter {
private static final Logger log = LoggerFactory.getLogger(JwtInterceptor.class);
@Autowired
private Audience audience;
@Autowired
private RedisTemplate redisTemplate;
public static String lastToken = "";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 忽略带JwtIgnore注解的请求, 不做后续token认证校验
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class);
if (jwtIgnore != null) {
return true;
}
}
if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
// 获取请求头信息authorization信息
final String authHeader = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
log.info("## authHeader= {}", authHeader);
if (StringUtils.isBlank(authHeader) || !authHeader.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
log.info("### 用户未登录,请先登录 ###");
throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
}
// 获取token
final String token = authHeader.substring(7);
if (audience == null) {
BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
audience = (Audience) factory.getBean("audience");
}
// 验证token是否有效--无效已做异常抛出,由全局异常处理后返回对应信息
// JwtTokenUtil.parseJWT(token, audience.getBase64Secret());
}
}
WebConfig
import com.spSystem.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public JwtInterceptor getInterceptor() {
return new JwtInterceptor();
}
/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截路径可自行配置多个 可用 ,分隔开
registry.addInterceptor(getInterceptor()).addPathPatterns("/**");
}
/**
* 跨域支持
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD")
.maxAge(3600 * 24);
}
@Override
public void configurePathMatch(PathMatchConfigurer pathMatchConfigurer) {
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer contentNegotiationConfigurer) {
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer asyncSupportConfigurer) {
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer defaultServletHandlerConfigurer) {
}
@Override
public void addFormatters(FormatterRegistry formatterRegistry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry resourceHandlerRegistry) {
}
@Override
public void addViewControllers(ViewControllerRegistry viewControllerRegistry) {
}
@Override
public void configureViewResolvers(ViewResolverRegistry viewResolverRegistry) {
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> list) {
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> list) {
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> list) {
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> list) {
}
@Override
public Validator getValidator() {
return null;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
JWT验证忽略注解
import java.lang.annotation.*;
/**
* JWT验证忽略注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JwtIgnore {
}
全局异常处理器
import com.spSystem.common.exception.CustomException;
import com.spSystem.common.exception.response.Result;
import com.spSystem.common.exception.response.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理自定义异常
*/
@ExceptionHandler(CustomException.class)
public Result handleException(CustomException e) {
// 打印异常信息
log.error("### 异常信息:{} ###", e.getMessage());
return new Result(e.getResultCode());
}
/**
* 参数错误异常
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
public Result handleException(Exception e) {
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
BindingResult result = validException.getBindingResult();
StringBuffer errorMsg = new StringBuffer();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
// errors.forEach(p ->{
// FieldError fieldError = (FieldError) p;
// errorMsg.append(fieldError.getDefaultMessage()).append(",");
// log.error("### 请求参数错误:{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+ "},errorMessage{"+fieldError.getDefaultMessage()+"}"); });
}
} else if (e instanceof BindException) {
BindException bindException = (BindException)e;
if (bindException.hasErrors()) {
log.error("### 请求参数错误: {}", bindException.getAllErrors());
}
}
return new Result(ResultCode.PARAM_IS_INVALID);
}
/**
* 处理所有不可知的异常
*/
@ExceptionHandler(Exception.class)
public Result handleOtherException(Exception e){
//打印异常堆栈信息
e.printStackTrace();
// 打印异常信息
log.error("### 不可知的异常:{} ###", e.getMessage());
return new Result(ResultCode.SYSTEM_INNER_ERROR);
}
}
自定义异常类型
import com.spSystem.common.exception.response.ResultCode;
import java.text.MessageFormat;
/**
* 自定义异常类型
**/
public class CustomException extends RuntimeException {
//错误代码
ResultCode resultCode;
public CustomException(ResultCode resultCode){
super(resultCode.message());
this.resultCode = resultCode;
}
public CustomException(ResultCode resultCode, Object... args){
super(resultCode.message());
String message = MessageFormat.format(resultCode.message(), args);
resultCode.setMessage(message);
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return resultCode;
}
}
统一响应结果集
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
/**
* 统一响应结果集
*/
@JsonSerialize(include= JsonSerialize.Inclusion.NON_NULL)
public class Result<T> {
//操作代码
int code;
//提示信息
String message;
//结果数据
T data;
public Result(ResultCode resultCode){
this.code = resultCode.code();
this.message = resultCode.message();
}
public Result(ResultCode resultCode, T data){
this.code = resultCode.code();
this.message = resultCode.message();
this.data = data;
}
public Result(String message){
this.message = message;
}
public static Result SUCCESS(){
return new Result(ResultCode.SUCCESS);
}
public static <T> Result SUCCESS(T data){
return new Result(ResultCode.SUCCESS, data);
}
public static Result FAIL(){
return new Result(ResultCode.FAIL);
}
public static Result FAIL(String message){
return new Result(message);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
通用响应状态
/**
* 通用响应状态
*/
public enum ResultCode {
/* 成功状态码 */
SUCCESS(200,"操作成功"),
/* 错误状态码 */
FAIL(500,"操作失败"),
/* 参数错误:10001-19999 */
PARAM_IS_INVALID(1001, "参数无效"),
PARAM_IS_BLANK(1002, "参数为空"),
PARAM_TYPE_BIND_ERROR(1003, "参数格式错误"),
PARAM_NOT_COMPLETE(1004, "参数缺失"),
/* 用户错误:20001-29999*/
USER_NOT_LOGGED_IN(2001, "用户未登录,请先登录"),
USER_LOGIN_ERROR(2002, "账号不存在或密码错误"),
USER_ACCOUNT_FORBIDDEN(2003, "账号已被禁用"),
USER_NOT_EXIST(2004, "用户不存在"),
USER_HAS_EXISTED(2005, "用户已存在"),
/* 业务错误:30001-39999 */
BUSINESS_GROUP_NO_ALLOWED_DEL(30001, "应用分组已经被应用使用,不能删除"),
BUSINESS_THEME_NO_ALLOWED_DEL(30002, "主题已经被用户使用,不能删除"),
BUSINESS_THEME_NO_ALLOWED_DISABLE(30003, "主题已经被用户使用,不能停用"),
BUSINESS_THEME_DEFAULT_NO_ALLOWED_DEL(30004, "默认主题,不能删除"),
BUSINESS_THEME_NO_ALLOWED_UPDATE(30005, "主题已经被用户使用,不能修改图片信息"),
BUSINESS_IS_TOP(30040, "已经到最顶部"),
BUSINESS_IS_BOTTOM(30041, "已经到最底部"),
BUSINESS_NAME_EXISTED(30051, "名称已存在"),
/* 系统错误:40001-49999 */
SYSTEM_INNER_ERROR(40001, "系统繁忙,请稍后重试"),
UPLOAD_ERROR(40002, "系统异常,上传文件失败"),
FILE_MAX_SIZE_OVERFLOW(40003, "上传尺寸过大"),
FILE_ACCEPT_NOT_SUPPORT(40004, "上传文件格式不支持"),
SET_UP_AT_LEAST_ONE_ADMIN(40005, "至少指定一个管理员"),
URL_INVALID(40006, "地址不合法"),
LINK_AND_LOGOUT_NO_MATCH(40006, "主页地址和注销地址IP不一致"),
IP_AND_PORT_EXISTED(40007, "当前IP和端口已经被占中"),
LINK_IS_REQUIRED(40008, "生成第三方token认证信息:主页地址不能为空,请完善信息"),
ONLY_ROOT_DEPARTMENT(40009, "组织机构只能存在一个根机构"),
DEPART_CODE_EXISTED(40010, "组织机构编码已存在"),
DEPART_CONTAINS_USERS(40011, "该机构下是存在用户,不允许删除"),
DEPART_CONTAINS_SON(40012, "该机构下是存在子级机构,不允许删除"),
DEPART_PARENT_IS_SELF(40013, "选择的父机构不能为本身"),
DICT_EXIST_DEPEND(40014, "该字典数据存在详情依赖,不允许删除"),
DICT_DETAIL_LOCK(40015, "该字典数据被锁定,不允许修改或删除"),
DEPART_CODE_EXISTED_WITH_ARGS(40016, "组织机构编码【{0}】系统已存在"),
/* 数据错误:50001-599999 */
RESULT_DATA_NONE(50001, "数据未找到"),
DATA_IS_WRONG(50002, "数据有误"),
DATA_ALREADY_EXISTED(50003, "数据已存在"),
/* 接口错误:60001-69999 */
INTERFACE_INNER_INVOKE_ERROR(60001, "内部系统接口调用异常"),
INTERFACE_OUTTER_INVOKE_ERROR(60002, "外部系统接口调用异常"),
INTERFACE_FORBID_VISIT(60003, "该接口禁止访问"),
INTERFACE_ADDRESS_INVALID(60004, "接口地址无效"),
INTERFACE_REQUEST_TIMEOUT(60005, "接口请求超时"),
INTERFACE_EXCEED_LOAD(60006, "接口负载过高"),
/* 权限错误:70001-79999 */
PERMISSION_UNAUTHENTICATED(70001,"此操作需要登陆系统"),
PERMISSION_UNAUTHORISE(70002,"权限不足,无权操作"),
PERMISSION_EXPIRE(70003,"登录状态过期"),
PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),
PERMISSION_LIMIT(70005, "访问次数受限制"),
PERMISSION_TOKEN_INVALID(70006, "无效token"),
PERMISSION_SIGNATURE_ERROR(70007, "签名失败");
//操作代码
int code;
//提示信息
String message;
ResultCode(int code, String message){
this.code = code;
this.message = message;
}
public int code() {
return code;
}
public String message() {
return message;
}
public void setCode(int code) {
this.code = code;
}
public void setMessage(String message) {
this.message = message;
}
}
JwtTokenUtil
import com.spSystem.common.exception.CustomException;
import com.spSystem.common.exception.response.ResultCode;
import com.spSystem.entity.Audience;
import com.spSystem.model.CdAppUsers;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
public class JwtTokenUtil {
private static Logger log = LoggerFactory.getLogger(JwtTokenUtil.class);
public static final String AUTH_HEADER_KEY = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 解析jwt
*/
public static Claims parseJWT(String jsonWebToken, String base64Security) {
try {
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
.parseClaimsJws(jsonWebToken).getBody();
return claims;
} catch (ExpiredJwtException eje) {
log.error("===== Token过期 =====", eje);
throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED);
} catch (Exception e){
log.error("===== token解析异常 =====", e);
throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
}
}
/**
* 构建jwt
*/
public static String createJWT(String userId, String phone, String role,Audience audience) {
try {
// 使用HS256加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//生成签名密钥
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(audience.getBase64Secret());
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
//userId是重要信息,进行加密下
String encryId = Base64Util.encode(userId);
//添加构成JWT的参数
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
// 可以将基本不重要的对象信息放到claims
.claim("role", role)
.claim("userId", userId)
.setSubject(phone) // 代表这个JWT的主体,即它的所有人
.setIssuer(audience.getClientId()) // 代表这个JWT的签发主体;
.setIssuedAt(new Date()) // 是一个时间戳,代表这个JWT的签发时间;
.setAudience(audience.getName()) // 代表这个JWT的接收对象;
.signWith(signatureAlgorithm, signingKey);
//添加Token过期时间
int TTLMillis = audience.getExpiresSecond();
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
// builder.setExpiration(exp) // 是一个时间戳,代表这个JWT的过期时间;
builder.setNotBefore(now); // 是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的
}
//生成JWT
return builder.compact();
} catch (Exception e) {
log.error("签名失败", e);
throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
}
}
/**
* 从token中获取用户名
*/
public static String getPhone(String token, String base64Security){
return parseJWT(token, base64Security).getSubject();
}
/**
* 从token中获取用户ID
*/
public static String getUserId(String token, String base64Security){
return parseJWT(token, base64Security).get("userId", String.class);
}
/**
* 是否已过期
*/
public static boolean isExpiration(String token, String base64Security) {
return parseJWT(token, base64Security).getExpiration().before(new Date());
}
}
原文:https://www.cnblogs.com/jingjiren/p/12764525.html