• SpringBoot 整合 JWT Token


       之前写了一篇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());
        }
    
    }
    

      

  • 相关阅读:
    Asp.Net Core, Angular 4 CLI站点部署
    .Net ViewState Workflow
    wpf附加属性理解
    wpf使用truetype字体ttf
    wpf 依赖属性注册解释
    wpf控件开发基础
    wpf path语法
    Adorner的收集
    正确理解WPF中的TemplatedParent (转贴)
    “/usr/local/var/run/nginx.pid” failed
  • 原文地址:https://www.cnblogs.com/jingjiren/p/12764525.html
Copyright © 2020-2023  润新知