<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>

                token的問題解決后本節展示如何將新令牌添加到請求的header中。 上節中學習過了使用`request.getHeader(String key);`來獲取header中某個key的值。通常來說則還會有個類似的`request.setHeader(String key, String value);`的方法用于設置header中的某個key的值。 很遺憾,出于某些方面的考慮spring中(確認說是servlet)中并沒有提供設置header的方法。 ![](https://img.kancloud.cn/4e/85/4e85bad48aad708677444b4b6a22b5f9_653x172.png) request只提供了上述兩個set方法,也就是說我們沒有辦法通過request來直接設置其`header`信息。既然無法設置其值,那么是否可以在獲取`header`的值的方法上下功夫呢?獲取某個`header`時則必然調用`getHeader`方法;其它人獲取令牌時,則必然調用`getHeader("auth-token")`。在編程的世界中,有一種叫做裝飾器模式的方法恰好能夠做到變更`getHeader("auth-token")`的返回值的目的。被改變后的`getHeader("auth-token")`可以指定的返回我們為其指定的`auth-token`信息,從而間接的**達到**設置header信息的目的。 # 裝飾器模式 alphago前幾年風靡全球,但你一定想不到的是:早上18世紀晚期的1770年,土耳其便已經擁有了一臺自動下棋(應該是國際象棋)裝置,比2016年3月alphago成名足足早了240年!該下棋裝置在當時擊敗了大多數挑戰者,這其中還包括了拿破侖。此裝置不止能夠計算出下一個落棋點,而且還可以自動拿著棋子下棋,甚至于還會與人交流說"將軍"。 ![](https://img.kancloud.cn/3d/95/3d9562f35c3aef4018c46dd47d643b26_668x572.png) 在那個只有機器裝置的年代,完成這項工作簡直不可思議。那么當時人們是怎么做到的呢? ![](https://img.kancloud.cn/84/d7/84d776b3d270eaad005f26ec7e913d31_347x313.png) 重點就在下棋裝置里面的"人",實際上在下棋裝置中偷偷藏了一個下棋的高手。而下棋裝置走的每一步棋,都是由該下棋高手在內容操作的(當然這種操作本身使用機器裝置傳遞到機器臂也很復雜)。 但筆者想說的是:這種在下棋裝置中將下棋高手包裝(裝飾)起來,以使得下棋傀儡擁有下棋高手的下棋能力的模式,被稱為"裝飾器模式"。 ## DEMO 下面以一個小的demo來講下如何使用"裝飾器模式"達到設置header中的auth-token的目的。 ```java class Request { String getHeader(String key) { // 獲取header值的真實代碼略 } } class TokenFilterTest { /**在此傳入auth-token的值*/ @Test void doFilter() { String authToken = "654321"; Request request = new Request(); this.getAuthUser(request); } /** 在此獲取auth-token的值 */ void getAuthUser(Request request) { // 獲取auth-token request.getHeader("auth-token"); // 根據auth-token獲取當前登錄用戶 } } ``` 上述代碼首先執行doFilter方法,然后再執行`getAuthUser`方法,在`getAuthUser`方法中調用了`request`的`getHeader`方法近而獲取到`auth-token`的值。 接下來使用裝飾器模式來簡單處理一下: ``` class Request { ... } class TokenFilterTest { ... } /** * Request傀儡 */ class RequestWrapper extends Request? { Request request; ? private RequestWrapper() { ? } public RequestWrapper(Request request) { ? this.request = request; } @Override String getHeader(String key) { ? return this.request.getHeader(key); ? } } ``` * ? 繼承了Request,說明Request有的方法本傀儡都有。指定需要Request的地方,本傀儡都能代替。 * ? 本傀儡之所有這么有底氣,是由于本傀儡內部擁有一個真正的Request(下棋高手) * ? 本傀儡也會下棋 * ? 實際上下的每一步棋都是真正的Request來完成的 * ? 若想啟動本傀儡,**必須**給我裝入一個下棋高手 有了這個傀儡以后,doFilter方法便可以改成這樣: ``` /**在此傳入auth-token的值*/ @Test void doFilter() { String authToken = "654321"; Request request = new Request(); RequestWrapper requestWrapper = new RequestWrapper(request); ? this.getAuthUser(request); ? ? this.getAuthUser(requestWrapper); ? } ``` * ? 將下棋高手裝入 * ? 由于下棋傀儡requestWrapper擁有下棋高手request的全部功能,所以此時將下棋傀儡requestWrapper傳入getAuthUser也是完全符合要求的。 此時程序執行的時序圖如下: ![](https://img.kancloud.cn/00/20/00202f24bcc24e7c4cec627564ce8332_627x221.png) ## 重寫裝飾器中的方法 此時只要稍稍的對裝飾器的getHeader方法進行改行,便能達到:如果傳入的key為`auth-token`便返回新的令牌的目的: ```java /** * Request傀儡 */ class RequestWrapper extends Request { ... @Override String getHeader(String key) { if ("auth-token".equals(key)) { // 在此返回新的auth-token值 return "456789"; } return this.request.getHeader(key); } } ``` 對應實序圖如下: ![](https://img.kancloud.cn/f2/5e/f25e23e9f1c809c4365e15e8ab126c90_613x361.png) ## 在外部向裝飾器中設置token 有了裝飾器,也能夠在裝飾器中按特定的思想傳值了,但這仍然滿足不了初始的要求,即: ```java class TokenFilterTest { /**在此傳入auth-token的值 ?*/ @Test void doFilter() { String authToken = "654321"; Request request = new Request(); this.getAuthUser(request); } /** 在此獲取auth-token的值 ?*/ void getAuthUser(Request request) { // 獲取auth-token request.getHeader("auth-token"); // 根據auth-token獲取當前登錄用戶 } } ``` 將方法?中設置的authToken的值通過request?帶入getAuthUser中。剛剛改寫getHeader的方法卻是傳回了固定的"456789",而非"654321"。繼續如下改造RequestWrapper ```java class RequestWrapper extends Request { Request request; String token; ? private RequestWrapper() { } private RequestWrapper(Request request) { ? this.request = request; } public RequestWrapper(Request request, String token) { this(request); ? this.token = token; ? } @Override String getHeader(String key) { if ("auth-token".equals(key)) { return this.token; ? } return this.request.getHeader(key); } ... ``` * ? 增加token,并做為auth-token返回 * ? 將原構造函數設置為私有,并在內部進行調用 此時doFilter隨之改造為: ``` @Test public void doFilter() { String authToken = "654321"; Request request = new Request(); RequestWrapper requestWrapper = new RequestWrapper(request, authToken); this.getAuthUser(requestWrapper); } ``` 加入測試信息后的最終代碼如下: filter/TokenFilterTest.java ```java public class TokenFilterTest { @Test public void doFilter() { String authToken = new RandomString(6).nextString(); System.out.println("authToken傳入值為" + authToken); Request request = new Request(); RequestWrapper requestWrapper = new RequestWrapper(request, authToken); this.getAuthUser(requestWrapper); } /** 在此獲取auth-token的值 */ void getAuthUser(Request request) { // 獲取auth-token System.out.println("獲取到的auth-token值為:" + request.getHeader("auth-token")); // 根據auth-token獲取當前登錄用戶 } } class Request { private String authToken = "123456"; String getHeader(String key) { System.out.println("調用了Request中的getHeader方法"); if ("auth-token".equals(key)) { return this.authToken; } // 其它的IF條件 return null; } } /** * Request傀儡 */ class RequestWrapper extends Request { Request request; String token; private RequestWrapper() { } private RequestWrapper(Request request) { this.request = request; } public RequestWrapper(Request request, String token) { this(request); this.token = token; } @Override String getHeader(String key) { System.out.println("調用了RequestWrapper中的getHeader方法"); if ("auth-token".equals(key)) { return this.token; } return this.request.getHeader(key); } } ``` 執行結果如下: ``` authToken傳入值為X0QR0u 調用了RequestWrapper中的getHeader方法 獲取到的auth-token值為:X0QR0u ``` 結果符合預期。達到了在不改變request中的header值的前提下,將token在A方法中傳入,然后在B方法中獲取的目的。 # HttpServletRequestWrapper spring已經提供了用于裝飾request的類 ---- HttpServletRequestWrapper。 ![](https://img.kancloud.cn/d8/5e/d85ee21915a15b076bb0da0997bbdfb8_299x164.png) 此類提供了構造函數:`HttpServletRequestWrapper(request)`方法,但未提供所需要的`HttpServletRequestWrapper(request, token)`構造函數。為此,在原有HttpServletRequestWrapper的基礎上,做一下**繼承**并將其命名為HttpServletRequestTokenWrapper: ![](https://img.kancloud.cn/59/6e/596ed0045d77082e4696ea1fc18900dc_322x390.png) 代碼如下: ```java @WebFilter public class TokenFilter extends HttpFilter { ... /** * 帶有請求token的Http請求 */ class HttpServletRequestTokenWrapper extends HttpServletRequestWrapper { ? HttpServletRequestWrapper httpServletRequestWrapper; String token; private HttpServletRequestTokenWrapper(HttpServletRequest request) { ? super(request); } public HttpServletRequestTokenWrapper(HttpServletRequest request, String token) { ? this(request); this.token = token; } @Override public String getHeader(String name) { ? if (TOKEN_KEY.equals(name)) { return this.token; } return super.getHeader(name); } } } ``` * ? 該類定義于TokenFilter內部 * ? 實現父類的構造函數,為防止其被外部使用從而聲明為私有 * ? 新增構造函數,接收request及token * ? 重寫getHeader方法 使用該HttpServletRequestTokenWrapper在header中設置auth-token的代碼如下: filter/TokenFilter.java ```java protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ... // 有效性判斷 if (!this.validateToken(token)) { // 如果無效則分發送的token token = this.makeToken(); logger.info("原token無效,發布的新的token為" + token); // 設置header中的auth-token request = new HttpServletRequestTokenWrapper(request, token); ? } ``` * ? 此時的request已經是那個被重寫過getHeader方法的HttpServletRequestTokenWrapper了。 # 設置響應的令牌 在響應令牌的header中設置令牌非常簡單:只需要調用response中的setHeader方法即可。 filter/TokenFilter.java ```java protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ... // 轉發數據。spring開始調用控制器中的特定方法 chain.doFilter(request, response); logger.info("在控制器被調用以后執行"); // 為http響應加入新token后返回 response.setHeader(TOKEN_KEY, token); ``` 至此,使用過濾器在請求的header信息中傳遞令牌的過程便完整的實現了。雖然此方法沒有考慮到生產環境下令牌過期以及令牌更新的問題,但在當前系統中已經完全夠用了。 >[info] 在生產環境中會采用更加優秀、集成度更高的spring security來替代令牌交互及后面章節中將到學習到的用戶認證過程。 # 測試 一切都不能想當然,一切都不能"我認為"。保證代碼質量最好的方法便是使用單元測試,如果單元測試不適用則么最少也要看看日志及返回值。再次新建http request測試如下: ``` GET http://localhost:8080/Teacher HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 ? Transfer-Encoding: chunked ? Date: Tue, 11 Feb 2020 05:22:36 GMT ? [] Response code: 200; Time: 121ms; Content length: 2 bytes ``` * ? 很遺憾:在響應的header并未獲取到令牌auth-token 筆者猜想原因如下 1. 之所以沒有在request上提供setHeader方法,官方是這么考慮的:一旦請求信息確立但不能夠被再次修改以保證數據在傳輸過程中不失真。也就是說過濾器的設計之初并不是用來變更請求信息的(比如進行訪問日志的記錄)。 2. `response.setHeader(TOKEN_KEY, token);`未生效的原因也是如此:一旦響應信息確立則不能夠對其進行修改,所以即使調用了setHeader也不會生效。 3. 確認request信息取決于前臺,前臺請求時帶有什么值request就有什么值。所以在request不提供setHeader方法。而response信息取決于后臺,在沒有為response定稿前,是需要setHeader方法來設置header信息的。所以response中提供了setHeader方法供調用。 基于此,若想改變response響應中的header信息,那么應該在響應信息被確立以前。所以代碼最終應該被修正為: filter/TokenFilter.java ```java protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { ... // 在確立響應信息前,設置響應的header值 ? response.setHeader(TOKEN_KEY, token); ? // 轉發數據。spring開始調用控制器中的特定方法 chain.doFilter(request, response); logger.info("在控制器被調用以后執行"); // 為http響應加入新token后返回 ? response.setHeader(TOKEN_KEY, token); ? ``` 重新啟動應用后再次測試: <hr> 請求時未傳入token: ``` GET http://localhost:8080/Teacher ``` 測試結果: ``` GET http://localhost:8080/Teacher HTTP/1.1 200 auth-token: e2047302-2c07-474a-9066-6404bc3b4534 ? Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 11 Feb 2020 05:34:44 GMT [] Response code: 200; Time: 122ms; Content length: 2 bytes ``` * ? 未傳入token時,系統分發新token <hr> 請求時傳入有效token: ``` GET http://localhost:8080/Teacher auth-token: d31e0d8a-470d-413a-beda-23885f4551f2 ? ``` 測試結果: ``` GET http://localhost:8080/Teacher HTTP/1.1 200 auth-token: d31e0d8a-470d-413a-beda-23885f4551f2 ? Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 11 Feb 2020 05:36:46 GMT [] Response code: 200; Time: 12ms; Content length: 2 bytes ``` * ? 傳入有效token時,系統回傳有效token <hr> 請求時傳入無效token ``` GET http://localhost:8080/Teacher auth-token: d31e0d8a-470d-413a-beda ? ``` 測試結果: ``` GET http://localhost:8080/Teacher HTTP/1.1 200 auth-token: e71dd873-0f0b-428b-b202-aa1b359fe3ff ? Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 11 Feb 2020 05:38:04 GMT [] Response code: 200; Time: 30ms; Content length: 2 bytes ``` * ? 傳入無效token時,系統分發新的有效token # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.3](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.3) | - |
                  <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>

                              哎呀哎呀视频在线观看