日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

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

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

1. 簡介

1.1 JWT

JWT,即JSON Web Token,是一種用于在網絡上傳遞聲明的開放標準(RFC 7519)。JWT 可以在用戶和服務器之間傳遞安全可靠的信息,通常用于身份驗證和信息交換。

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

1.2 攔截器

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

使用場景

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

1.3 ThreadLocal

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

應用場景

  • 線程安全的數據傳遞:在多線程環境中,通過 ThreadLocal 可以輕松地將數據在方法調用間傳遞,而無需將數據作為參數傳遞。
  • 數據庫連接管理:在數據庫連接的管理中,可以使用 ThreadLocal 來存儲每個線程的數據庫連接,確保每個線程使用的是自己的連接。
  • 事務管理ThreadLocal 可以用于事務管理,確保事務的一致性。

注意:

  • ThreadLocal可以在虛擬線程環境下使用
  • ThreadLocal 應該謹慎使用,避免濫用。過多的使用可能導致代碼難以理解和維護。
  • 避免在 ThreadLocal 中存儲大對象,以防止內存泄漏。
  • 在使用線程池時,需要注意清理 ThreadLocal,以防止線程復用時出現數據污染。

2. 代碼實戰

2.1 引入依賴

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

2.2 獲取token的函數

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

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

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

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

// refresh-token過期時間
@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請求進來的時候將JWT解析的數據放進ThreadLocal,出去的時候需要將數據從ThreadLocal移除,否則會造成內存泄漏。

/**
 * <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內容的地方,使用以下代碼即可獲取對應的內容

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

3. 進階

3.1 結合自定義注解實現對方法的鑒權

角色枚舉類

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>
 * 自定義校驗注解
 * </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;

    /**
     * 是否強制校驗
     * @return
     */
    boolean required() default true;

    /**
     * 校驗不通過時的報錯信息
     * @return
     */
    String message() default "權限不足!";

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

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

自定義注解實現類

/**
 * <p>
 * 自定義注解實現
 * </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);
    }

    /**
     * 判斷當前是否有權限
     * @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());
}

注解會從ThreadLocal獲取當前用戶的類型,然后進行比對,當然你的判斷邏輯可以比這個更加復雜,只需要符合業務實現就好了。

4. 注意

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

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。