• JWT-JWT实现SSO


    默认你已经了解了什么是JWT,若对JWT概念不清晰,可查看上一篇文章:

    JWT-jwt是什么

    一、实现思路

    1、第一次访问

    在刚刚进入系统时,此刻我们是未登录状态,会跳转到登录界面,登录后,后端服务在返回中会带一个JWT的token给前端服务。

    2、非第一次访问

    登录之后进行其他请求时,前端服务需要将该token放入请求头中带给后端服务(放其他地方也行,放请求头中方便一点),后端服务接收到接口请求时,获取该token,进行JWT的验证,通过则放行请求,失败则拦截请求。

    二、所需模块

    在绝大多数SSO系统中,会有个登录系统,主要功能有两个:账号信息管理、登录管理。
    今天我们要聊的就是如何利用JWT实现登录管理。

    1、必须模块

    a、JWT工具类

    这个工具类主要包含了JWT的创建、验证、获取信息等功能,主要对外提供操作JWT的各种方法,是整个JWT实现的核心。
    实现:
    maven依赖:
    <!--jwt auth0-->
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.8.3</version>
    </dependency>
    创建JWTUtils.java文件
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTCreator;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.Claim;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author pkuoukuo
     * @date 2021/8/20 14:17
     * <auth0类包JWT工具类包>
     */
    
    public class JWTUtils {
        /**
         * JWT头部信息
         */
        private static Map<String, Object> HEADER_MAP = new HashMap<>();
    
        /**
         * JWT加密秘钥
         */
        private static String TOKEN_KEY = "p3:As3[Vd&da.D";
    
        /**
         * JWT生效时间
         */
        private static Date TOKEN_BEGIN = new Date();
    
        /**
         * JWT到期时间,默认十分钟
         */
        private static Date TOKEN_TIMEOUT = new Date(TOKEN_BEGIN.getTime() + 10 * 60 * 1000);
    
        /**
         * JWT签发主体
         */
        private static String TOKEN_ISSUSER = "";
    
        /**
         * JWT接收主体
         */
        private static String TOKEN_AUDIENCE = "aaa";
    
        /**
         * JWT所有人
         */
        private static String TOKEN_SUBJECT = "";
    
        /**
         * JWT唯一标识
         */
        private static String TOKEN_JWT_ID = "";
    
        /**
         * JWT自定义声明
         */
        private static Map<String, Object> TOKEN_CLAIM_MAP = new HashMap<>();
    
        private static String TOKEN_KEY_ID = "";
    
        private static Map<String, Object[]> TOKEN_ARRAY_CLAIM = new HashMap<>();
    
        /**
         * 设置头部信息
         * @param header_map
         */
        public static void setHeaderMap(Map<String, Object> header_map){
            HEADER_MAP = header_map;
        }
        /**
         * 设置加密秘钥
         * @param token_key
         */
        public static void setTokenKey(String token_key){
            TOKEN_KEY = token_key;
        }
    
        /**
         * 设置生效时间
         * @param token_begin
         */
        public static void setTokenBegin(Date token_begin){
            TOKEN_BEGIN = token_begin;
        }
    
        /**
         * 设置到期时间
         * @param token_timeout
         */
        public static void setTokenTimeout(Date token_timeout){
            TOKEN_TIMEOUT = token_timeout;
        }
    
        /**
         * 设置签发主体
         * @param token_issuser
         */
        public static void setTokenIssuser(String token_issuser){
            TOKEN_ISSUSER = token_issuser;
        }
    
        /**
         * 设置接收主体
         * @param token_audience
         */
        public static void setTokenAudience(String token_audience){
            TOKEN_AUDIENCE = token_audience;
        }
    
        /**
         * 设置所有人
         * @param token_subject
         */
        public static void setTokenSubject(String token_subject){
            TOKEN_SUBJECT = token_subject;
        }
    
        /**
         * 设置唯一标识
         * @param token_jwt_id
         */
        public static void setTokenJwtId(String token_jwt_id){
            TOKEN_JWT_ID = token_jwt_id;
        }
    
        /**
         * 设置自定义声明
         * @param token_claim_map
         */
        public static void setTokenClaimMap(Map<String, Object> token_claim_map){
            TOKEN_CLAIM_MAP = token_claim_map;
        }
    
        /**
         *
         * @param token_key_id
         */
        public static void setTokenKeyId(String token_key_id){
            TOKEN_KEY_ID = token_key_id;
        }
    
        /**
         *
         * @param token_array_claim
         */
        public static void setTokenArrayClaim(Map<String, Object[]> token_array_claim){
            TOKEN_ARRAY_CLAIM = token_array_claim;
        }
    
        /**
         * 创建JWT
         */
        public static String createJWT(String token_key){
            return createJWT(HEADER_MAP, TOKEN_BEGIN, TOKEN_TIMEOUT, TOKEN_AUDIENCE, TOKEN_ISSUSER, TOKEN_SUBJECT, token_key, TOKEN_CLAIM_MAP, TOKEN_JWT_ID, TOKEN_KEY_ID);
        }
    
        public static String createJWT(String token_key, Map<String,Object> token_claim_map){
            return createJWT(HEADER_MAP, TOKEN_BEGIN, TOKEN_TIMEOUT, TOKEN_AUDIENCE, TOKEN_ISSUSER, TOKEN_SUBJECT, token_key, token_claim_map, TOKEN_JWT_ID, TOKEN_KEY_ID);
        }
    
        public static String createJWT(String token_key, Map<String,Object> token_claim_map, Map<String, Object> header_map){
            return createJWT(header_map, TOKEN_BEGIN, TOKEN_TIMEOUT, TOKEN_AUDIENCE, TOKEN_ISSUSER, TOKEN_SUBJECT, token_key, token_claim_map, TOKEN_JWT_ID, TOKEN_KEY_ID);
        }
    
        public static String createJWT(String token_key, Map<String,Object> token_claim_map, Date token_begin, Date token_timeout){
            return createJWT(HEADER_MAP, token_begin, token_timeout, TOKEN_AUDIENCE, TOKEN_ISSUSER, TOKEN_SUBJECT, token_key, token_claim_map, TOKEN_JWT_ID, TOKEN_KEY_ID);
        }
    
        public static String createJWT(String token_key, Map<String,Object> token_claim_map, Date token_timeout, String token_audience, String token_issuser){
            return createJWT(HEADER_MAP, TOKEN_BEGIN, token_timeout, token_audience, token_issuser, TOKEN_SUBJECT, token_key, token_claim_map, TOKEN_JWT_ID, TOKEN_KEY_ID);
        }
    
        public static String createJWT(String token_key, Map<String,Object> token_claim_map, Date token_begin, Date token_timeout, String token_audience, String token_issuser, String token_subject){
            return createJWT(HEADER_MAP, token_begin, token_timeout, token_audience, token_issuser, token_subject, token_key, token_claim_map, TOKEN_JWT_ID, TOKEN_KEY_ID);
        }
    
    
        /**
         * 创建JWT
         * @param header_map 头部信息
         * @param token_begin 生效时间
         * @param token_timeout 到期时间
         * @param token_audience 接收主体
         * @param token_issuser 签发主体
         * @param token_subject 所有人
         * @param token_key 加密秘钥
         * @param token_claim_map 自定义声明
         * @param token_jwt_id 唯一标识
         * @param token_key_id key_id
         * @return jwt
         */
        public static String createJWT(Map<String, Object> header_map, Date token_begin, Date token_timeout,
                                         String token_audience, String token_issuser, String token_subject,
                                         String token_key, Map<String,Object> token_claim_map,
                                         String token_jwt_id, String token_key_id){
            String token;
            try {
                JWTCreator.Builder builder = JWT.create()
                        .withHeader(header_map) // 自定义头部
                        .withIssuedAt(new Date()) // 设置签发时间
                        .withNotBefore(token_begin) // 设置生效时间
                        .withExpiresAt(token_timeout) // 设置过期时间
                        .withAudience(token_audience) // 接收人
                        .withIssuer(token_issuser) // 签发人
                        .withSubject(token_subject) //JWT的所有人
                        .withJWTId(token_jwt_id) // JWT的唯一标识
                        .withKeyId(token_key_id); //
    
    //                .withClaim("","") // 自定义声明
    //                .withArrayClaim("",new String[]{}) //
                for (Map.Entry<String, Object> stringObjectEntry : token_claim_map.entrySet()) {
                    builder.withClaim(stringObjectEntry.getKey(), String.valueOf(stringObjectEntry.getValue()));
                }
                token = builder.sign(Algorithm.HMAC256(token_key));
            }catch (Exception e){
                e.printStackTrace();
                return "";
            }
            return token;
        }
    
        /**
         * 校验JWT
         * @param token 待校验的jwt
         * @param token_key 加密秘钥
         * @return boolean
         */
        public static Boolean verifyJWT(String token, String token_key){
            try {
                JWTVerifier verifier = JWT.require(Algorithm.HMAC256(token_key)).build();
                DecodedJWT jwt = verifier.verify(token);
                return true;
            }catch (Exception e){
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 获取签发对象
         */
        public static String getAudience(String token) {
            String audience;
            try {
                audience = JWT.decode(token).getAudience().get(0);
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
            return audience;
        }
    
        /**
         * 根据名称获取声明
         * @param token
         * @param name
         * @return
         */
        public static Claim getClaimByName(String token, String name){
            return JWT.decode(token).getClaim(name);
        }
    
        /**
         * 获取所有声明
         * @param token
         * @return
         */
        public static Map<String, Claim> getClaims(String token){
            return JWT.decode(token).getClaims();
        }
    
    
    
    }
    View Code

    b、拦截器

    就以前后端分离项目来说,我们需要配置一个拦截器,使所有请求都会经过后端Web服务的拦截器,然后获取接口访问请求中所携带的JWT,验证该JWT,通过则放行,失败则拦截请求。
    创建LoginHandler.java文件:
    import com.pkuokuo.jwtdemo.VO.JSONResultVo;
    import com.pkuokuo.jwtdemo.utils.JWTUtils;
    import com.pkuokuo.jwtdemo.utils.PassToken;
    import com.pkuokuo.jwtdemo.utils.Utils;
    import io.netty.util.internal.StringUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.lang.Nullable;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.lang.reflect.Method;
    
    /**
     * @author pkuoukuo
     * @date 2021/8/17 14:09
     * <功能简介>
     */
    @Component
    @Slf4j
    public class LoginHandler extends HandlerInterceptorAdapter {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String requestName = String.valueOf(handler);
            HttpSession session = request.getSession();
            log.warn("LoginHandler_requestName:"+requestName+";sessionId:"+session.getId());
    
            String token = request.getHeader("token");
            // 如果不是映射到方法直接通过
            if (!(handler instanceof HandlerMethod)) {
                log.info("不是映射到方法直接通过");
                return true;
            }
    
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            //检查是否有passtoken注释,有则跳过认证
            if (method.isAnnotationPresent(PassToken.class)) {
                PassToken passToken = method.getAnnotation(PassToken.class);
                if (passToken.required()) {
                    return true;
                }
            }
            else {
                // 默认全部检查
                log.info("进行jwt验证");
                if (StringUtil.isNullOrEmpty(token)){
                    log.error("token为空");
                    Utils.result(response, JSONResultVo.error("token为空"));
                    return false;
                }
                // 获取 token 中的 user Name
                String userId = JWTUtils.getAudience(token);
                log.info(userId);
    
                // 验证 jwt
                if (!JWTUtils.verifyJWT(token, "abcdefg")){
                    Utils.result(response, JSONResultVo.error("token失效"));
                    return false;
                }
                //获取载荷内容
                String phone = JWTUtils.getClaimByName(token, "phone").asString();
                log.info(phone);
            }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { /* compiled code */ }
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { /* compiled code */ }
        @Override
        public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /* compiled code */ }
    
    }
    View Code
    创建WebConfig.java文件:
    import com.pkuokuo.jwtdemo.handler.LoginHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    /**
     * @author pkuoukuo
     * @date 2021/8/17 14:07
     * <功能简介>
     */
    @Configuration
    public class WebConfig extends WebMvcConfigurationSupport {
        @Autowired
        private LoginHandler loginHandler;
    
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 如果配置项目名,则拦截项目后面的地址,
            //比如配置访问项目名地址为  springboot,则拦截的是"localhost:8080/springboot/hello" 后面的地址
            // 如果没有配置项目名,则拦截地址为 "localhost/hello" 后面的地址
    //        registry.addInterceptor(loginHandler).addPathPatterns( "/**").excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
            registry.addInterceptor(loginHandler).addPathPatterns( "/**");
    
        }
    
    }
    View Code

    2、非必须模块

    a、免验证注解

    可以自定义一个注解,在接口方法处声明,表示该接口免JWT验证,可以用于一些不需要验证登录状态的接口,比如登录、注册等。
    创建PassToken.java文件:
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author pkuoukuo
     * @date 2021/8/17 13:49
     * <功能简介>
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PassToken {
        boolean required() default true;
    }
    View Code

    b、redis

    这个主要是使JWT实现单点登录更加灵活,更加方便而引入的。使用场景有JWT失效等等。

    三、项目下载

     https://github.com/pk929/jwtdemo.git

    分享所感,如有侵权,请联系删除,可扫码关注微信公众号获取更多福利噢。
    (您的“打赏”将是我最大的写作动力!转载请注明出处.)

    关注微信公众号

  • 相关阅读:
    快速认识 Spring Boot 技术栈
    Spring JDBC Template -实验介绍
    Spring 基于注解的配置 -实验介绍
    Spring 自动扫描与装配 -实验介绍
    Spring IoC 容器 -实验介绍
    Spring 框架的 AOP -实验介绍
    使用 AspectJ 框架实现 Spring AOP
    Spring框架自动创建 proxy
    Spring 框架的 AOP
    考研计算机专业课基础:计算机结构化编程
  • 原文地址:https://www.cnblogs.com/pengpengdeyuan/p/15176080.html
Copyright © 2020-2023  润新知