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

posted @ 2021-08-23 16:04  砰砰的猿  阅读(473)  评论(0编辑  收藏  举报