<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                spring中的攔截器與前面學習過的過濾器大同小異。它們都可以在數據被轉發到控制器前獲取數據。不同的是過濾器的功能是改變得到的數據從而完成一定的功能,而攔截器的功能體現在監視數據上,一旦發被被監視的數據不合規,攔截器可以直接終止數據的轉發并自定義返回的數據格式。在spring中,過濾器與攔截器都是支持多個的。在配置了多個過濾器與攔截器的項目中,數據首先依次通過過濾器,然后在依次通過攔截器。 >[info] 某些功能即可以使用攔截器來實現又可以使用過濾器來實現,這并沒有一定的界線。比如對將前臺的請求依次寫入日志,無論是攔截器還是過濾器都能夠滿足功能需求。但往往在對數據進行變更時使用過濾器,在對數據進行監視時使用攔截器。 # 初始化 攔截器的使用方法與過濾器基本一致。新建過濾器時直接繼承了HttpFilter,新建攔截器時直接繼承HandlerInterceptorAdapter就好。 interceptor/AuthInterceptor.java ```java package com.mengyunzhi.springbootstudy.interceptor; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /** * 認證攔截器 * @author panjie */ public class AuthInterceptor extends HandlerInterceptorAdapter { } ``` Spring的攔截器提供了4種方法,4種方法執行在各個階段。比較常用的是preHandle方法及postHandler方法,它們一個運行在控制器被執行前,另一個運行在控制器被執行后。 interceptor/AuthInterceptor.java ```java @Component ? public class AuthInterceptor extends HandlerInterceptorAdapter { private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("執行攔截器preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { logger.info("執行攔截器postHandle"); } } ``` * ? 使用Component進行注解,以便在項目中可以使用@Autowired注入 ## 添加攔截器 與添加過濾器的方法有所不同,添加攔截器的方法稍微的復雜一些。使攔截器生效的方法是打開`config/WebConfig.java`去覆寫`addInterceptors`方法: config/WebConfig.java ```java public class WebConfig implements WebMvcConfigurer { @Autowired AuthInterceptor authInterceptor; ? @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(this.authInterceptor?); } ``` * ? 由于AuthInterceptor使用了@Component,所以此時可以使用@Autowired進行注入。 * ? 將自定義的攔截器使用registry.addInterceptor方法添加到系統中,spring啟動的過程中會自動的執行該方法以完成向項目中添加攔截器的目標。 ## 測試 啟動后臺并嘗試訪問/Teacher/me接口,得到日志如下: ``` 2020-02-19 17:38:42.926 INFO 27620 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 獲取到的token為8f03f582-4284-4b4e-85f2-258f30f016c0 ? 2020-02-19 17:38:42.926 INFO 27620 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 原token無效,發布的新的token為8e79419b-f632-4e4a-966b-6353f5647458 ? 2020-02-19 17:38:42.929 INFO 27620 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 在控制器被調用以前執行 ? 2020-02-19 17:38:42.933 DEBUG 27620 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/Teacher/me", parameters={} ? 2020-02-19 17:38:42.937 DEBUG 27620 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public com.mengyunzhi.springbootstudy.entity.Teacher com.mengyunzhi.springbootstudy.controller.TeacherController.me() ? 2020-02-19 17:38:42.938 INFO 27620 --- [nio-8080-exec-1] c.m.s.interceptor.AuthInterceptor : 執行攔截器preHandle ? 2020-02-19 17:38:42.991 DEBUG 27620 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json] ? 2020-02-19 17:38:42.992 DEBUG 27620 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Nothing to write: null body ? 2020-02-19 17:38:42.992 INFO 27620 --- [nio-8080-exec-1] c.m.s.interceptor.AuthInterceptor : 執行攔截器postHandle ? 2020-02-19 17:38:42.992 DEBUG 27620 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK ? 2020-02-19 17:38:42.994 INFO 27620 --- [nio-8080-exec-1] c.m.springbootstudy.filter.TokenFilter : 在控制器被調用以后執行 ? ``` * ? 執行過濾器的前置部分 * ? 執行spring內部路由 * ? 執行攔截器的preHandle方法 * ? spring處理控制器的返回值 * ? 執行攔截器的postHandle * ? 完成響應,此后對響應的變更已經于事無補 * ? 執行過濾器的后置部分 所以大膽的猜測帶有多攔截器與過濾器的spring項目大概長這個樣子: ![](https://img.kancloud.cn/00/4f/004fa1e0b428e0b249ccee544541191f_1429x733.png) >[info] 此圖也解釋了在學習過濾器小節時遇到的于chain.doFilter之后改變reponse的值未生效的問題。 # 攔截請求 實現對用戶進行認證的功能適用于preHandle方法。 ![](https://img.kancloud.cn/5f/4f/5f4f8f1ed6e56464121ff61ed6b7274e_925x674.png) 如圖所示:在preHandle方法中判斷用戶的每次請求是否是被允許,如果允許則不攔截,如果不允許則進行攔截。 interceptor/AuthInterceptor.java ```java @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取請求地址及請求方法 // 判斷請求地址、方法是否與用戶登錄相同 // auth-token是否綁定了用戶 // 為響應加入提示:用戶未登錄 return true; } ``` ## 獲取請求地址及方法 獲取請求地址調用HttpServletRequest的getRequestURI方法,獲取請求方法調用HttpServletRequest的getMethod方法。 interceptor/AuthInterceptor.java ```java public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取請求地址及請求方法 String url = request.getRequestURI(); String method = request.getMethod(); // 判斷請求地址、方法是否與用戶登錄相同 if ("/Teacher/login".equals(url) && "POST".equals(method)) { return true; } ``` ## 判斷用戶是否登錄 在TeacherServiceImpl的me方法中已經處理過用戶是否登錄的邏輯:即判斷`TeacherServiceImpl`中的`authTokenTeacherIdHashMap`中是否存在以`authToken`為`key`的`teacherId`。如果存在則說此`authToken`已經與相應的`teacherId`進行綁定,從而得出攜帶有`authToken`的請求是經過用戶認證的。`authTokenTeacherIdHashMap`做為私有屬性存在于基于`TeacherServiceImpl`類實現的對象中: service/TeacherServiceImpl.java ```java public class TeacherServiceImpl implements TeacherService { ... private HashMap<String, Long> authTokenTeacherIdHashMap = new HashMap<>(); } ``` 實現讀取該authTokenTeacherIdHashMap的方法不止一種。 第一種方法:使用`public static`將`authTokenTeacherIdHashMap`聲明為靜態公有屬性,從而使其由對方的屬性上升到類的屬性。此后便可以在其它的方法中調用:`TeacherServiceImpl.authTokenTeacherIdHashMap.get(authToken)`來判斷某個`authToken`是否綁定了`teacherId`。但此方法違背了面向接口編程的思想。如果在攔截器中直接使用`TeacherServiceImpl`,則攔截器直接對`TeacherServiceImpl`這個實現類產生依賴,而非`TeacherService`接口。 第二種方法:在TeacherService中建立`boolean isLogin(String authToken);`方法,然后在實現類`TeacherServiceImpl`中實現這個方法。最后在攔截器中注入`TeacherService`。這與教程中的其它實現保持了統一。同時由于攔截器中聲明的依賴為`TeacherService`,從而符合了面向接口開發的思想。 >[info] 面向接口開發是一種思想,一種編程的規范。在此不必在意為什么要這么做,以及實際是怎么做的。簡簡單單有個了解就好。 service/TeacherService.java ```java /** * 判斷用戶是否登錄 * @param authToken 認證令牌 * @return */ boolean isLogin(String authToken); ``` service/TeacherServiceImpl.java ```java @Override public boolean isLogin(String authToken) { // 獲取authToken映射的teacherId Long teacherId = this.authTokenTeacherIdHashMap.get(authToken); return teacherId != null; } ``` 在攔截器中注入相應的TeacherService服務并調isLogin方法完成用戶是否登錄的校驗。 interceptor/AuthInterceptor.java ```java private TeacherService teacherService; @Autowired public AuthInterceptor(TeacherService teacherService) { this.teacherService = teacherService; } ... // 判斷請求地址、方法是否與用戶登錄相同 if ("/Teacher/login".equals(url) && "POST".equals(method)) { return true; } // auth-token是否綁定了用戶 String authToken = request.getHeader(TokenFilter.TOKEN_KEY); if (this.teacherService.isLogin(authToken)) { return true; } ``` ## 在響應中加入提示:用戶未登錄 用戶未登錄的提醒通常使用設置http狀態碼的方法來通知請求者。前面已經接觸了幾個常見的狀態碼,比如查詢信息成功時對應的狀態碼為200;創建數據成功時返回的狀態為201;更新或刪除數據成功時對應的狀態為204;請求的映射未找到的時對應的狀態碼為404;請求映射找到了但請求方法不匹配時對應的狀態碼為405;在進行參數綁定發生錯誤時對應的狀態碼為400。用戶未登錄時應向請求者發送401狀態碼。 interceptor/AuthInterceptor.java ```java // auth-token是否綁定了用戶 String authToken = request.getHeader(TokenFilter.TOKEN_KEY); if (this.teacherService.isLogin(authToken)) { return true; } // 為響應加入提示:用戶未登錄 response.setStatus(401); ? return false; ? } ``` * ? 返回狀態碼401,表示當前請求只對認證用戶開放。但當前接收到的authToken未找到對應的認證用戶。 * ? 返回false,程序至此中斷不再向下進行轉發,將設置了狀態碼的response發送給請求者。 # 測試 在使用http request進行測試中,加入以下日志信息以更好的觀察者其它過程: interceptor/AuthInterceptor.java ```java public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 獲取請求地址及請求方法 String url = request.getRequestURI(); String method = request.getMethod(); System.out.println(("請求的地址為" + url + "請求的方法為:" + method); // 判斷請求地址、方法是否與用戶登錄相同 if ("/Teacher/login".equals(url) && "POST".equals(method)) { System.out.println(("請求地址方法匹配到登錄地址,不攔截"); return true; } // auth-token是否綁定了用戶 String authToken = request.getHeader(TokenFilter.TOKEN_KEY); if (this.teacherService.isLogin(authToken)) { System.out.println(("當前token已綁定登錄用戶,不攔截"); return true; } System.out.println(("當前token未綁定登錄用戶,返回401"); // 為響應加入提示:用戶未登錄 response.setStatus(401); return false; } ``` controller/TeacherController.java ```java @GetMapping("me") public Teacher me() { System.out.println("用戶成功的請求了me方法"); return this.teacherService.me(); } ``` ## 未登錄請求ME 未登錄請求非login方法時,應該返回401. ``` GET http://localhost:8080/Teacher/me HTTP/1.1 401 auth-token: 832787ed-597e-4f35-8702-562e7a83bf68 Content-Length: 0 Date: Thu, 20 Feb 2020 03:07:29 GMT <Response body is empty> Response code: 401?; Time: 122ms; Content length: 0 bytes ``` * ? 狀態碼為401,說明攔截成功 日志信息如下: ``` 請求的地址為/Teacher/me請求的方法為:GET 當前token未綁定登錄用戶,返回401 ``` 未觸發TeacherController中的me方法,近一步說明攔截成功。 ## 用戶登錄 有效用戶登錄 ``` POST http://localhost:8080/Teacher/login Content-type: application/json; {"username": "zhaokaiqiang", "password":"yunzhi"} ``` 響應: ``` POST http://localhost:8080/Teacher/login HTTP/1.1 200 auth-token: d3bf3d7f-0663-4258-9c82-025073602181 ★ Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Thu, 20 Feb 2020 03:15:25 GMT true ? Response code: 200?; Time: 29ms; Content length: 4 bytes ``` * ? 返回了true,說明正常執行了login方法。攔截器未攔截 * ? 返回狀態碼200 * ★ 記錄該成功認證的token,備用 日志信息進一步說明攔截工作成功 ``` 請求的地址為/Teacher/login請求的方法為:POST 請求地址方法匹配到登錄地址,不攔截 ``` 無效用戶登錄 ``` POST http://localhost:8080/Teacher/login Content-type: application/json; {"username": "test", "password":"yunzhi"} ``` 響應信息: ``` POST http://localhost:8080/Teacher/login HTTP/1.1 200 auth-token: 819943cb-b34a-4bfa-bf74-1ca18c95585c Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Thu, 20 Feb 2020 03:18:43 GMT false Response code: 200; Time: 19ms; Content length: 5 bytes ``` ## 使用成功登錄的authToken訪問me 使用登錄成功后臺返回的authToken★加入header信息。 ``` GET http://localhost:8080/Teacher/me auth-token: d3bf3d7f-0663-4258-9c82-025073602181 ``` 響應信息: ``` GET http://localhost:8080/Teacher/me HTTP/1.1 200 auth-token: d3bf3d7f-0663-4258-9c82-025073602181 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Thu, 20 Feb 2020 03:22:08 GMT { "id": 1, "name": null, "sex": true, "username": "zhaokaiqiang", "email": null, "createTime": null, "updateTime": null, "password": "yunzhi" } Response code: 200; Time: 193ms; Content length: 126 bytes ``` 使用已認證的authToken訪問后臺,成功的訪問了me方法并返回了正常信息。日志如下: ``` 請求的地址為/Teacher/me請求的方法為:GET 當前token已綁定登錄用戶,不攔截 用戶成功的請求了me方法 ``` 近一步得到了驗證。 至此,有一個擁有認證功能的攔截器便成功的被完成了。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.8](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.8) | - | | spring-mvc-handlerinterceptor | [https://www.baeldung.com/spring-mvc-handlerinterceptor](https://www.baeldung.com/spring-mvc-handlerinterceptor) | - | | HttpServletRequest | [https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html) | - |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看