日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

使用JWT、拦截器与ThreadLocal实现在任意位置获取Token中的信息,并结合自定义注解实现对方法的鉴权

發(fā)布時(shí)間:2023/11/16 107 coder
生活随笔 收集整理的這篇文章主要介紹了 使用JWT、拦截器与ThreadLocal实现在任意位置获取Token中的信息,并结合自定义注解实现对方法的鉴权 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 簡介

1.1 JWT

JWT,即JSON Web Token,是一種用于在網(wǎng)絡(luò)上傳遞聲明的開放標(biāo)準(zhǔn)(RFC 7519)。JWT 可以在用戶和服務(wù)器之間傳遞安全可靠的信息,通常用于身份驗(yàn)證和信息交換。

  1. 聲明(Claims): JWT 包含一組稱為聲明的信息,聲明描述了一些數(shù)據(jù)。有三種類型的聲明:
    • 注冊聲明(Registered Claims):這是一些預(yù)定義的聲明,包括標(biāo)準(zhǔn)的聲明,例如"iss"(簽發(fā)者)、"sub"(主題)和"exp"(過期時(shí)間)等。
    • 公共聲明(Public Claims):這些聲明是由使用 JWT 的雙方定義的,并且必須遵守一定的規(guī)定,以防止沖突。
    • 私有聲明(Private Claims):這些是自定義的聲明,用于在雙方之間共享信息。
  2. 編碼結(jié)構(gòu): JWT 由三部分組成,使用點(diǎn)號(hào)(.)分隔開:
    • Header(頭部):包含了令牌的元數(shù)據(jù),例如算法和令牌類型。
    • Payload(載荷):包含了聲明,即實(shí)際傳輸?shù)臄?shù)據(jù)。
    • Signature(簽名):用于驗(yàn)證發(fā)送方的身份以及確保消息的完整性。簽名由前兩部分的內(nèi)容和密鑰組成,以防篡改。
  3. 編碼方法: JWT 可以使用不同的編碼方法,包括:
    • Base64 URL encoding:用于編碼頭部和載荷。
    • HMACSHA256:用于生成簽名,以確保消息完整性。
  4. 使用場景
    • 身份驗(yàn)證:用戶登錄后,服務(wù)器生成一個(gè)包含用戶信息的JWT,并將其發(fā)送給客戶端。客戶端在后續(xù)請求中將JWT包含在請求頭中,服務(wù)器驗(yàn)證JWT以確保請求的合法性。
    • 信息交換:JWT還可以包含其他信息,例如用戶的角色、訪問權(quán)限等,這些信息可以在不需要查詢數(shù)據(jù)庫的情況下進(jìn)行快速訪問。

1.2 攔截器

攔截器(Interceptor)是一種用于處理請求的機(jī)制,可以讓你在請求的處理過程中進(jìn)行預(yù)處理和后處理。攔截器類似于過濾器(Filter),但相比過濾器,攔截器更加專注于處理控制器層面的請求,可以對處理器的執(zhí)行過程進(jìn)行更加細(xì)粒度的控制。

使用場景

  • 身份驗(yàn)證和授權(quán):攔截器可以用于檢查用戶是否已經(jīng)登錄,是否具有足夠的權(quán)限訪問某個(gè)資源。
  • 日志記錄:攔截器可以用于記錄請求和響應(yīng)的日志信息,方便調(diào)試和監(jiān)控。
  • 性能監(jiān)控:通過攔截器,你可以記錄請求的處理時(shí)間,幫助進(jìn)行性能監(jiān)控。
  • 執(zhí)行順序:攔截器可以配置多個(gè),它們的執(zhí)行順序由配置時(shí)的順序決定。
  • 異步請求:攔截器也能處理異步請求,需要實(shí)現(xiàn)AsyncHandlerInterceptor接口。

1.3 ThreadLocal

ThreadLocal 是 Java 中的一個(gè)類,主要用于提供了線程本地變量。 ThreadLocal 創(chuàng)建的變量只能被當(dāng)前線程訪問,其他線程無法直接訪問或修改它。ThreadLocal 主要用于保持線程封閉性,即每個(gè)線程都擁有自己獨(dú)立的變量副本,不同線程之間不會(huì)相互干擾。

應(yīng)用場景

  • 線程安全的數(shù)據(jù)傳遞:在多線程環(huán)境中,通過 ThreadLocal 可以輕松地將數(shù)據(jù)在方法調(diào)用間傳遞,而無需將數(shù)據(jù)作為參數(shù)傳遞。
  • 數(shù)據(jù)庫連接管理:在數(shù)據(jù)庫連接的管理中,可以使用 ThreadLocal 來存儲(chǔ)每個(gè)線程的數(shù)據(jù)庫連接,確保每個(gè)線程使用的是自己的連接。
  • 事務(wù)管理ThreadLocal 可以用于事務(wù)管理,確保事務(wù)的一致性。

注意:

  • ThreadLocal可以在虛擬線程環(huán)境下使用
  • ThreadLocal 應(yīng)該謹(jǐn)慎使用,避免濫用。過多的使用可能導(dǎo)致代碼難以理解和維護(hù)。
  • 避免在 ThreadLocal 中存儲(chǔ)大對象,以防止內(nèi)存泄漏。
  • 在使用線程池時(shí),需要注意清理 ThreadLocal,以防止線程復(fù)用時(shí)出現(xiàn)數(shù)據(jù)污染。

2. 代碼實(shí)戰(zhàn)

2.1 引入依賴

<!-- java-jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>${jwt.version}</version>
</dependency>

2.2 獲取token的函數(shù)

推薦使用@Value的方式從application.yml中獲取密鑰和過期時(shí)間

// refresh-token密鑰
@Value("${refresh-token.secret}")
private String REFRESH_TOKEN_SECRET;

// refresh-token過期時(shí)間
@Value("${refresh-token.expire-time}")
private int REFRESH_TOKEN_EXPIRE_TIME;

// refresh-token密鑰
@Value("${access-token.secret}")
private String ACCESS_TOKEN_SECRET;

// refresh-token過期時(shí)間
@Value("${access-token.expire-time}")
private int ACCESS_TOKEN_EXPIRE_TIME;

/**
 * 獲取access_token
 * @param userId
 * @param userType
 * @return
 */
private String getAccessToken(int userId, int userType){
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.HOUR, ACCESS_TOKEN_EXPIRE_TIME);
    return JWT.create()
            .withClaim("userId", userId)
            .withClaim("userType", userType)
            .withExpiresAt(calendar.getTime())
            .sign(Algorithm.HMAC512(ACCESS_TOKEN_SECRET));
}

/**
 * 獲取refresh_token
 * @param userId
 * @param userType
 * @return
 */
private String getRefreshToken(int userId, int userType){
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.HOUR, REFRESH_TOKEN_EXPIRE_TIME);
    return JWT.create()
            .withClaim("userId", userId)
            .withClaim("userType", userType)
            .withExpiresAt(calendar.getTime())
            .sign(Algorithm.HMAC512(REFRESH_TOKEN_SECRET));
}

2.3 ThreadLocal工具類

/**
 * <p>
 * 用戶SessionVO
 * </p>
 *
 * @author jonil
 * @since 2023/11/10 16:03
 */
@Data
public class UserSessionVO {

    // 用戶id
    Integer userId;
    // 用戶類型
    Integer userType;
}

/**
 * <p>
 * 用戶session上下文
 * </p>
 *
 * @author jonil
 * @since 2023/11/10 16:02
 */
public class UserSessionContext {
    private static ThreadLocal<UserSessionVO> userSessionVOThreadLocal = new ThreadLocal<>();

    public static void set(UserSessionVO userSessionVO) {
        userSessionVOThreadLocal.set(userSessionVO);
    }

    public static UserSessionVO get() {
        return userSessionVOThreadLocal.get();
    }

    public static void remove() {
        userSessionVOThreadLocal.remove();
    }
}

2.4 攔截器

此處攔截器為處理HTTP請求 各階段需要做的操作。HTTP請求進(jìn)來的時(shí)候?qū)WT解析的數(shù)據(jù)放進(jìn)ThreadLocal,出去的時(shí)候需要將數(shù)據(jù)從ThreadLocal移除,否則會(huì)造成內(nèi)存泄漏。

/**
 * <p>
 * JWT攔截器
 * </p>
 *
 * @author jonil
 * @since 2023/11/10 15:55
 */
public class JWTInterceptor implements HandlerInterceptor {

    // refresh-token密鑰
    @Value("${access-token.secret}")
    private String ACCESS_TOKEN_SECRET;

    /**
     * 將用戶基本信息添加到ThreadLocal
     * @param request
     * @param response
     * @param handler
     * @return
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String accessToken = request.getHeader("Authorization");
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC512(ACCESS_TOKEN_SECRET)).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(accessToken);

        Integer userId = decodedJWT.getClaim("userId").asInt();
        Integer userType = decodedJWT.getClaim("userType").asInt();

        UserSessionVO userSessionVO = new UserSessionVO();
        userSessionVO.setUserId(userId);
        userSessionVO.setUserType(userType);
        UserSessionContext.set(userSessionVO);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) {

    }

    /**
     * 將用戶的基本信息從ThreadLocal移除
     * @param request
     * @param response
     * @param handler
     * @param ex
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) {
        UserSessionContext.remove();
    }
}

2.5 使用

在需要用到JWT內(nèi)容的地方,使用以下代碼即可獲取對應(yīng)的內(nèi)容

UserSessionVO userSessionVO = UserSessionContext.get();
Integer userId = userSessionVO.getUserId();

3. 進(jìn)階

3.1 結(jié)合自定義注解實(shí)現(xiàn)對方法的鑒權(quán)

角色枚舉類

public enum UserType {

    systemAdmin(0),
    userAdmin(1),
    maintenancePersonnel(2),
    vipUser(3),
    user(4),
    none(5);

    UserType(int code) {
        this.code = code;
    }

    private int code;

    public int getCode() {
        return code;
    }
}

自定義注解類

/**
 * <p>
 * 自定義校驗(yàn)注解
 * </p>
 *
 * @author jonil
 * @since 2023/11/13 20:05
 */
@Documented
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {PermissionValidator.class})
public @interface Permission {

    UserType type() default UserType.none;

    /**
     * 是否強(qiáng)制校驗(yàn)
     * @return
     */
    boolean required() default true;

    /**
     * 校驗(yàn)不通過時(shí)的報(bào)錯(cuò)信息
     * @return
     */
    String message() default "權(quán)限不足!";

    /**
     * 分組
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * bean的負(fù)載
     * @return
     */
    Class<? extends Payload>[] payload() default {};
}

自定義注解實(shí)現(xiàn)類

/**
 * <p>
 * 自定義注解實(shí)現(xiàn)
 * </p>
 *
 * @author jonil
 * @since 2023/11/13 20:05
 */
public class PermissionValidator implements ConstraintValidator<Permission, UserType> {

    @Override
    public void initialize(Permission constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    /**
     * 判斷當(dāng)前是否有權(quán)限
     * @param userType object to validate
     * @param context context in which the constraint is evaluated
     *
     * @return
     */
    @Override
    public boolean isValid(UserType userType, ConstraintValidatorContext context) {
        return UserSessionContext.get().getUserType() <= userType.getCode();
    }
}

使用

/**
 * 獲取信息接口
 */
@Permission(type = UserType.user)
@GetMapping("/info")
public R getInfo() {
    return R.success(service.getInfo());
}

注解會(huì)從ThreadLocal獲取當(dāng)前用戶的類型,然后進(jìn)行比對,當(dāng)然你的判斷邏輯可以比這個(gè)更加復(fù)雜,只需要符合業(yè)務(wù)實(shí)現(xiàn)就好了。

4. 注意

倘若你是在微服務(wù)環(huán)境或分布式環(huán)境下使用這一套邏輯,需要注意在網(wǎng)關(guān)(流量網(wǎng)關(guān)、業(yè)務(wù)網(wǎng)關(guān))和OpenFeign中攜帶原生的Header,否則獲取不到該用戶的信息。

總結(jié)

以上是生活随笔為你收集整理的使用JWT、拦截器与ThreadLocal实现在任意位置获取Token中的信息,并结合自定义注解实现对方法的鉴权的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。