1. 程式人生 > 實用技巧 >SpringBoot整合JWT實現許可權驗證(個人技術部落格)

SpringBoot整合JWT實現許可權驗證(個人技術部落格)

一、技術概述

Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準((RFC 7519).定義了一種簡潔的,自包含的方法用於通訊雙方之間以JSON物件的形式安全的傳遞資訊。由於完成的專案需要進行不同身份使用者的訪問許可權驗證,因此採用的JWT實現驗證。

二、技術詳述

總體思路:

在專案中的設計的思路如下圖所示,首先是在登入驗證的函式介面不設定許可權(具體許可權怎麼設定下文會講),經過驗證後將資料返回前端(本專案業務需要這裡返回的是使用者檢視物件-UserVO,資料型別採用JSON資料型別,在賬戶資料中添加了token欄位儲存),前端在獲取資料後將token進行儲存,在後續的訪問中將token加入訪問的請求頭中,經過解析token驗證使用者訪問是否滿足訪問函式要求的許可權,決定是否拒絕訪問請求。

public class UserVO{

    private User user;
    private AccountData accountData;
    private String token;

}

TokenService是根據使用者資訊(這裡是通過id和password)動態生成token,加密演算法是HMAC256,這樣可以用於後續如果兩個使用者同時異地登入但是一端在使用過程中修改密碼,另一端也被迫中止訪問的業務需求。讀者可以根據具體情況設計自己需要的token生成方法。

@Service("TokenService")
public class TokenService {
    public String getToken(User user) {
        String token="";
        token= JWT.create().withAudience(user.getId().toString())//將user.id儲存到token裡面
                .sign(Algorithm.HMAC256(user.getPassword()));//以password作為 token 的金鑰
        return token;
    }
}

TokenService中的JWT是通過匯入com.auth0.jwt.JWT;包引入的類,maven依賴如下

<dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.4.0</version>
</dependency>

為訪問介面新增許可權

這裡通過自定義註解的方式為訪問類/方法新增許可權,下面這個是我自定義的一個管理員許可權的註解,@Target的內容可以修改,修改後可以更改許可權新增的位置(是類還是方法等等)

/**
 * 需要管理員許可權的註釋
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AdminLimit {
    boolean required() default true;
}

我這裡的是許可權設定的是方法,我把註釋放在了controller的訪問介面上面,這裡添加了兩個註解分別對應了後面註解的許可權,在前端訪問該介面的時候會經過過濾器驗證,過濾器在下面會給出。

//管理員介面獲取獎勵申請記錄列表(具體實現)
@LoginToken//需要登入
@AdminLimit//管理員許可權
@GetMapping("/rewards")
public @ResponseBody List<RewardVO> getRewards(){
    return rewardService.getRewardList();
}

許可權驗證過濾器

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;
    @Autowired
    private TokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        String token = request.getHeader("token");// 從 http 請求頭中取出 token
        User user = null;//登入使用者

        // 如果不是對映到方法直接通過
        if(!(object instanceof HandlerMethod)){
            return true;
        }

        HandlerMethod handlerMethod=(HandlerMethod)object;
        Method method=handlerMethod.getMethod();
        //檢查是否有passToken註釋,有則跳過認證
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }

        //判斷是否需要登入許可權
        if(method.isAnnotationPresent(LoginToken.class)){
            LoginToken userLoginToken = method.getAnnotation(LoginToken.class);
            if (userLoginToken.required()) {
                //執行認證
                if(token == null){
                    //未登入使用者
                    //設定響應狀態碼
                    response.setStatus(ErrorStatus.NOT_LOGGED_IN);
                    //停止後續訪問
                    return false;
                }else{//有token
                    String userId;
                    try {//根據token獲取uid
                        userId = JWT.decode(token).getAudience().get(0);
                    } catch (JWTDecodeException j) {
                        //錯誤的token
                        response.setStatus(ErrorStatus.BAD_TOKEN);
                        return false;
                    }

                    user = userService.getUserById(Integer.parseInt(userId));
                    if(user == null){
                        //使用者不存在
                        //設定響應狀態碼
                        response.setStatus(ErrorStatus.ACCOUNT_NOT_EXIT);
                        //終止後續訪問
                        return false;
                    }else{
                        String token2 = tokenService.getToken(user);
                        if(!token2.equals((token))){
                            response.setStatus(ErrorStatus.PASSWORD_ERROR);
                            return false;
                        }
                    }
                }
            }
        }


        //檢查是否需要管理員許可權
        if(method.isAnnotationPresent(AdminLimit.class)){
            if(!user.getIdentity().equals(UserIdentity.admin)){//沒有管理員許可權
                //設定響應狀態碼
                response.setStatus(ErrorStatus.BEYOND_IDENTITY_LIMIT);
                //終止後續訪問
                return false;
            }
        }
        if(method.isAnnotationPresent(UserLimit.class)){//需要普通使用者許可權
            if(!user.getIdentity().equals(UserIdentity.student)
                    && !user.getIdentity().equals(UserIdentity.teacher)){
                //既不是老師也不是學生身份
                //設定響應狀態碼
                response.setStatus(ErrorStatus.BEYOND_IDENTITY_LIMIT);
                //終止後續訪問
                return false;
            }
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {

    }

}

配置攔截器

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 攔截所有請求,通過判斷是否有 @LoginRequired 註解 決定是否需要登入
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}