springCloud JWT
近段时间跟着项目走,记录一下springcloud使用JWT来完成授权功能
1.引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
2.常量类
public class CommonConstant {
/**
* AUTHORIZATION
*/
public final static String AUTHORIZATION = "token";
/**
* 禁止
*/
public final static String DISABLE = "0";
/**
* JWT-userId:
*/
public final static String USER_ID = "userId";
/**
* JWT-mobile
*/
public final static String MOBILE = "mobile";
/**
* JWT-currentTimeMillis:
*/
public final static String CURRENT_TIME_MILLIS = "currentTimeMillis";
}
3.封装JWTUtil工具类
package com.dytz.dyyy.app.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.dytz.dyyy.app.consts.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import static com.dytz.dyyy.app.consts.CommonConstant.CURRENT_TIME_MILLIS;
@Component
@Slf4j
public class JwtUtil {
/**
* 过期时间
*/
private static String accessTokenExpireTime;
/**
* JWT认证加密私钥(Base64加密)
*/
private static String encryptJWTKey;
/**
* 解决@Value不能修饰static的问题
*/
@Value("${accessTokenExpireTime}")
public void setAccessTokenExpireTime(String accessTokenExpireTime) {
JwtUtil.accessTokenExpireTime = accessTokenExpireTime;
}
@Value("${encryptJWTKey}")
public void setEncryptJWTKey(String encryptJWTKey) {
JwtUtil.encryptJWTKey = encryptJWTKey;
}
/**
* @Title: verify @Description: TODO(检验token是否有效) @param: @param
* token @param: @return @return: boolean @throws
*/
public static boolean verify(String token) {
try {
// 通过token获取密码
String secret = getUserId(token) + Base64ConvertUtil.encode(encryptJWTKey) + getMobile(token);
// 进行二次加密
Algorithm algorithm = Algorithm.HMAC256(secret);
// 使用JWTVerifier进行验证解密
JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(token);
return true;
} catch (UnsupportedEncodingException e) {
log.error("JWTToken认证解密出现UnsupportedEncodingException异常:" + e.getMessage());
return false;
}
}
/**
* @Title: sign @Description: TODO(生成签名) @param: @param mobile @param: @param
* password @param: @param currentTimeMillis @param: @return @return:
* String @throws
*/
public static String getToken(String userId, String mobile, String currentTimeMillis) {
try {
// 使用私钥进行加密
String secret = userId + Base64ConvertUtil.encode(encryptJWTKey) + mobile;
// 设置过期时间:根据当前时间计算出过期时间。 此处过期时间是以毫秒为单位,所以乘以1000。
Date date = new Date(System.currentTimeMillis() + Long.parseLong(accessTokenExpireTime) * 1000);
// 对私钥进行再次加密
Algorithm algorithm = Algorithm.HMAC256(secret);
// 生成token 附带account信息(生成令牌)
String token = JWT.create() //生成令牌
.withClaim(CommonConstant.MOBILE, mobile)//设置自定义数据
.withClaim(CommonConstant.USER_ID, userId)//设置自定义数据
.withClaim(CURRENT_TIME_MILLIS, currentTimeMillis)
.withExpiresAt(date)//设置过期时间
.sign(algorithm);//设置签名 保密 复杂
return token;
} catch (UnsupportedEncodingException e) {
log.error("JWTToken加密出现UnsupportedEncodingException异常:" + e.getMessage());
throw new RuntimeException("JWTToken加密出现UnsupportedEncodingException异常:" + e.getMessage());
}
}
/**
* @Title: getClaim @Description:
* TODO(获取token中的信息就是withClaim中设置的值) @param: @param token @param: @param
* claim:sign()方法中withClaim设置的值 @param: @return @return: String @throws
*/
public static String getClaim(String token, String claim) {
try {
// 对token进行解码获得解码后的jwt
DecodedJWT jwt = JWT.decode(token);
// 获取到指定的claim,如果是其他类型返回null
return jwt.getClaim(claim).asString();
} catch (JWTDecodeException e) {
log.error("解密Token中的公共信息出现JWTDecodeException异常:" + e.getMessage());
throw new RuntimeException("解密Token中的公共信息出现JWTDecodeException异常:" + e.getMessage());
}
}
/**
* token 获取用户userID
*
* @param token
* @return
*/
public static String getUserId(String token) {
String userId = getClaim(token, CommonConstant.USER_ID);
return userId;
}
/**
* token 获取手机号码
*
* @param token
* @return
*/
public static String getMobile(String token) {
String mobile = getClaim(token, CommonConstant.MOBILE);
return mobile;
}
/**
* token 获取token过期时间
*
* @param token
* @return
*/
public static String getCurrentTimeMillis(String token) {
String currentTimeMillis = getClaim(token, CommonConstant.CURRENT_TIME_MILLIS);
return currentTimeMillis;
}
}
public class AuthorizationInterceptor extends SentinelWebInterceptor implements HandlerInterceptor {
@Value("${refreshTokenExpireTime}")
private String refreshTokenExpireTime;
@Autowired
private IUserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
String requestURI = request.getRequestURI();
//对于不用进行验证的接口直接放行
if (ReleaseAddressUtil.confirm(requestURI)) {
return true;
}
String token = request.getHeader(CommonConstant.AUTHORIZATION);
if (token == null) {
// 获取当前请求类型
String httpMethod = request.getMethod();
// 获取当前请求URI
log.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod);
// mustLoginFlag = true 开启任何请求必须登录才可访问
this.response401(request, response, "请先登录");
return false;
}
String mobile = null;
String userId = null;
try {
mobile = JwtUtil.getMobile(token);
userId = JwtUtil.getUserId(token);
} catch (Exception e) {
e.printStackTrace();
log.info(e.getMessage());
this.response401(request,response,"token错误!");
return false;
}
if (StringUtils.isEmpty(mobile)) {
this.response401(request, response, "token错误!");
return false;
}
if (StringUtils.isEmpty(userId)) {
this.response401(request, response, "token错误!");
return false;
}
User user = userService.getByUserId(Long.valueOf(userId));
if (user == null) {
this.response401(request, response, "账号不存在!");
return false;
}
if (user.getStatus() != null && CommonConstant.DISABLE.equals(user.getStatus())) {
this.response401(request, response, "帐号已被锁定,禁止登录!");
return false;
}
try {
if (JwtUtil.verify(token) && RedisUtil.hasKey(RedisKeyPrefix.SFC_REFRESH_TOKEN + mobile)) {
Object currentTimeMillisRedis = RedisUtil.get(RedisKeyPrefix.SFC_REFRESH_TOKEN + mobile);
// 获取AccessToken时间戳,与RefreshToken的时间戳对比
if (JwtUtil.getCurrentTimeMillis(token).equals(currentTimeMillisRedis.toString())) {
UserInfo userInfo = new UserInfo();
userInfo.setId(user.getId());
userInfo.setUserId(user.getUserId());
userInfo.setMobile(user.getMobile());
userInfo.setNickname(user.getNickname());
UserContext.set(userInfo);
//TODO token 刷新策略问题,每次/每天刷新?
// refreshToken(request, response);
return true;
}
response401(request,response,"token currentTimeMillis not equals.");
return false;
}
} catch (Exception e) {
e.printStackTrace();
// 认证出现异常,传递错误信息msg
String msg = e.getMessage();
// if (refreshToken(request, response)) {
// return true;
// } else {
this.response401(request, response, msg);
return false;
// }
/* // 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
Throwable throwable = e.getCause();
if (throwable instanceof SignatureVerificationException) {
// 该异常为JWT的AccessToken认证失败(Token或者密钥不正确)
msg = "Token或者密钥不正确(" + throwable.getMessage() + ")";
} else if (throwable instanceof TokenExpiredException) {
// 该异常为JWT的AccessToken已过期,判断RefreshToken未过期就进行AccessToken刷新
// 刷新token
if (refreshToken(request, response)) {
return true;
} else {
msg = "Token已过期(" + throwable.getMessage() + ")";
}
} else {
// 应用异常不为空
if (throwable != null) {
// 获取应用异常msg
msg = throwable.getMessage();
}
}
this.response401(request, response, msg);
return false;*/
}
return false;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) {
//关闭threadLocal
UserContext.remove();
}
/**
* 缺少权限内部转发至401处理
*
* @param request
* @param response
* @param msg
*/
private void response401(ServletRequest request, ServletResponse response, String msg) {
HttpServletRequest req = (HttpServletRequest) request;
try {
req.getRequestDispatcher("/app/user/login/unauthorized?message=" + msg).forward(request, response);
} catch (Exception e) {
e.printStackTrace();
log.error("未授权登录{}", e);
}
}
// @Async
public boolean refreshToken(HttpServletRequest request, HttpServletResponse response) {
String token = request.getHeader(CommonConstant.AUTHORIZATION);
// 获取当前Token的帐号信息
String userId = JwtUtil.getUserId(token);
String mobile = JwtUtil.getMobile(token);
// 判断Redis中RefreshToken是否存在
if (RedisUtil.hasKey(RedisKeyPrefix.SFC_REFRESH_TOKEN + mobile)) {
// Redis中RefreshToken还存在,获取RefreshToken的时间戳
Object currentTimeMillisRedis = RedisUtil.get(RedisKeyPrefix.SFC_REFRESH_TOKEN + mobile);
// 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新
if (JwtUtil.getCurrentTimeMillis(token).equals(currentTimeMillisRedis.toString())) {
// 获取当前最新时间戳
String currentTimeMillis = String.valueOf(System.currentTimeMillis());
// 设置RefreshToken中的时间戳为当前最新时间戳,且刷新过期时间重新为30分钟过期(配置文件可配置refreshTokenExpireTime属性)
RedisUtil.setEx(RedisKeyPrefix.SFC_REFRESH_TOKEN + mobile, currentTimeMillis,
Long.parseLong(refreshTokenExpireTime), TimeUnit.SECONDS);
// 刷新AccessToken延长过期时间,设置时间戳为当前最新时间戳
token = JwtUtil.getToken(userId, mobile, currentTimeMillis);
// 将token刷入response的header中
response.setHeader("refreshToken", token);
// response.setHeader("Access-Control-Expose-Headers", "Authorization");
return true;
}
}
return false;
}
}
public class RedisKeyPrefix {
/**
* 验证码发送前缀key
*/
public static final String DYYY_APP_SERVER_LOGIN_SMS_SEND = "mwx:mwx-server:sms-send:";
/**
* 验证码发送次数统计
*/
public static final String DYYY_APP_SERVER_LOGIN_SMS_SEND_COUNT = "mwx:mwx-server:sms-send:count:";
/**
* redis-key-前缀-shiro:refresh_token:
*/
public final static String SFC_REFRESH_TOKEN = "mwx:refresh_token:";
}
@Component
public class RedisUtil {
private static RedisTemplate redisTemplate;
@Autowired
public RedisUtil(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
// *********************************************** key相关操作 start
/**
* @throws
* @Title: keys
* @Description: TODO(获取所有的key)
* @param: @return
* @return: Set<String>
*/
public static Set<String> keys() {
Set<String> keys = redisTemplate.keys("*");
return keys;
}
//获取所有的values
public static List<Object> values() {
Set<String> keys = redisTemplate.keys("*");
List<Object> list = new ArrayList<Object>();
for (String key : keys) {
list.add(RedisUtil.get(key));
}
return list;
}
/**
* 删除key
*/
public static void delete(String key) {
redisTemplate.delete(key);
}
/**
* 批量删除key
*/
public static void delete(Collection<String> keys) {
redisTemplate.delete(keys);
}
/**
* 序列化key
*/
public byte[] dump(String key) {
return redisTemplate.dump(key);
}
/**
* 是否存在key
*/
public static Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*/
public static Boolean expire(String key, Long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 设置过期时间
*/
public static Boolean expireAt(String key, Date date) {
return redisTemplate.expireAt(key, date);
}
/**
* 查找匹配的key
*/
public static Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 将当前数据库的 key 移动到给定的数据库 db 当中
*/
public static Boolean move(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
/**
* 移除 key 的过期时间,key 将持久保持
*/
public static Boolean persist(String key) {
return redisTemplate.persist(key);
}
/**
* 返回 key 的剩余的过期时间
*/
public static Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
/**
* 返回 key 的剩余的过期时间
*/
public static Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
//*************************************************string相关操作
/**
* 设置指定 key 的值
*/
public static void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 获取指定 key 的值
*/
public static Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 返回 key 中字符串值的子字符
*/
public static String getRange(String key, Long start, Long end) {
return redisTemplate.opsForValue().get(key, start, end);
}
/**
* 将值 value 关联到 key ,并将 key 的过期时间设为 timeout
*/
public static void setEx(String key, String value, Long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 只有在 key 不存在时设置 key 的值
*/
public static Boolean setIfAbsent(String key, String value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
*/
public static void setRange(String key, String value, Long offset) {
redisTemplate.opsForValue().set(key, value, offset);
}
}
/**
* 用户信息上下文
*
* @author linfan.sun
* @description:
* @time: 2020/12/18 17:15
*/
public class UserContext {
private static ThreadLocal<UserInfo> userThread = new ThreadLocal<>();
public static void set(UserInfo userInfo) {
userThread.set(userInfo);
}
public static UserInfo get() {
return userThread.get();
}
/**
* 获取当前登录用户的ID
* 未登录返回null
*
* @return
*/
public static Long getUserId() {
UserInfo user = get();
if (user != null && user.getUserId() != null) {
return user.getUserId();
}
return null;
}
//防止内存泄漏
public static void remove() {
userThread.remove();
}
}
@Component
public class Base64ConvertUtil{
/**
* @Title: encode
* @Description: TODO(JDK1.8加密)
* @param: @param str
* @param: @return
* @param: @throws UnsupportedEncodingException
* @return: String
* @throws
*/
public static String encode(String str) throws UnsupportedEncodingException {
byte[] encodeBytes = Base64.getEncoder().encode(str.getBytes("utf-8"));
return new String(encodeBytes);
}
/**
* @Title: decode
* @Description: TODO(JDK1.8解密)
* @param: @param str
* @param: @return
* @param: @throws UnsupportedEncodingException
* @return: String
* @throws
*/
public static String decode(String str) throws UnsupportedEncodingException {
byte[] decodeBytes = Base64.getDecoder().decode(str.getBytes("utf-8"));
return new String(decodeBytes);
}
}
@Slf4j
public class DateUtils {
final static SimpleDateFormat formatterDay = new SimpleDateFormat("yyyy-MM-dd");
public static Date getNextDate() throws ParseException {
Date date=new Date();//取时间
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
//把日期往后增加一天.整数往后推,负数往前移动
calendar.add(Calendar.DATE,1);
//这个时间就是日期往后推一天的结果
date=calendar.getTime();
return formatterDay.parse(formatterDay.format(date));
}
}
/**
* @ClassName StringRegex
* @Description 正则验证工具类
* @Author stone
* @Date 2020/12/15 上午1:41
*/
public class StringRegexUtils {
private static final Pattern mobilePattern = Pattern.compile("^(13[0-9]|14[01456879]|15[0-3,5-9]|16[2567]|17[0-8]|18[0-9]|19[0-3,5-9])\\d{8}$");
/**
* 验证中国各运营商号段手机号
* @param phone
* @return
*/
public static boolean verifyMobilePhone(String phone) {
return StringUtils.isNotEmpty(phone) && mobilePattern.matcher(phone).matches();
}
}
/**
* 自定义要放行的接口
*
* @description:
* @author: sun
* @time: 2020/11/25 16:39
*/
public class ReleaseAddressUtil {
private static Set<String> getInterface() {
Set<String> set = new HashSet<String>();
set.add("/app/user/login/getVerificationCode");
set.add("/app/user/login/getVerificationVoiceCode");
set.add("/app/user/login/loginBySms");
set.add("/app/user/login/loginBySy");
set.add("/app/user/login/unauthorized");
set.add("/app/user/logout");
set.add("/app/map/searchPlace");
set.add("/error");
// 所有请求通过我们自己的JWT Filter
return set;
}
public static Boolean confirm(String requestURI) {
Set<String> set = getInterface();
return set.contains(requestURI);
}
}
/**
* 跨域配置
* @author linfan.sun
* @description:
* @time: 2020/12/17 16:45
*/
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
return new CorsFilter(configSource);
}
}