springCloud JWT

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);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值