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

                本節開始時以**一卡通**為例進行認證的簡單講解。在使用**一卡通**時關鍵的一環在于:**一卡通**與學生的綁定及解綁。 ## 用戶綁定 用戶綁定實際上是建立一種映射關系。在校園生活中它是一種**一卡通**與**學生**的映射關系,由學生的學號具有唯一性,所以還可以說是一種**一卡通**與**學號**的映射關系;在前后端分離的應用中它是一種**auth-token**與數據表中**teacher**的映射關系,由于teacher的id具有唯一性,所以還可以說是一種**auth-token**與**teacherId**之間的映射關系。 在java的世界里HashMap這種數據類型便是為了解決此類的映射問題而生的。下面開始打開后臺來到TeacherController的Login方法查看其實現的邏輯,并對其調用的服務層進行升級。以使得服務層提供:當用戶名與密碼校驗成功后,對應的建立**auth-token**與**teacherId**之間的關系,從而達到用戶綁定的目的。 controller/TeacherController.java ```java @PostMapping("login") public boolean login(@RequestBody Teacher teacher) { return this.teacherService.login(teacher.getUsername(), teacher.getPassword()); } ``` 查看C層調用獲取知其調用的是M層的login方法,于是找到對應的service/TeacherServiceImpl.java對應方法 service/TeacherServiceImpl.java ```java @Override public boolean login(String username, String password) { Teacher teacher = this.teacherRepository.findByUsername(username); return this.validatePassword(teacher, password); } ``` 升級如下: ```java @Service public class TeacherServiceImpl implements TeacherService { /** auth-token與teacherId的映射 */ private HashMap<String, Long> authTokenTeacherIdHashMap = new HashMap<>(); ? ... @Override public boolean login(String username, String password) { Teacher teacher = this.teacherRepository.findByUsername(username); if (!this.validatePassword(teacher, password)) { // 認證不成功直接返回 return false; } // 認證成功,進行auth-token與teacherId的綁定綁定 return true; } ``` * ? HashMap理解為鍵值對的存儲類型。HashMap<String, Long>表示鍵的類型為String,值的類型為Long。存儲示例:`{"abcd" -> 123, "bcd" -> 456}`。 向hashMap中存數據使用`put`方法,比如預將相應的`auth-token`與`teacherId`進行綁定,代碼如下: ```java ... // 認證成功,進行auth-token與teacherId的綁定綁定 this.authTokenTeacherIdHashMap.put("header中auth-token的值★"?, teacher.getId()?); return true; } ``` * put(鍵, 值); * ? 類型是String,表示鍵 * ? 類型為Long,表示值 ## 獲取header 認證的代碼準備好后,是否可以在TeacherServiceImpl中獲取header中的auth-token的值成為了是否能夠成功完成認證功能的關鍵因素。回想下前面在過濾器中已經成功的獲取過header中的token信息: filter/TokenFilter.java ```java String token = request.getHeader(this.TOKEN_KEY); ``` 觀察上述的代碼發現只要能夠獲取相應的request,便能夠獲取對應的token。在spring中,可以像注入其它的服務一樣注入HttpServletRequest: service/TeacherServiceImpl.java ```java public class TeacherServiceImpl implements TeacherService { ... private final? HttpServletRequest request; private TeacherRepository teacherRepository; ... @Autowired public TeacherServiceImpl(TeacherRepository teacherRepository, HttpServletRequest? request) { this.teacherRepository = teacherRepository; this.request = request; } ``` * ? 使用final關鍵字聲明該變量不可變,這有一些當前還看不到的優點 * ? 將HttpServletRequest聲明到以@Autowired注解的構造函數中。spring會自動將需要的HttpServletRequest注入 有了request后獲取header是一件非常輕松的事情。 service/TeacherServiceImpl.java ```java ... // 認證成功,進行auth-token與teacherId的綁定綁定 this.authTokenTeacherIdHashMap.put(this.request.getHeader("auth-token"), teacher.getId()); return true; } ``` ### 測試一下 service/TeacherServiceImpl.java ```java public class TeacherServiceImpl implements TeacherService { private final static Logger logger = LoggerFactory.getLogger(TeacherServiceImpl.class); ... // 認證成功,進行auth-token與teacherId的綁定綁定 logger.info("獲取到的auth-token為" + this.request.getHeader("auth-token")); this.authTokenTeacherIdHashMap.put(this.request.getHeader("auth-token"), teacher.getId()); return true; } ``` 嘗試啟動后臺后得到如下錯誤: ``` Error:(21, 45) java: 無法將類 com.mengyunzhi.springbootstudy.service.TeacherServiceImpl中的構造器 TeacherServiceImpl應用到給定類型; 需要: com.mengyunzhi.springbootstudy.repository.TeacherRepository,javax.servlet.http.HttpServletRequest 找到: com.mengyunzhi.springbootstudy.repository.TeacherRepository 原因: 實際參數列表和形式參數列表長度不同 ``` 提示說:單元測試文件實例化TeacherServiceImpl時,僅接收到了一個參數,還差一個。這是由于在構造函數中加入了新的`HttpServletRequest`造成的,對應修正如下: serivce/TeacherServiceImplTest.java ```java public class TeacherServiceImplTest { private TeacherServiceImpl teacherService; private TeacherRepository teacherRepository; private HttpServletRequest httpServletRequest; ? @Before public void before() { this.teacherRepository = Mockito.mock(TeacherRepository.class); this.httpServletRequest = Mockito.mock(HttpServletRequest.class); ? TeacherServiceImpl teacherService = new TeacherServiceImpl(this.teacherRepository, this.httpServletRequest?); this.teacherService = Mockito.spy(teacherService); } ``` 再次嘗試運行后臺成功。在數據庫中維護一個測試教師,并在IDEA中新建http request如下: ``` POST http://localhost:8080/Teacher/login Content-type: application/json; {"username": "panjie", "password":"yunzhi"} ``` 請求結果: ``` POST http://localhost:8080/Teacher/login HTTP/1.1 200 auth-token: 0076f815-2b8b-4c66-8df0-48fac5a4104e Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 17 Feb 2020 10:02:14 GMT true Response code: 200; Time: 159ms; Content length: 4 bytes ``` 日志情況: ``` 2020-02-17 18:02:13.903 INFO 59059 --- [nio-8080-exec-4] c.m.springbootstudy.filter.TokenFilter : 原token無效,發布的新的token為0076f815-2b8b-4c66-8df0-48fac5a4104e ... 2020-02-17 18:02:14.056 INFO 59059 --- [nio-8080-exec-4] c.m.s.service.TeacherServiceImpl : 獲取到的auth-token為0076f815-2b8b-4c66-8df0-48fac5a4104e ``` 進行用戶綁定時,過濾器首先為沒有推帶auth-token的請求分發auth-token。login方法隨后將其分發的auth-token與當前的認證用戶進行綁定。符合預期。 ## 用戶解綁 用戶解綁的流程與用戶綁定流程稍有不同,體現在: 一、用戶綁定時需要首先驗證用戶名密碼是否正確,只有當用戶名密碼正確的時候,才進行綁定。而用戶解除綁定不需要,只要用戶攜帶了分發給他的**一卡通**,后臺便能夠對應的解除**一卡通**與原有用戶的綁定。 二、用戶綁定時使用的是hashMap的put方法先添加String及Long的映射,而解綁中對應使用hashMap的remove方法。 與login方法相對應,解綁方法命名為logout: service/TeacherService.java ```java /** * 用戶注銷 * 系統可以根據HttpServletRequest獲取到header中的令牌令牌 * 所以注銷方法不需要傳入任何參數 */ void logout(); ``` 實現類: service/TeacherServiceImpl.java ```java @Override public void logout() { // 獲取auth-token // 刪除hashMap中對應auth-token的映射 } ``` 補充代碼: service/TeacherServiceImpl.java ```java @Override public void logout() { // 獲取auth-token String authToken = this.request.getHeader("auth-token"); logger.info("獲取到的auth-token為" + this.request.getHeader("auth-token")); // 刪除hashMap中對應auth-token的映射 this.authTokenTeacherIdHashMap.remove(authToken); } ``` ### C層 增加數據轉發的C層,為下一步的測試做準備: controller/TeacherController.java ```java @GetMapping("logout") public void login() { this.teacherService.logout(); } ``` ### 測試 成功測試解除綁定方法的前提先進行用戶的綁定。用戶綁定成功后,再使用其對應的**auth-token**來進行解綁操作的測試。 重新啟動后臺,先發起用戶綁定請求,獲取對應的auth-token,請求結果如下: ```java POST http://localhost:8080/Teacher/login HTTP/1.1 200 auth-token: 4048387e-3d87-4553-862e-37fb2c2a81cf ★ Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 18 Feb 2020 01:19:41 GMT true ``` * ★ 記錄后臺下發的auth-token 接著使用得到的auth-token發起解綁操作: ``` GET http://localhost:8080/Teacher/logout auth-token: 4048387e-3d87-4553-862e-37fb2c2a81cf ``` 響應信息: ``` GET http://localhost:8080/Teacher/logout HTTP/1.1 200 auth-token: 4048387e-3d87-4553-862e-37fb2c2a81cf Content-Length: 0 Date: Tue, 18 Feb 2020 01:25:07 GMT <Response body is empty> ``` 結果返回了200,說明響應信息正常。 ## 我是誰 雖然已經成功的完成了用戶綁定與解綁功能。看總感覺心里面少點什么,原因是當前的測試僅僅能夠證明綁定與解綁兩個操作是可用的,但是否真正的完成用戶綁定與解綁的實際工作卻不得而知(通過觀察源代碼來判斷功能是否正常是最不負責的行為)。若想驗證綁定與解綁是否成功,則還需要一個"我是誰"的接口來進行驗證。 * 在進行用戶綁定前,訪問"我是誰"接口應該獲取到null信息,表示:當前沒有進行用戶認證,所以你誰也是不是。 * 用戶綁定成功后,訪問"我是誰"接口應該返回綁定的用戶信息。 * 重新使用其它用戶綁定后,訪問"我是誰"接口應該返回剛剛綁定的用戶 * 解除綁定后,訪問"我是誰"接口應該返回null信息 service/TeacherService.java ```java /** * 我是誰 * @return 當前登錄用戶。用戶未登錄則返回null */ Teacher me(); ``` 實現類: service/TeacherServiceImpl.java ```java @Override public Teacher me() { // 獲取authToken // 獲取authToken映射的teacherId // 未獲取到teacherId,說明該auth-token未與用戶進行綁定,返回null // 如獲取到teacherId,則由數據庫中獲取teacher并返回 } ``` 補充功能性代碼: service/TeacherServiceImpl.java ```java @Override public Teacher me() { // 獲取authToken String authToken = this.request.getHeader("auth-token"); // 獲取authToken映射的teacherId Long teacherId = this.authTokenTeacherIdHashMap.get(authToken); if (teacherId == null) { // 未獲取到teacherId,說明該auth-token未與用戶進行綁定,返回null return null; } // 如獲取到teacherId,則由數據庫中獲取teacher并返回 Optional<Teacher> teacherOptional = this.teacherRepository.findById(teacherId); return teacherOptional.get(); } ``` ### C層數據轉發 controller/TeacherController.java ```java @GetMapping("me") public Teacher me() { return this.teacherService.me(); } ``` ### 測試 重新啟動后臺后依次做如下測試: <hr> 測試一:在進行用戶綁定前,訪問"我是誰"接口應該獲取到null信息,表示:當前沒有進行用戶認證,所以你誰也是不是。 ``` GET http://localhost:8080/Teacher/me ``` 請求結果:未返回任何信息。 ``` GET http://localhost:8080/Teacher/me HTTP/1.1 200 auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ★ Content-Length: 0 Date: Tue, 18 Feb 2020 01:43:46 GMT <Response body is empty> ``` * ★ 記錄分發的令牌,在以后的測試請求中均使用該令牌 <hr> 測試二:用戶綁定成功后,訪問"我是誰"接口應該返回綁定的用戶信息。 先訪問login接口,然后再請求me接口 login ``` POST http://localhost:8080/Teacher/login Content-type: application/json; auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ? {"username": "panjie", "password":"yunzhi"} ``` * ? 使用上一步接收到的令牌發起訪問。 返回信息: ``` POST http://localhost:8080/Teacher/login HTTP/1.1 200 auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ? Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 18 Feb 2020 01:46:07 GMT true ? ``` * ? 返回了原令牌,符合預期 * ? 返回結果為true,說明登錄成功符合預期 發起對me接口的請求: ``` GET http://localhost:8080/Teacher/me auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ``` 請求結果: ``` GET http://localhost:8080/Teacher/me HTTP/1.1 200 auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 18 Feb 2020 01:48:19 GMT { "id": 1, "name": null, "sex": true, "username": "panjie", "email": null, "createTime": null, "updateTime": null, "password": "yunzhi" } ``` 返回了登錄用戶的信息,符合預期。 <hr> 測試三: 重新使用其它用戶綁定后,訪問"我是誰"接口應該返回剛剛綁定的用戶 在數據庫中新建測試教師:用戶名liuyuxuan ,密碼:yunzhi,然后訪問login接口發起認證。 ``` POST http://localhost:8080/Teacher/login Content-type: application/json; auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 {"username": "liuyuxuan", "password":"yunzhi"} ``` 接著訪問me接口,驗證是否重新綁定成功。 ``` GET http://localhost:8080/Teacher/me auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ``` 響應結果: ``` GET http://localhost:8080/Teacher/me HTTP/1.1 200 auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 18 Feb 2020 01:51:59 GMT { "id": 2, "name": null, "sex": true, "username": "liuyuxuan", ? "email": null, "createTime": null, "updateTime": null, "password": "yunzhi" } ``` * ? 當前登錄用戶名由panjie變更為liuyuxuan,說明重新綁定成功 <hr> 測試四:解除綁定后,訪問"我是誰"接口應該返回null信息。 首先發起logout接口訪問: ``` GET http://localhost:8080/Teacher/logout auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ``` 響應信息: ``` GET http://localhost:8080/Teacher/logout HTTP/1.1 200 auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 Content-Length: 0 Date: Tue, 18 Feb 2020 01:53:35 GMT <Response body is empty> ``` 再發起me接口訪問: ``` GET http://localhost:8080/Teacher/me auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 ``` 響應信息為空,說明解除綁定成功。 ``` GET http://localhost:8080/Teacher/me HTTP/1.1 200 auth-token: 8f03f582-4284-4b4e-85f2-258f30f016c0 Content-Length: 0 Date: Tue, 18 Feb 2020 01:54:05 GMT <Response body is empty> ``` 至此一個基本的登錄、注銷、我是誰的功能開發完畢。 ## 重構代碼 重構代碼是提高代碼質量的重要一環,好的代碼講求的是:易閱讀、易維護、對擴展開放、對修改關閉。本例中在獲取認證令牌時,使用了大量的字符串`auth-token`。這為日志修改該認識關鍵字挖下了坑:我們希望在日后的維護過程中,可以很輕松的修改該字符串,比如將`auth-token`修正為`x-auth=token`、`web-auth-token`或`app-auth-token`等。而大量字符串出現在源代碼中將要求以后完成該字符串修改工作的成員對項目極其熟悉,以致于不會漏掉任何一個`auth-token`(雖然這可以使用編輯器的查找替換工作完成,但此時我們更關注是一種編程的習慣)。 解決該問題的方法很簡單:`auth-token`在整個項目中只出現一次,只為其它使用到該值的地方,全部引用該變量。比如本項目中`auth-token`首次出現在過濾器`TokenFilter`中,則在`TeacherServiceImpl`中所有使用字符串`auth-token`地方,均應該使用`TokenFilter`中的`auth-token`。為此,首先將`TokenFilter`中記錄`auth-token`的屬性聲明為公有靜態的,然后在`TeacherServiceImpl`中引用其值。 filter/TokenFilter.java ```java public class TokenFilter extends HttpFilter { private String TOKEN_KEY = "auth-token"; ? public static String TOKEN_KEY = "auth-token"; ? ... String token = request.getHeader(this.TOKEN_KEY); ? String token = request.getHeader(TOKEN_KEY); ? ``` 然后將`TeacherServiceImpl`所有使用`auth-token`字符串的地方替換為`TokenFilter.TOKEN_KEY`,比如: service/TeacherServiceImpl.java ```java String authToken = this.request.getHeader(TokenFilter.TOKEN_KEY); ``` # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.6](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step5.2.6) | - |
                  <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>

                              哎呀哎呀视频在线观看