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

                # Basic access authentication 用戶登錄在術語中更多的稱為用戶認證,英文單詞以authentication為關鍵字,也常常被簡寫為auth。認證的方式有很多種,比如我們常看到的用戶名密碼認證、手機號驗證碼認證、使用微信支付寶等第三方快捷認證等。在此,我們僅講述用戶名密碼的認證方式。 認證的過程也可以有很多種,比如我們歷史上曾經學習過將用戶名、密碼做為表單數據,以post方式發送給過去,繼而完成用戶認證。今天我們學習的是另一種更加通用的認證方式:`Basic access authentication`,有時也被簡稱為`Basic Auth`。 ## Basic Auth Basic Auth,顧名思義其為一種基本的認證模式,它也是最常用的HTTP認證方案。它的基本認證邏輯是:將認證信息放到Http請求的Header部分。 以用戶名為`zhangsan`密碼為`yunzhi.club`為例,使用Basic Auth認證流程如下: 1. 將用戶名密碼與`:`相連,接拼為字符串`zhangsan:yunzhi.club`。 2. 使用base64進行加密 `base64(zhangsan:yunzhi.club)`,加密結果為`emhhbmdzYW46eXVuemhpLmNsdWI=`。 3. 在http請求中的headers中增加以下項:`Authorization: Basic emhhbmdzYW46eXVuemhpLmNsdWI=` 4. 向后臺發起請求 此時,用戶名密碼便成功的通過headers以Basic Auth的模式發送給了后臺。 > 除最常用的Basic認證外,還有**Bearer**、**Digest**、**HOBA**等認證模式。 ## 后臺接口 后臺為我們提供了專用的認證地址(實際上并不拘泥于此),接口信息如下: ```bash GET /teacher/login ``` 認證模式:Basic。認證失敗將返回狀態碼401,認證成功將返回用戶名密碼對應的教師數據。 ## 發起認證 我們來到login組件的`onSubmit`方法,按Basic Auth的步驟逐步完成代碼。 ### 自動化 按前面學習的方法,我們可以利用`ng t`來啟動組件測試,接著點擊登錄中的登錄按扭,以達到調用`onSubmit`的方法。其我們還可以借助單元測試的思想,寫一些自動化的代碼,這樣當我們每次改動代碼并按`ctrl + s`保存文件后,這些代碼便會自動執行。在這些自動執行的代碼中實現**調用onSubmit**的方法。 是的,我們完全可以參考第一節的內容,使用模塊點擊V層按鈕的方法。除此以外,我們還可以在單元測試代碼直接調用組件的方法。為此,我們增加如下代碼以協助開發用戶登錄。 ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -39,4 +39,11 @@ fdescribe('LoginComponent', () => { // 點擊按鈕以后,onSubmit方法應該被調用了1次。 expect(component.onSubmit).toHaveBeenCalledTimes(1); }); + + it('onSubmit 用戶登錄', () => { + // 啟動自動變更檢測 + fixture.autoDetectChanges(); + + component.onSubmit(); + }); }); ``` 使用`ng t`啟動,將自動執行本方法: ![image-20210304093344967](https://img.kancloud.cn/48/cd/48cdf278ef99592125cf62b77bf449eb_912x332.png) 如果想僅僅執行當前方法,則可以在`it`前面加入`f`: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -40,7 +40,7 @@ fdescribe('LoginComponent', () => { expect(component.onSubmit).toHaveBeenCalledTimes(1); }); - it('onSubmit 用戶登錄', () => { + fit('onSubmit 用戶登錄', () => { // 啟動自動變更檢測 fixture.autoDetectChanges(); ``` 此時,單元測試則將僅僅執行當前方法: ![image-20210304093500558](https://img.kancloud.cn/9d/40/9d401b0b0e3e14effcb936606298d47c_880x178.png) 控制臺日志如下: ![image-20210304093549368](https://img.kancloud.cn/ca/8d/ca8dba700abc966092f0b29cd6c13491_882x214.png) ### 接拼認證信息 ```typescript +++ b/first-app/src/app/login/login.component.ts @@ -19,5 +19,7 @@ export class LoginComponent implements OnInit { onSubmit(): void { console.log('點擊了登錄按鈕'); + const authString = this.teacher.username + ':' + this.teacher.password; + console.log(authString); }?? } ``` 控制臺信息如下: ![image-20210304093954438](https://img.kancloud.cn/e8/c7/e8c7670826eba3114c522b1798cddfba_778x148.png) 由于初始化的teacher并不存在用戶名密碼信息,所以最終在控制臺打印了`undefined:undefined`。為此,在單元測試代碼中,我們為`teacher`設置一個用戶名、密碼: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -43,7 +43,7 @@ fdescribe('LoginComponent', () => { fit('onSubmit 用戶登錄', () => { // 啟動自動變更檢測 fixture.autoDetectChanges(); - + component.teacher = {username: 'zhangsan', password: 'codedemo.club'}; component.onSubmit(); }); }); ``` ![image-20210304094227330](https://img.kancloud.cn/21/ea/21ea9c2100c1019f630ff7b0453d4162_980x252.png) ### Base64加密 Base64是眾多加密算法中的一種,最近也被廣泛地用于在瀏覽器中顯示圖片。比如你可以將以下代碼粘貼到html文件中,在對應的位置上將顯示一張圖片: ```` <img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjE4NHB4IiBoZWlnaHQ9IjIwMHB4IiB2aWV3Qm94PSIwIDAgMTg0IDIwMCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDMuMi4yICg5OTgzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5zaGllbGQtbGFyZ2U8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4KICAgICAgICA8ZyBpZD0ic2hpZWxkLWxhcmdlIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIj4KICAgICAgICAgICAgPGcgaWQ9IkltcG9ydGVkLUxheWVycyIgc2tldGNoOnR5cGU9Ik1TTGF5ZXJHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoOC4wMDAwMDAsIDExLjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTgzLjIsMS4wMzI0OTI3NSBMODMuMiwxLjAyODYwMTQ1IEwwLjMwNjgsMzAuNDQ4MTU5NCBMMTIuODk4NiwxMzkuNjczMTgxIEw4Mi45OTk4LDE3OC40NTAwMjkgTDE1NC4wOTgxLDEzOS4xNTA0NDkgTDE2Ny45NTg3LDI5LjkyNjcyNDYgTDgzLjIsMS4wMzI0OTI3NSIgaWQ9IkZpbGwtMSIgZmlsbD0iI0UyMzIzNyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPgogICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2Ny44Mzc4LDI5LjkyNjcyNDYgTDgyLjk3MTIsMS4wMzI0OTI3NSBMODIuOTcxMiwxNzguNDUwMDI5IEwxNTQuMDgzOCwxMzkuMTUwNDQ5IEwxNjcuODM3OCwyOS45MjY3MjQ2IEwxNjcuODM3OCwyOS45MjY3MjQ2IFoiIGlkPSJGaWxsLTIiIGZpbGw9IiNCNTJFMzEiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0wLjE5MjQsMzAuNDQ4MTU5NCBMMTIuODQxNCwxMzkuNjczMTgxIEw4Mi45NzEyLDE3OC40NTAwMjkgTDgyLjk3MTIsMS4wMjg2MDE0NSBMMC4xOTI0LDMwLjQ0ODE1OTQgTDAuMTkyNCwzMC40NDgxNTk0IFoiIGlkPSJGaWxsLTMiIGZpbGw9IiNFMjMyMzciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMDAuNjgzNyw5NC4yMjY2Mzc3IEw4Mi45NzEyLDU3LjcwNTQ0OTMgTDY3LjcyNjEsOTQuMjI2NjM3NyBMMTAwLjY4MzcsOTQuMjI2NjM3NyBMMTAwLjY4MzcsOTQuMjI2NjM3NyBaIE0xMDcuMzY3LDEwOS41ODMwMjIgTDYwLjkwNSwxMDkuNTgzMDIyIEw1MC41MTE1LDEzNS41MjM3NTQgTDMxLjE3NzksMTM1Ljg3OTE1OSBMODIuOTcxMiwyMC44MTA2OTU3IEwxMzYuNjY2NCwxMzUuODc5MTU5IEwxMTguNzQ1OSwxMzUuODc5MTU5IEwxMDcuMzY3LDEwOS41ODMwMjIgTDEwNy4zNjcsMTA5LjU4MzAyMiBaIiBpZD0iRmlsbC00IiBmaWxsPSIjRkZGRkZGIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=="> ```` 上面的圖片`src`的部分以`data:image/svg+xml;base64`打頭,即表示使用了base64算法。簡單來講,base64算法一種在加密時將二進制串轉換為ASCII字符串(實際上只選取了部分ASCII),在解密時再將ASCII字符串轉換為二制進的加密解密算法。由于http中的header部分只能夠攜帶ASCII編碼的字符串,所以在沒有base64算法轉換之前。將用戶名、密碼信息放到header中傳遞,則僅支持英文字符;在base64的幫助下,可以將用戶名、密碼轉換為ASCII字符串,近而可以做為header數據項中傳遞。 ```typescript +++ b/first-app/src/app/login/login.component.ts @@ -21,5 +21,7 @@ export class LoginComponent implements OnInit { console.log('點擊了登錄按鈕'); const authString = this.teacher.username + ':' + this.teacher.password; console.log(authString); + const authToken = btoa(authString); ???? + console.log(authToken); } } ``` TypeScript提供了`btoa`函數來快捷的完成加密操作 ????。 ![image-20210304095629649](https://img.kancloud.cn/76/dc/76dc93d9d1ce1f83ac5220978551eea1_782x184.png) ### 請求Header Angular提供了傳用的HttpHeaders用于構建請求的header信息: ```typescript +++ b/first-app/src/app/login/login.component.ts -import {HttpHeaders} from '@angular/common/http'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; @@ -24,5 +24,6 @@ export class LoginComponent implements OnInit { console.log(authString); const authToken = btoa(authString); console.log(authToken); + let httpHeaders = new HttpHeaders(); + httpHeaders = httpHeaders.append('Authorization', 'Basic ' + authToken); ?? } } 注意是`Basic `不是`Basic`, 前一個存在空格 ?? ``` ### 發起請求 然后便可以在httpClient的任意方法中加入此header請求信息: ```typescript +++ b/first-app/src/app/login/login.component.ts @@ -1,5 +1,5 @@ import {Component, OnInit} from '@angular/core'; @Component({ selector: 'app-login', @@ -12,7 +12,7 @@ export class LoginComponent implements OnInit { password: string }; - constructor() { + constructor(private httpClient: HttpClient) { } ngOnInit(): void { @@ -26,5 +26,12 @@ export class LoginComponent implements OnInit { console.log(authToken); let httpHeaders = new HttpHeaders(); httpHeaders = httpHeaders.append('Authorization', 'Basic ' + authToken); + + this.httpClient + .get( + 'http://angular.api.codedemo.club:81/teacher/login', + {headers: httpHeaders}) + .subscribe(teacher => console.log(teacher), + error => console.log('發生錯誤, 登錄失敗', error)); } } ``` 此時單元測試中將觸發一個錯誤,相信你現在有足夠的能力把它解決掉,解決以后控制臺將打印以下信息: ![image-20210305075729153](https://img.kancloud.cn/4a/96/4a96457295a82884f60a750834bac8b7_1428x216.png) 除使用`get`方法外,還可以使用`put`、`post`、`delete`等請求方式,比如: ```typescript this.httpClient.post(url, {}, {headers: httpHeaders}) ``` **注意:**我們的后臺每日將清空一次數據,對所有的成員開放后臺API,這意味著當前正在有其它的學員進行教師編輯功能的練習。這會使得用戶名`zhangsan`處于失效狀態(比如有學員將zhangsan改成了zhangsanfeng)。你可以在瀏覽器中直接訪問[http://angular.api.codedemo.club:81/teacher](http://angular.api.codedemo.club:81/teacher)來獲取當前有效的用戶名信息,系統默認用戶的密碼均為`codedemo.club` 。 ## 充分的測試 一個優秀的項目離不開充分的測試,測試是保障軟件質量最重要的一環,沒有之一。在測試中,我們需要充分的站在用戶的角度,根據自己的經驗努力思索用戶在實際使用過程中可能會出現的情景,然后一一把它們模擬出來。 ### 用戶名密碼錯誤 前面我們僅驗證了用戶名、密碼正確的情況。在實際的使用過程中顯然這是不夠的。而用戶名、密碼錯誤時是否是按我們的預期發起的呢?與其猜、想、看、盯,不如實際用代碼測試一下: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -45,7 +45,7 @@ fdescribe('LoginComponent', () => { fit('onSubmit 用戶登錄', () => { // 啟動自動變更檢測 fixture.autoDetectChanges(); - component.teacher = {username: 'zhangsan', password: 'codedemo.club'}; + component.teacher = {username: 'notzhangsan', password: 'codedemo.club'}; component.onSubmit(); }); }); ``` ![image-20210305080512651](https://img.kancloud.cn/c7/22/c7224d680a59a6682607d4bba48b22b1_1490x178.png) 再驗證一下密碼錯誤的情況: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -45,7 +45,7 @@ fdescribe('LoginComponent', () => { fit('onSubmit 用戶登錄', () => { // 啟用自動變更檢測 fixture.autoDetectChanges(); - component.teacher = {username: 'notzhangsan', password: 'codedemo.club'}; + component.teacher = {username: 'zhangsan', password: 'password'}; component.onSubmit(); }); }); ``` ![image-20210305080512651](https://img.kancloud.cn/c7/22/c7224d680a59a6682607d4bba48b22b1_1490x178.png) ### 中文用戶名密碼 雖然我個人沒有將中文做為用戶名密碼的習慣,但是部分用戶的確有這個需求,那么我們當前代碼是否能夠很好的處理這種情況呢? ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -45,7 +45,7 @@ fdescribe('LoginComponent', () => { fit('onSubmit 用戶登錄', () => { // 啟動自動變更檢測 fixture.autoDetectChanges(); - component.teacher = {username: 'zhangsan', password: 'password'}; + component.teacher = {username: '中文用戶名', password: 'codedemo.club'}; component.onSubmit(); }); }); ``` ![image-20210305080955464](https://img.kancloud.cn/cf/10/cf10057c35ba2a758f6c26b9dfaf2bfd_1082x116.png) 我們得到了一個錯誤,該錯誤表明當前代碼在處理中文用戶名時會發生異常。那么處理中文密碼是否會發生異常呢,請先給出自己的答案后驗證。 當前控制臺信息如下: ![image-20210305081630515](https://img.kancloud.cn/85/23/8523b0bffc7759444e03db58377f1e57_580x138.png) 由以上信息我們能夠得出,上述異常發生在`btoa()`方法上,用以下關鍵字來搜索問題,我們可以快速的找到問題的原因及解決方案: ![image-20210305081814242](https://img.kancloud.cn/13/c1/13c1883d70e22a62632c7185f577f66c_1068x150.png) 搜索結果為我們指引到了[https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings](https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings)一文,該文的回答又為我們指引到了權威的[https://developer.mozilla.org/en-US/docs/Glossary/Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64)在此文章中,有一行note是這么說的: ``` Note that btoa() expects to be passed binary data, and will throw an exception if the given string contains any characters whose UTF-16 representation occupies more than one byte. For more details, see the documentation for btoa(). ``` 上面大概是說: ``` 注意btoa()方法只能傳入二進制數據,如果傳入的參數中包含任何UTF-16的大于1個字節的字符串,將會觸發異常。 ``` 我們接著點擊函數名,查看詳情[https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa),該文中又有如下的描述: ``` The btoa() function takes a JavaScript string as a parameter. In JavaScript strings are represented using the UTF-16 character encoding: in this encoding, strings are represented as a sequence of 16-bit (2 byte) units. Every ASCII character fits into the first byte of one of these units, but many other characters don't. ``` 簡單翻譯下我們大概明白了,原來btoa只能接收以1個字節的字符組成的字符串。而JavaScript的string是用UTF-16來編碼的,該編碼占用了2個字節。每個ASCII編碼的字符都可以用首單元的字節來代碼,但是其它的字符就不是了(言外之意,其它字符就是2個字節了)。 這就需要我們在C語言、數據結構、計算機組成原理等基礎課程中學習過的ASCII編碼了。ASCII編碼中,0 - 127分別代表一個字符,共128個。占用了一個字節的后7位,為:`0000 0000` 至 `0111 1111`。所以每個ASCII編碼的字符,必然可以用一個字節來表示。 在[WindowOrWorkerGlobalScope.btoa()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa)一文中,我們還可以找到相應測試的示例代碼: ```typescript const ok = "a"; console.log(ok.codePointAt(0).toString(16)); // 61: occupies < 1 byte const notOK = "?" console.log(notOK.codePointAt(0).toString(16)); // 2713: occupies > 1 byte console.log(btoa(ok)); // YQ== console.log(btoa(notOK)); // error ``` 我們當然也可以用中文來做下實驗: ![image-20210305083642444](https://img.kancloud.cn/1f/df/1fdf6993d0a663c38498fbe0f9d68518_1156x284.png) 如上所示`zhangsan`中的首字母`z`,轉換為10進制后值為`122`,該值位于`0-255`之間,占用一個字節。當然了,實際上我們完全可以在ASSCI編碼表中找到字母`z`的編碼: ![image-20210305083829485](https://img.kancloud.cn/d6/ee/d6eebdf7320baa5b521d530a6c8574b6_544x132.png) 繼續測試中文的`張`: ![image-20210305084048576](https://img.kancloud.cn/f0/87/f0871b575190c26f949215728b945a7e_1264x536.png) 上述代碼分別將`張`轉換為10 16 2進制,我們能夠由16進制的`5f20`快速的得出`張`占用了兩個字節,實際上我們還可以在字符[編碼相關的站點](https://www.fileformat.info/info/unicode/char/5f20/index.htm)上來快速的找到`張`的utf編碼。 ![image-20210305085125647](https://img.kancloud.cn/07/04/0704afa089201556f1130700825760f5_1002x158.png) 錯誤的原因找到了,解決問題的重點便在于如何將UTF-16中占2個字節的編碼變換為變1個字節的ASSCI。 ### 單元大小為1字節的字符串 mozilla給出了如何將多字節字符組成的字符串變為1個字符組成的字符串的方案: ```javascript // convert a Unicode string to a string in which // each 16-bit unit occupies only one byte function toBinary(string) { const codeUnits = new Uint16Array(string.length); for (let i = 0; i < codeUnits.length; i++) { codeUnits[i] = string.charCodeAt(i); } return String.fromCharCode(...new Uint8Array(codeUnits.buffer)); } ``` 參數上述代碼,建立轉換方法如下: ### encodeURI 既然已經擴展到此程度了,我們不防再多擴展一下。其實我們早早的就接觸到了這種將非ASSCI編碼轉換為ASSCI編碼的方案。以我們用的百度翻譯(類似的例子有很多,基本上涉及到查詢都會有)為例: ![image-20210305085523472](https://img.kancloud.cn/23/6d/236dec393f39033c7419e189cac8ad66_1618x516.png) 請跟隨教程打開翻譯,然后查詢一個`你好`,請注意當前的URL。接下來,我們復制這個URL,然后再粘貼到任意的位置,你將得到如下鏈接: ``` https://fanyi.baidu.com/#zh/en/%E4%BD%A0%E5%A5%BD ``` 如果你在瀏覽器中打開[https://fanyi.baidu.com/#zh/en/%E4%BD%A0%E5%A5%BD](https://fanyi.baidu.com/#zh/en/%E4%BD%A0%E5%A5%BD),同樣可以正常訪問顯示為你好。 將這個`你好`變更為`%E4%BD%A0%E5%A5%BD`的過程稱為`encodeURI`,表示對URI進行編碼。目的是適用于http協議中非主體部分只支持ASSCI編碼的規則。`encodeURIComponent`函數則可以實現此功能。 ```typescript +++ b/first-app/src/app/login/login.component.ts @@ -20,7 +20,7 @@ export class LoginComponent implements OnInit { onSubmit(): void { console.log('點擊了登錄按鈕'); - const authString = this.teacher.username + ':' + this.teacher.password; + const authString = encodeURIComponent(this.teacher.username) + ':' + this.teacher.password; console.log(authString); const authToken = btoa(authString); console.log(authToken); ``` ![image-20210305095434083](https://img.kancloud.cn/b7/c8/b7c8b84a8d9f04553fccaa9e5a87dd05_800x120.png) ## 本節作業 1. 一個項目前后臺是統一的整體,我們剛剛在傳送用戶名密碼時增加了encodeURI轉碼,那么后臺是否也支持這種方式呢?請新創建一個新教師并使用`codedemo.club`做為用戶名嘗試登錄。 2. 如果我們想使密碼也支持中文的話該怎么辦呢? 3. 我們往往怕的是修改好了一個新功能,同時卻改壞了兩個老功能。中文用戶名的問題解決了,那么是否還支持英文登錄呢?請測試。 4. 請思索:在團隊開發中,如何保證你已有的功能不被其它團隊成員誤殺。 | 名稱 | 地址 | 備注 | | ---------------------- | ------------------------------------------------------------ | ---- | | Http身份認證 | [https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication) | | | RFC7617 Basic認證 | [https://tools.ietf.org/html/rfc7617](https://tools.ietf.org/html/rfc7617) | | | Base64的編碼與解碼 | [https://developer.mozilla.org/zh-CN/docs/Glossary/Base64](https://developer.mozilla.org/zh-CN/docs/Glossary/Base64) | | | 一個查詢字符編碼的網站 | [https://www.fileformat.info/info/unicode/char/68a6/index.htm](https://www.fileformat.info/info/unicode/char/68a6/index.htm) | | | ASCII | [https://zh.wikipedia.org/wiki/ASCII](https://zh.wikipedia.org/wiki/ASCII) | | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step3.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step3.3.zip) | |
                  <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>

                              哎呀哎呀视频在线观看