使用 spring 攔截器和自定義注解進(jìn)行登錄攔截

為何要用自定義注解

有些方法我們想要它只能被特定的用戶訪問到,比如用戶登錄之后才能訪問。spring 的攔截器可以配置攔截的路由,但在 restful 風(fēng)格的路由中,往往有重復(fù)的,根據(jù) http method 來(lái)指定功能,這樣子的話直接配置攔截器路由規(guī)則也不太方便。所以我們可以自定義一個(gè)注解,將它用在需要登錄的方法中,然后在攔截器中判斷要訪問的方法是否有我們自定義的注解,如果有就判斷當(dāng)前用戶是否登錄了(判斷是否攜帶了登錄之后獲取到的 token ),從而決定是否攔截。

編寫一個(gè)自定義注解

這篇文章新增的文件如下


目錄結(jié)構(gòu)

新增 LoginRequired.java

/**
 * 在需要登錄驗(yàn)證的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

ElementType.MeTHOD 表示該自定義注解可以用在方法上
RetentionPolicy.RUNTIME 表示該注解在代碼運(yùn)行時(shí)起作用

編寫登錄攔截器

新增 AuthenticationInterceptor.java

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;

    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        // 如果不是映射到方法直接通過
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        // 判斷接口是否需要登錄
        LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class);
        // 有 @LoginRequired 注解,需要認(rèn)證
        if (methodAnnotation != null) {
            // 執(zhí)行認(rèn)證
            String token = request.getHeader("token");  // 從 http 請(qǐng)求頭中取出 token
            if (token == null) {
                throw new RuntimeException("無(wú)token,請(qǐng)重新登錄");
            }
            int userId;
            try {
                userId = Integer.parseInt(JWT.decode(token).getAudience().get(0));  // 獲取 token 中的 user id
            } catch (JWTDecodeException e) {
                throw new RuntimeException("token無(wú)效,請(qǐng)重新登錄");
            }
            User user = userService.findById(userId);
            if (user == null) {
                throw new RuntimeException("用戶不存在,請(qǐng)重新登錄");
            }
            // 驗(yàn)證 token
            try {
                JWTVerifier verifier =  JWT.require(Algorithm.HMAC256(user.getPassword())).build();
                try {
                    verifier.verify(token);
                } catch (JWTVerificationException e) {
                    throw new RuntimeException("token無(wú)效,請(qǐng)重新登錄");
                }
            } catch (UnsupportedEncodingException ignore) {}
            request.setAttribute("currentUser", user);
            return true;
        }
        return true;
    }

token 的驗(yàn)證過程和 token 的生成過程有關(guān),在用戶登錄接口中,我使用的是用戶的密碼左右 token 的密鑰進(jìn)行加密(因?yàn)榉?wù)器并沒有對(duì) token 進(jìn)行存儲(chǔ),所以加密的密鑰最好是一個(gè)用戶更改密碼之后會(huì)變的東西,我就直接用密碼了),還將 user id 存到了 JWT token 的 audience 中,因此我們能夠從 token 中知道用戶是誰(shuí)。具體的JWT token 的生成和驗(yàn)證過程可以看看我們項(xiàng)目中使用的 jar 包的文檔

配置攔截器

spring boot 有很多默認(rèn)配置,如果要添加攔截器之類的,就繼承 WebMvcConfigurerAdapter 類,Override 相應(yīng)的方法,來(lái)看看怎么添加我們剛剛編寫好的攔截器

新增 WebMvcConfigurer.java

@Configuration
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    // 攔截所有請(qǐng)求,通過判斷是否有 @LoginRequired 注解 決定是否需要登錄
        super.addInterceptors(registry);
    }

    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        return new AuthenticationInterceptor();
    }
}

測(cè)試

在 userApi.java 里面添加一個(gè)臨時(shí)用的測(cè)試方法

@GetMapping("/test")
public Object testLogin() {
    return "success";
}

重啟項(xiàng)目
訪問 /api/user/test


不需要登錄

正常返回 “success” 字符串。現(xiàn)在給 testLogin 方法加上自定義的 @LoginRequired 注解

@LoginRequired
@GetMapping("/test")
public Object testLogin() {
    return "success";
}

重啟項(xiàng)目,再次訪問 /api/use/test

無(wú)token

請(qǐng)求被登錄攔截器攔截了,攔截器拋出異常,由全局異常處理返回了錯(cuò)誤信息。
怎樣添加 token 呢?訪問登錄接口,復(fù)制返回的token,將它添加到 header 中
有 token

返回 success,請(qǐng)求成功。測(cè)試完畢,將臨時(shí)添加的測(cè)試方法刪掉吧。

查看項(xiàng)目完整代碼

項(xiàng)目地址: https://github.com/hyrijk/spring-boot-blog
克隆項(xiàng)目到本地

git clone https://github.com/hyrijk/spring-boot-blog.git

checkout 到當(dāng)前版本

git checkout b7498954eba034b82b3619a3f07b62f48d390eb0

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,694評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,285評(píng)論 6 342
  • 標(biāo)記-清除算法(Mark-Sweep)1、標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象2、在標(biāo)記...
    瘋狂的喵喵閱讀 433評(píng)論 0 6
  • 姑蘇,細(xì)雨,瘦馬,黑衣,執(zhí)劍,相望。 劍鳴泣,風(fēng)落雨,雙劍起,瞬分離,白紗去,驚素顏,卿羞赧,隱無(wú)蹤。 竹林細(xì)溪,...
    魏幺九閱讀 1,206評(píng)論 33 7
  • 什么是記憶? 用心理學(xué)專業(yè)的話來(lái)說(shuō),記憶是過去經(jīng)驗(yàn)在頭腦里的反映。 具體來(lái)說(shuō),我們過去對(duì)事物的感知、對(duì)問題的思考、...
    叫我哆啦美閱讀 1,225評(píng)論 1 6

友情鏈接更多精彩內(nèi)容