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

                啟動前后臺后進行單元測試以驗證攔截器是否生效,并修正在集成測試中發現的一些問題。 ## CORS錯誤 點擊登錄按鈕后發現如下錯誤: ``` Access to XMLHttpRequest at 'http://localhost:8080/Teacher/login' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` 該錯誤是個老生常談的問題,教程伊始便與該錯誤打過交道。它的原因是后臺沒有返回對應的`Access-Control-Allow-Origin`,解決的方法是對應添加跨域設置。而我們在上一個小節的攔截器環節中確認并沒有動跨域的任何設置,所以錯誤的方向還是應該由攔截器入手。 找到后臺的日志簡單瀏覽一下看是否能夠得到一些有幫助的信息,日志中有以下兩條: ``` 請求的地址為/Teacher/login請求的方法為:OPTIONS? 當前token未綁定登錄用戶,返回401 ``` ? 在前臺的代碼明明使用的是`this.httpClient.post`方法,為何在用戶登錄時后臺會接收到options請求呢?。這是由于瀏覽器在進行跨域訪問時,如果發現請求的方法不是`get`,那么在請求以前則會向該請求地址(此時為/Teacher/login)發送`options`方法來確認后臺允許前臺發起的請求方法。仍然以登錄為例:當后臺返回的允許請求方法中包括了 `POST`方法時,瀏覽器才會向`/Teacher/login`進行`post`請求,否則將放棄請求。 所以才有了在教程開始時的這段配置代碼: config/WebConfig.java ```java @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:4200") .allowedMethods("PUT", "DELETE", "POST", "GET", "PATCH") .exposedHeaders("auth-token"); } ``` 上述的代碼的是在說:當由`http://localhost:4200`發起對本系統任意地址(`/**`)的訪問請求,允許發起"PUT", "DELETE", "POST", "GET", "PATCH"5種請求方法,同時允許前臺獲取`header`中的"auth-token"。 但由于在整體訪問流程中,攔截器早于此處代碼段執行,所以還未執行到此代碼段的生效位置,就被攔截器截胡了。解決的方法是:在攔截中獲取請求方法為`options`中直接放行: interceptor/AuthInterceptor.java ```java System.out.println("請求的地址為" + url + "請求的方法為:" + method); if( "OPTIONS".equals(method)) { // 請求方法為OPTIONS,不攔截 return true; } // 判斷請求地址、方法是否與用戶登錄相同 ``` 重新啟動后臺,繼續測試。 ## 刷新錯誤 使用用戶名密碼登錄系統后,在任意界面進行刷新都將在控制臺發生如下網絡錯誤: ![](https://img.kancloud.cn/b2/92/b2926ca56f5354486545e2bdaa8fd1a8_1178x275.png) 點擊任意錯誤請求后,點擊響應header上的 view source ![](https://img.kancloud.cn/ee/f8/eef8f445e6df37c389ed220f0c92f6a8_951x225.png) 發現錯誤的類型均為401 ![](https://img.kancloud.cn/95/24/9524d50c49b63686beb8e9d34a0c5ac2_587x187.png) 這是由于在進行頁面刷新時前臺用于存儲auth-token的CacheService重新進行了初始化。而在初始化的過程中,將auth-token重置為undefined的原因: service/cache.service.ts ```typescript export class CacheService { /** 認證令牌 */ private static authToken: string = undefined; ? ``` * ? 刷新前臺時authToken被重置為undefined 這個問題與前面碰到的由于未對登錄狀態進行緩存,從而導致每次刷新瀏覽器都要重新登錄一次的原因是一樣的。解決的方法也一樣:使用瀏覽器提供的緩存來存儲auth-token,以保證用戶在進行瀏覽器刷新時能夠保持auth-token不變: service/cache.service.ts ```typescript private static authToken: string = undefined; ? private static authToken: string = sessionStorage.getItem('authToken') === null ? undefined : sessionStorage.getItem('authToken'); ? constructor() { } static setAuthToken(token: string) { CacheService.authToken = token; sessionStorage.setItem('authToken', token); ? } ``` * ? 使用sessionStorage存儲的值設置authToken * ? 更新sessionStorage存儲 sessionStorage獲取某個不存在項時返回了null,這與CacheService的authToken的默認值為undefined不同。所以在初始化時,需要使用比目運算符進行轉換。如果將CacheService的authToken的默認值同樣設置為null。代碼還會精簡一些: service/cache.service.ts ```typescript private static authToken: string = ? sessionStorage.getItem('authToken') === null ? undefined : sessionStorage.getItem('authToken'); ? private static authToken: string = sessionStorage.getItem('authToken'); ... static getAuthToken() { if (CacheService.authToken === undefined) { ? ? if (CacheService.authToken === null) { return ''; ? } return CacheService.authToken; } ``` * ? 由于angular在在處理header的過程中遇到值為undefined時會報異常,所以當authToken的值為undefined時對應返回`''`?以規避以異常。 **注意:** 你此時需要參考下圖清下緩存 ![](https://img.kancloud.cn/50/9d/509db51e1ed2fea9531cab814e49737f_1194x406.png) ## logout 最后再修正下這個看不到的注銷。當前的注銷功能并未調用后臺對應的logout接口。這將導致用戶注銷后實質上為后臺為當前窗口分配的auth-token仍然是生效的。這增加了系統數據被滲透的風險。用戶點擊注銷時只有真正的觸發后臺的注銷接口,才會起到auth-token與用戶的解綁作用。 為此,為service/teacher.service.ts新增logout方法如下: service/teacher.service.ts ```typescript /** * 注銷 */ logout(): Observable<void> { const url = 'http://localhost:8080/Teacher/logout'; return this.httpClient.get<void>(url); } ``` 測試過程略。 <hr> 在C層的注銷方法中調用logout方法: nav/nav.component.ts ```typescript onLogout() { this.teacherService.logout() .subscribe(() => { this.teacherService.setIsLogin(false); }); } ``` 修正單元測試如下: nav/nav.component.spec.ts ```typescript fit('onLogout', () => { const service = TestBed.get(TeacherService) as TeacherService; spyOn(service, 'setIsLogin'); spyOn(service, 'logout').and.returnValue(of(null)); ? component.onLogout(); expect(service.logout).toHaveBeenCalled(); ? expect(service.setIsLogin).toHaveBeenCalledWith(false); }); ``` * ? 設置logout方法的替身,并指定替身的返回值 * ? 斷言方法被調用 測試結果: ![](https://img.kancloud.cn/9f/55/9f550f7fa30ee0195d9ffd9a66094391_428x119.png) 這是由于沒有為TeacherService的測試替身TeacherStubService同步添加logout的原因所致 test/service/teacher-stub.service.ts ```typescript logout(): Observable<void> { return of(null); } ``` 再次運行單元測試通過。 # 測試結果 ![](https://img.kancloud.cn/b6/5f/b65f4927c97f95a83b637ff1e16baee8_1418x395.gif) # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.9](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.9) | - | | 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>

                              哎呀哎呀视频在线观看