<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錯誤搞的焦頭爛額。可能既不知道它是怎么產生的,又不知道它為何產生。在本教程的1.5.2小節前后臺對接初期便遇到了此CORS錯誤,在教程的2.2.4前后臺對接時仍然遇到了此CORS錯誤,在5.2.9小節的集成測試中又出現了此CORS錯誤。本節中嘗試帶領大家找找這錯誤的根本原因。 # OPTIONS 在教程中學習了使用get方法查詢數據、post方法新增數據、put方法更新數據、delete方法刪除數據。在前面的章節中已提到`瀏覽在進行跨域訪問時,如果發現請求的方法不是get(各瀏覽器處理的方式不同),那么將首先發起options請求`,而這個options請求在chrome的控制臺中并未發現。但換一個瀏覽器就不一樣了:使用firefox進行用戶登錄將在網絡中查看到如下信息: 在登錄前首先在登錄地址上發起options請求: ![](https://img.kancloud.cn/6a/d6/6ad61eed0b5d6f4b16ddb5b22df40488_979x499.png) 當發現`Access-Control-Allow-Methods`的值`PUT,DELETE,POST,GET,PATCH`包含`POST`時,而且`Access-Control-Allow-Origin`包含當前請求域名時才繼續發起`POST`請求;在以后發起的每次請求的響應中,瀏覽器還會比較`Access-Control-Allow-Methods`的值`PUT,DELETE,POST,GET,PATCH`是否包含當前請求的方法以及`Access-Control-Allow-Origin`的值是否包含當前請求域名。如未滿足包含條件將阻斷相應的請求。 ![](https://img.kancloud.cn/87/80/878002597b3a11caf33f50c1a7b1a7ce_464x96.png) ## 測試一 注銷到當前登錄用戶,然后終止前臺服務,并使用`ng serve --port 4201`重新啟動前臺。使用瀏覽器打開`http://localhost:4201/`并使用用戶名密碼登錄: ![](https://img.kancloud.cn/4a/0e/4a0e09db718bd7b89cfb537dc5e52a7c_1228x486.png) options請求直接返回了403(無權限),瀏覽器未接收到`Access-Control-Allow-Methods`以及`Access-Control-Allow-Origin`,自動終止了用戶登錄的post請求。 ## 測試二 終止前臺服務,重新使用`ng serve`啟動前臺。找到后臺的`config/WebConfig.java`。刪除` .allowedMethods("PUT", "DELETE", "POST", "GET", "PATCH")`中的`"POST"`: ```java .allowedOrigins("http://localhost:4200") .allowedMethods("PUT", "DELETE", "GET", "PATCH") .exposedHeaders("auth-token"); ``` 重新啟動后臺后在前臺`http://localhost:4200/`繼續嘗試登錄: ![](https://img.kancloud.cn/b6/5c/b65c31bf78a2c577cc4a7698dd1cf344_532x477.png) 發起options請求時直接返回了403,瀏覽器未接收到`Access-Control-Allow-Methods`以及`Access-Control-Allow-Origin`,瀏覽器同樣直接終止了用戶的登錄請求。 **注意:** 請恢復后臺代碼,重啟后臺后繼續學習。 # 錯誤重現 若想根本上解決CORS問題關鍵在于錯誤重現。此CORS錯誤可以按以下步驟重現。步驟一:啟動前臺、后臺,使用用戶進行登錄。步驟二:重新啟動后臺。此時在前臺進行操作將得到如下錯誤信息: ![](https://img.kancloud.cn/47/31/4731bbf60e3074a4d5d93786179be5e0_1247x158.png) ## 解決錯誤 可以在sessionStorage中清除isLogin達到清除錯誤的目的。間接的說明該錯誤是由于登錄緩存isLogin引起的。為了徹底弄清楚這個問題,點擊注銷并查看網絡請求信息: ![](https://img.kancloud.cn/a5/e5/a5e57292a4f7e406cd761d59c20f4054_660x83.png) 用戶點擊注銷時,瀏覽器首先在注銷地址發起了options請求,接收到了`Access-Control-Allow-Methods`以及`Access-Control-Allow-Origin`并符合發向注銷地址發請get請求的要求,近而繼續發起了get請求。 ![](https://img.kancloud.cn/e6/73/e673123d13b599e89cdd0b0f3988634e_524x399.png) 在該請求中并未接收到`Access-Control-Allow-Methods`以及`Access-Control-Allow-Origin`信息,所以瀏覽器終止了此請求。只所以未接收到有效的`Access-Control-Allow-Methods`以及`Access-Control-Allow-Origin`信息,是由于在后臺的認證攔截器對注銷功能進行了攔截:未登錄的用戶在訪問注銷接口時,將直接返回401未認證的狀態碼。也就是說當前后只支持已登錄的用戶發起注銷請求,未登錄的用戶無法發起注銷請求。這個邏輯并沒有任何問題。問題出在:如果由于后臺重啟的原因導致前臺的authToken失效,前臺需要在接收到401的狀態碼后及時的切換用戶的登錄狀態為:未登錄。而當前由于后臺返回401時未返回`Access-Control-Allow-Methods`以及`Access-Control-Allow-Origin`信息,從而導致了前臺代碼無法獲取該401狀態碼的信息。 綜上,似使用以下步驟依次解決由于后臺重啟的原因導致的authToken失效的問題。 ### 加入Access-Control-Allow-Methods 找到后臺的認證攔截器,加入`Access-Control-Allow-Methods`信息: interceptor/AuthInterceptor.java ```java response.setStatus(401); response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,PATCH"); return false; } ``` 重新啟動后臺,再次點擊注銷按鈕: ![](https://img.kancloud.cn/3f/79/3f795fdf841e8c45a7765f8c5a7ac787_513x371.png) 控制臺信息: ![](https://img.kancloud.cn/4f/6d/4f6d218b42cedcca842d8245c3b445d4_1134x46.png) ### 加入Access-Control-Allow-Origin interceptor/AuthInterceptor.java ```java response.setStatus(401); response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,PATCH"); response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200"); return false; } ``` ![](https://img.kancloud.cn/88/8c/888c238efa3ac679fd3d65f998181cdf_504x361.png) 控制臺關于CORS的錯誤消失 ### 當接收到401的在前臺完成注銷操作 后臺發出了401的信息則說明當前的認證用戶已經由于各種完成自動被后臺注銷掉了,那么此時應該在前臺主動的跳出登錄界面。我們在前臺中使用了攔截器來處理authToken,同樣的還可以在前臺的攔截器中統一處理401信息。 在數據流的轉發過程中若想獲取錯誤的數據流,則需要使用`tap`操作符。該操作符可接收3個回調函數做為參數,當執行成功時將調用第一個回調函數(必須傳入),當執行失敗時將調用第二個回調函數(選擇傳入),無論執行成功或失敗均調用第3個回調函數(選擇傳入)。 它的用法如下: core/auth-token-interceptor.ts ```typescript return next.handle(reqClone).pipe(map((httpEvent) => { if (httpEvent instanceof HttpResponse) { const httpResponse = httpEvent as HttpResponse<any>; const authToken = httpResponse.headers.get('auth-token'); CacheService.setAuthToken(authToken); } return httpEvent; }), tap(() => {}, () => {})); } ``` 當前需要對`網絡錯誤`進行攔截并判斷是否發生了401認證錯誤,所以在tap操作符中僅需要處理第二個回調函數即可: core/auth-token-interceptor.ts ```typescript }), tap(() => { }, (event: HttpErrorResponse?) => { if (event.status === 401) { console.log('發生了401錯誤'); } })); } ``` * ? 當發生網絡錯誤時tap操作符會將響應的信息做為回調函數的參數傳入。 ![](https://img.kancloud.cn/84/76/8476f07580b764319c23da2db20e1e07_273x82.png) 最后在攔截器中注入TeacherService并調用setIsLogin方法來實現當后臺發出401時前臺自動完成注銷的功能。 core/auth-token-interceptor.ts ```typescript constructor(private teacherService: TeacherService) { } ... }), tap(() => { }, (event: HttpErrorResponse) => { if (event.status === 401) { this.teacherService.setIsLogin(false); } })); } ``` 此時當后臺重新啟動后,無論前臺進行任何的非登錄操作均會因收到401狀態碼而導致前臺自動完成注銷操作。 # Chrome Chrome瀏覽器可能是出于安全的角度考慮在網絡中默認關閉了`options`的請求。如果希望在Chrome的網絡中查看`options`請求,則可以在Chrome打開 `chrome://flags/#out-of-blink-cors`,將`Out of blink CORS`項改為`disabled`,然后重新啟動Chrome: ![](https://img.kancloud.cn/0c/f5/0cf598083135ee53640136d6e8f12d9c_811x100.png) 此時再次查看網絡,將查看到`options`方法的請求信息: ![](https://img.kancloud.cn/fc/2d/fc2d4e0f9fa7bb307f7de7cf6a82b953_928x253.png) ## 單元測試 由于在auth-token-interceptor引入了`TeacherService`,對應修正單元測試如下: core/auth-token-interceptor.spec.ts ```typescript import { AuthTokenInterceptor } from './auth-token-interceptor'; import {async, TestBed} from '@angular/core/testing'; import {TeacherService} from '../service/teacher.service'; import {TeacherStubService} from '../test/service/teacher-stub.service'; describe('AuthTokenInterceptor', () => { beforeEach(async(() => { TestBed.configureTestingModule({ providers: [ {provide: TeacherService, useClass: TeacherStubService} ] }) .compileComponents(); })); it('should create an instance', () => { const teacherService: TeacherService = TestBed.get(TeacherService); ? expect(new AuthTokenInterceptor(teacherService)).toBeTruthy(); ? }); }); ``` * ? 無法使用`TestBed.get(AuthTokenInterceptor)`來直接獲取`AuthTokenInterceptor`實例,你知道這是為什么嗎? # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.10](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.10) | - | | tap操作符 | [https://rxjs-dev.firebaseapp.com/api/operators/tap](https://rxjs-dev.firebaseapp.com/api/operators/tap) | 10 |
                  <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>

                              哎呀哎呀视频在线观看