Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 欧美激情黄色,夜间免费小视频,成人久久免费视频

          整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          「SpringCloud」(四十)微服務(wù)實(shí)現(xiàn)單點(diǎn)登錄

          「SpringCloud」(四十)微服務(wù)實(shí)現(xiàn)單點(diǎn)登錄(SSO)授權(quán)服務(wù)器

          、單點(diǎn)登錄SSO介紹

          ??目前每家企業(yè)或者平臺(tái)都存在不止一套系統(tǒng),由于歷史原因每套系統(tǒng)采購于不同廠商,所以系統(tǒng)間都是相互獨(dú)立的,都有自己的用戶鑒權(quán)認(rèn)證體系,當(dāng)用戶進(jìn)行登錄系統(tǒng)時(shí),不得不記住每套系統(tǒng)的用戶名密碼,同時(shí),管理員也需要為同一個(gè)用戶設(shè)置多套系統(tǒng)登錄賬號(hào),這對系統(tǒng)的使用者來說顯然是不方便的。我們期望的是如果存在多個(gè)系統(tǒng),只需要登錄一次就可以訪問多個(gè)系統(tǒng),只需要在其中一個(gè)系統(tǒng)執(zhí)行注銷登錄操作,則所有的系統(tǒng)都注銷登錄,無需重復(fù)操作,這就是單點(diǎn)登錄(Single Sign On 簡稱SSO)系統(tǒng)實(shí)現(xiàn)的功能。
          ??單點(diǎn)登錄是系統(tǒng)功能的定義,而實(shí)現(xiàn)單點(diǎn)登錄功能,目前開源且流行的有CAS和OAuth2兩種方式,過去我們用的最多的是CAS,現(xiàn)在隨著SpringCloud的流行,更多人選擇使用SpringSecurity提供的OAuth2認(rèn)證授權(quán)服務(wù)器實(shí)現(xiàn)單點(diǎn)登錄功能。
          ??OAuth2是一種授權(quán)協(xié)議的標(biāo)準(zhǔn),任何人都可以基于這個(gè)標(biāo)準(zhǔn)開發(fā)Oauth2授權(quán)服務(wù)器,現(xiàn)在百度開放平臺(tái)、騰訊開放平臺(tái)等大部分的開放平臺(tái)都是基于OAuth2協(xié)議實(shí)現(xiàn),
          OAuth2.0定義了四種授權(quán)類型,最新版OAuth2.1協(xié)議定義了七種授權(quán)類型,其中有兩種因安全問題已不再建議使用

          【OAuth2.1 建議使用的五種授權(quán)類型】

          • Authorization Code 【授權(quán)碼授權(quán)】:用戶通過授權(quán)服務(wù)器重定向URL返回到客戶端后,應(yīng)用程序從URL中獲取授權(quán)碼,并使用授權(quán)碼請求訪問令牌。
          • PKCE【Proof Key for Code Exchange 授權(quán)碼交換證明密鑰】:授權(quán)碼類型的擴(kuò)展,用于防止CSRF和授權(quán)碼注入攻擊。
          • Client Credentials【客戶端憑證授權(quán)】:直接由客戶端使用客戶端 ID 和客戶端密鑰向授權(quán)服務(wù)器請求訪問令牌,無需用戶授權(quán),通常用于系統(tǒng)和系統(tǒng)之間的授權(quán)。
          • Device Code【設(shè)備代碼授權(quán)】:用于無瀏覽器或輸入受限的設(shè)備,使用提前獲取好的設(shè)備代碼獲取訪問令牌。
          • Refresh Token【刷新令牌授權(quán)】:當(dāng)訪問令牌失效時(shí),可以通過刷新令牌獲取訪問令牌,不需要用戶進(jìn)行交互。

          【OAuth2.1 不建議/禁止使用的兩種授權(quán)類型】

          • Implicit Flow【隱式授權(quán)】:隱式授權(quán)是以前推薦用于本機(jī)應(yīng)用程序和 JavaScript 應(yīng)用程序的簡化 OAuth 流程,其中訪問令牌立即返回,無需額外的授權(quán)代碼交換步驟。其通過HTTP重定向直接返回訪問令牌,存在很大的風(fēng)險(xiǎn),不建議使用,有些授權(quán)服務(wù)器直接禁止使用此授權(quán)類型。
          • Password Grant【密碼授權(quán)】:客戶端通過用戶名密碼向授權(quán)服務(wù)器獲取訪問令牌。因客戶端需收集用戶名和密碼,所以不建議使用,最新的 OAuth 2 安全最佳實(shí)踐完全不允許密碼授權(quán)。

          【SpringSecurity對OAuth2協(xié)議的支持】:

          ??通過SpringSecurity官網(wǎng)可知,通過長期的對OAuth2的支持,以及對實(shí)際業(yè)務(wù)的情景考慮,大多數(shù)的系統(tǒng)都不需要授權(quán)服務(wù)器,所以,Spring官方不再推薦使用spring-security-oauth2,SpringSecurity逐漸將spring-security-oauth2中的OAuth2登錄、客戶端、資源服務(wù)器等功能抽取出來,集成在SpringSecurity中,并單獨(dú)新建spring-authorization-server項(xiàng)目實(shí)現(xiàn)授權(quán)服務(wù)器功能。
          ??目前我們了解最多的是Spring Security OAuth對OAuth2協(xié)議的實(shí)現(xiàn)和支持,這里需要區(qū)分Spring Security OAuth和Spring Security是兩個(gè)項(xiàng)目,過去OAth2相關(guān)功能都在Spring Security OAuth項(xiàng)目中實(shí)現(xiàn),但是自SpringSecurity5.X開始,SpringSecurity項(xiàng)目開始逐漸增加Spring Security OAuth中的功能,自SpringSecurity5.2開始,添加了OAuth 2.0 登錄, 客戶端, 資源服務(wù)器的功能。但授權(quán)服務(wù)器的功能,并不打算集成在SpringSecurity項(xiàng)目中,而是新建了spring-authorization-server項(xiàng)目作為單獨(dú)的授權(quán)服務(wù)器:詳細(xì)介紹。spring-security實(shí)現(xiàn)的是OAuth2.1協(xié)議,spring-security-oauth2實(shí)現(xiàn)的是OAuth2.0協(xié)議。
          ??Spring未來的計(jì)劃是將 Spring Security OAuth 中當(dāng)前的所有功能構(gòu)建到 Spring Security 5.x 中。 在 Spring Security 達(dá)到與 Spring Security OAuth 的功能對等之后,他們將繼續(xù)支持錯(cuò)誤和安全修復(fù)至少一年。

          【GitEgg框架單點(diǎn)登錄實(shí)現(xiàn)計(jì)劃】:

          ??因spring-authorization-server目前最新發(fā)布版本0.2.3,部分功能仍在不斷地修復(fù)和完善,還不足以應(yīng)用到實(shí)際生產(chǎn)環(huán)境中,所以,我們目前使用spring-security-oauth2作為授權(quán)服務(wù)器,待后續(xù)spring-authorization-server發(fā)布穩(wěn)定版本后,再進(jìn)行遷移升級(jí)。

          【spring-security-oauth2默認(rèn)實(shí)現(xiàn)的授權(quán)類型】:

          • 隱式授權(quán)(Implicit Flow)【spring-authorization-server不再支持此類型】
          • 授權(quán)碼授權(quán)(Authorization Code)
          • 密碼授權(quán)(Password Grant)【spring-authorization-server不再支持此類型】
          • 客戶端憑證授權(quán)(Client Credentials)
          • 刷新令牌授權(quán) (Refresh Token)

          ??在GitEgg微服務(wù)框架中,gitegg-oauth已經(jīng)引入了spring-security-oauth2,代碼中使用了了Oauth2的密碼授權(quán)和刷新令牌授權(quán),并且自定義擴(kuò)展了【短信驗(yàn)證碼授權(quán)類型】和【圖形驗(yàn)證碼授權(quán)】,這其實(shí)是密碼授權(quán)的擴(kuò)展授權(quán)類型。
          ??目前,基本上所有的SpringCloud微服務(wù)授權(quán)方式都是使用的OAuth2密碼授權(quán)模式獲取token,可能你會(huì)有疑惑,為什么上面最新的Oauth2協(xié)議已經(jīng)不建議甚至是禁止使用密碼授權(quán)類型了,而我們GitEgg框架的系統(tǒng)管理界面還要使用密碼授權(quán)模式來獲取token?因?yàn)椴唤ㄗh使用密碼授權(quán)類型的原因是第三方客戶端會(huì)收集用戶名密碼,存在安全風(fēng)險(xiǎn)。而在我們這里,我們的客戶端是自有系統(tǒng)管理界面,不是第三方客戶端,所有的用戶名密碼都是我們自有系統(tǒng)的用戶名密碼,只要做好系統(tǒng)安全防護(hù),就可最大限度的避免用戶名密碼泄露給第三方的風(fēng)險(xiǎn)。

          ??在使用spring-security-oauth2實(shí)現(xiàn)單點(diǎn)登錄之前,首先我們一定要搞清楚單點(diǎn)登錄SSO、OAuth2、spring-security-oauth2的區(qū)別和聯(lián)系:

          • 單點(diǎn)登錄SSO是一種系統(tǒng)登錄解決方案的定義,企業(yè)內(nèi)部系統(tǒng)登錄以及互聯(lián)網(wǎng)上第三方QQ、微信、GitHub登錄等都是單點(diǎn)登錄。
          • OAuth2是一種系統(tǒng)授權(quán)協(xié)議,它包含多種授權(quán)類型,我們可以使用授權(quán)碼授權(quán)和刷新令牌授權(quán)兩種授權(quán)類型來實(shí)現(xiàn)單點(diǎn)登錄功能。
          • spring-security-oauth2是對OAuth2協(xié)議中授權(quán)類型的具體實(shí)現(xiàn),也是我們實(shí)現(xiàn)單點(diǎn)登錄功能實(shí)際用到的代碼。

          二、SpringSecurity單點(diǎn)登錄服務(wù)端和客戶端實(shí)現(xiàn)流程解析

          單點(diǎn)登錄業(yè)務(wù)流程時(shí)序圖:

          spring-security-oauth2單點(diǎn)登錄

          A系統(tǒng)(單點(diǎn)登錄客戶端)首次訪問受保護(hù)的資源觸發(fā)單點(diǎn)登錄流程說明

          • 1、用戶通過瀏覽器訪問A系統(tǒng)被保護(hù)的資源鏈接
          • 2、A系統(tǒng)判斷當(dāng)前會(huì)話是否登錄,如果沒有登錄則跳轉(zhuǎn)到A系統(tǒng)登錄地址/login
          • 3、A系統(tǒng)首次接收到/login請求時(shí)沒有state和code參數(shù),此時(shí)A系統(tǒng)拼接系統(tǒng)配置的單點(diǎn)登錄服務(wù)器授權(quán)url,并重定向至授權(quán)鏈接。
          • 4、單點(diǎn)登錄服務(wù)器判斷此會(huì)話是否登錄,如果沒有登錄,那么返回單點(diǎn)登錄服務(wù)器的登錄頁面。
          • 5、用戶在登錄頁面填寫用戶名、密碼等信息執(zhí)行登錄操作。
          • 6、單點(diǎn)登錄服務(wù)器校驗(yàn)用戶名、密碼并將登錄信息設(shè)置到上下文會(huì)話中。
          • 7、單點(diǎn)登錄服務(wù)器重定向到A系統(tǒng)的/login鏈接,此時(shí)鏈接帶有code和state參數(shù)。
          • 8、A系統(tǒng)再次接收到/login請求,此請求攜帶state和code參數(shù),系統(tǒng)A通過OAuth2RestTemplate請求單點(diǎn)登錄服務(wù)端/oauth/token接口獲取token。
          • 9、A系統(tǒng)獲取到token后,首先會(huì)對token進(jìn)行解析,并使用配置的公鑰對token進(jìn)行校驗(yàn)(非對稱加密),如果校驗(yàn)通過,則將token設(shè)置到上下文,下次訪問請求時(shí)直接從上下文中獲取。
          • 10、A系統(tǒng)處理完上下問會(huì)話之后重定向到登錄前請求的受保護(hù)資源鏈接。

          B系統(tǒng)(單點(diǎn)登錄客戶端)訪問受保護(hù)的資源流程說明

          • 1、用戶通過瀏覽器訪問B系統(tǒng)被保護(hù)的資源鏈接
          • 2、B系統(tǒng)判斷當(dāng)前會(huì)話是否登錄,如果沒有登錄則跳轉(zhuǎn)到B系統(tǒng)登錄地址/login
          • 3、B系統(tǒng)首次接收到/login請求時(shí)沒有state和code參數(shù),此時(shí)B系統(tǒng)拼接系統(tǒng)配置的單點(diǎn)登錄服務(wù)器授權(quán)url,并重定向至授權(quán)鏈接。
          • 4、單點(diǎn)登錄服務(wù)器判斷此會(huì)話是否登錄,因上面訪問A系統(tǒng)時(shí)登陸過,所以此時(shí)不會(huì)再返回登錄界面。
          • 5、單點(diǎn)登錄服務(wù)器重定向到B系統(tǒng)的/login鏈接,此時(shí)鏈接帶有code和state參數(shù)。
          • 6、B系統(tǒng)再次接收到/login請求,此請求攜帶state和code參數(shù),系統(tǒng)B通過OAuth2RestTemplate請求單點(diǎn)登錄服務(wù)端/oauth/token接口獲取token。
          • 7、B系統(tǒng)獲取到token后,首先會(huì)對token進(jìn)行解析,并使用配置的公鑰對token進(jìn)行校驗(yàn)(非對稱加密),如果校驗(yàn)通過,則將token設(shè)置到上下文,下次訪問請求時(shí)直接從上下文中獲取。
          • 8、B系統(tǒng)處理完上下問會(huì)話之后重定向到登錄前請求的受保護(hù)資源鏈接。

          spring-security-oauth2 單點(diǎn)登錄代碼實(shí)現(xiàn)流程說明:

          • 1、用戶通過瀏覽器訪問單點(diǎn)登錄被保護(hù)的資源鏈接
          • 2、SpringSecurity通過上下文判斷是否登錄(SpringSecurity單點(diǎn)登錄服務(wù)端和客戶端默認(rèn)都是基于session的),如果沒有登錄則跳轉(zhuǎn)到單點(diǎn)登錄客戶端地址/login
          • 3、單點(diǎn)登錄客戶端OAuth2ClientAuthenticationProcessingFilter攔截器通過上下文獲取token,因第一次訪問單點(diǎn)登錄客戶端/login時(shí),沒有code和state參數(shù),所以拋出UserRedirectRequiredException異常
          • 4、單點(diǎn)登錄客戶端捕獲UserRedirectRequiredException異常,并根據(jù)配置文件中的配置,組裝并跳轉(zhuǎn)到單點(diǎn)登錄服務(wù)端的授權(quán)鏈接/oauth/authorize,鏈接及請求中會(huì)帶相關(guān)配置參數(shù)
          • 5、單點(diǎn)登錄服務(wù)端收到授權(quán)請求,根據(jù)session判斷是否此會(huì)話是否登錄,如果沒有登錄則跳轉(zhuǎn)到單點(diǎn)登錄服務(wù)器的統(tǒng)一登錄界面(單點(diǎn)登錄服務(wù)端也是根據(jù)session判斷是否登錄的,在這里為了解決微服務(wù)的session集群共享問題,引入了spring-session-data-redis)
          • 6、用戶完成登錄操作后,單點(diǎn)登錄服務(wù)端重定向到單點(diǎn)登錄客戶端的/login鏈接,此時(shí)鏈接帶有code和state參數(shù)
          • 7、再次用到第三步的OAuth2ClientAuthenticationProcessingFilter攔截器通過上下文獲取token,此時(shí)上下文中肯定沒有token,所以會(huì)通過OAuth2RestTemplate請求單點(diǎn)登錄服務(wù)端/oauth/token接口使用重定向獲得的code和state換取token
          • 8、單點(diǎn)登錄客戶端獲取到token后,首先會(huì)對token進(jìn)行解析,并使用配置的公鑰對token進(jìn)行校驗(yàn)(非對稱加密),如果校驗(yàn)通過,則將token設(shè)置到上下文,下次訪問請求時(shí)直接從上下文中獲取。
          • 9、單點(diǎn)登錄客戶端處理完上下問會(huì)話之后重定向到登錄前請求的受保護(hù)資源鏈接。

          三、使用【授權(quán)碼授權(quán)】和【刷新令牌授權(quán)】來實(shí)現(xiàn)單點(diǎn)登錄服務(wù)器

          1、自定義單點(diǎn)登錄服務(wù)器頁面

          ??當(dāng)我們的gitegg-oauth作為授權(quán)服務(wù)器使用時(shí),我們希望定制自己的登錄頁等信息,下面我們自定義登錄、主頁、錯(cuò)誤提示頁、找回密碼頁。其他需要的頁面可以自己定義,比如授權(quán)確認(rèn)頁,我們此處業(yè)務(wù)不需要用戶二次確認(rèn),所以這里沒有自定義此頁面。

          • 在gitegg-oauth工程的pom.xml中添加Thymeleaf依賴,作為Spring官方推薦的模板引擎,我們使用Thymeleaf來實(shí)現(xiàn)前端頁面的渲染展示。
                  <!--thymeleaf 模板引擎 渲染單點(diǎn)登錄服務(wù)器頁面-->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-thymeleaf</artifactId>
                  </dependency>
          
          • 在GitEggOAuthController中新增頁面跳轉(zhuǎn)路徑
              /**
               * 單點(diǎn)登錄-登錄頁
               * @return
               */
              @GetMapping("/login") public String login() {
                  return "login";
              }
          
              /**
               * 單點(diǎn)登錄-首頁:當(dāng)直接訪問單點(diǎn)登錄系統(tǒng)成功后進(jìn)入的頁面。從客戶端系統(tǒng)進(jìn)入的,直接返回到客戶端頁面
               * @return
               */
              @GetMapping("/index") public String index() {
                  return "index";
              }
          
              /**
               * 單點(diǎn)登錄-錯(cuò)誤頁
               * @return
               */
              @GetMapping("/error") public String error() {
                  return "error";
              }
          
              /**
               * 單點(diǎn)登錄-找回密碼頁
               * @return
               */
              @GetMapping("/find/pwd") public String findPwd() {
                  return "findpwd";
              }
          
          • 在resources目錄下新建static(靜態(tài)資源)目錄和templates(頁面代碼)目錄,新增favicon.ico文件
          • 自定義登錄頁login.html代碼
          <!DOCTYPE html>
          <html xmlns:th="http://www.thymeleaf.org">
          <head>
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
              <meta name="description" content="統(tǒng)一身份認(rèn)證平臺(tái)">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>統(tǒng)一身份認(rèn)證平臺(tái)</title>
              <link rel="shortcut icon" th:href="@{/gitegg-oauth/favicon.ico}"/>
              <link rel="bookmark" th:href="@{/gitegg-oauth/favicon.ico}"/>
              <link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css}">
              <link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/bootstrap-validator-0.5.3/css/bootstrapValidator.css}">
              <link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/css/font-awesome.min.css}">
              <link type="text/css" rel="stylesheet" th:href="@{/gitegg-oauth/assets/css/login.css}">
              <!--[if IE]>
                  <script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/html5shiv.min.js}"></script>
              <![endif]-->
          </head>
          <body>
              <div class="htmleaf-container">
                  <div class="form-bg">
                          <div class="container">
                              <div class="row login_wrap">
                                  <div class="login_left">
                                      <span class="circle">
                                        <!-- <span></span>
                                        <span></span> -->
                                        <img th:src="@{/gitegg-oauth/assets/images/logo.svg}" class="logo" alt="logo">
                                      </span>
                                      <span class="star">
                                        <span></span>
                                        <span></span>
                                        <span></span>
                                        <span></span>
                                        <span></span>
                                        <span></span>
                                        <span></span>
                                        <span></span>
                                      </span>
                                      <span class="fly_star">
                                        <span></span>
                                        <span></span>
                                      </span>
                                      <p id="title">
                                          GitEgg Cloud 統(tǒng)一身份認(rèn)證平臺(tái)
                                      </p>
                                  </div>
                                  <div class="login_right">
                                      <div class="title cf">
                                          <ul class="title-list fr cf ">
                                              <li class="on">賬號(hào)密碼登錄</li>
                                              <li>驗(yàn)證碼登錄</li>
                                              <p></p>
                                          </ul>
                                      </div>
                                      <div class="login-form-container account-login">
                                          <form class="form-horizontal account-form" th:action="@{/gitegg-oauth/login}" method="post">
                                              <input type="hidden" class="form-control" name="client_id" value="gitegg-admin">
                                              <input id="user_type" type="hidden" class="form-control" name="type" value="user">
                                              <input id="user_mobileType" type="hidden" class="form-control" name="mobile" value="0">
                                              <div class="input-wrapper input-account-wrapper form-group">
                                                  <div class="input-icon-wrapper">
                                                      <i class="input-icon">
                                                          <svg t="1646301169630" class="icon" viewBox="64 64 896 896" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8796" width="1.2em" height="1.2em" fill="currentColor"><path d="M858.5 763.6c-18.9-44.8-46.1-85-80.6-119.5-34.5-34.5-74.7-61.6-119.5-80.6-0.4-0.2-0.8-0.3-1.2-0.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-0.4 0.2-0.8 0.3-1.2 0.5-44.8 18.9-85 46-119.5 80.6-34.5 34.5-61.6 74.7-80.6 119.5C146.9 807.5 137 854 136 901.8c-0.1 4.5 3.5 8.2 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c0.1 4.4 3.6 7.8 8 7.8h60c4.5 0 8.1-3.7 8-8.2-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z" p-id="8797"></path></svg>
                                                      </i>
                                                  </div>
                                                  <input type="text" class="input" name="username" placeholder="請輸入您的賬號(hào)">
                                              </div>
                                              <div class="input-wrapper input-psw-wrapper form-group">
                                                  <div class="input-icon-wrapper">
                                                      <i class="input-icon">
                                                          <svg t="1646302713220" class="icon" viewBox="64 64 896 896" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8931" width="1.2em" height="1.2em" fill="currentColor"><path d="M832 464h-68V240c0-70.7-57.3-128-128-128H388c-70.7 0-128 57.3-128 128v224h-68c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32zM332 240c0-30.9 25.1-56 56-56h248c30.9 0 56 25.1 56 56v224H332V240z m460 600H232V536h560v304z" p-id="8932"></path><path d="M484 701v53c0 4.4 3.6 8 8 8h40c4.4 0 8-3.6 8-8v-53c12.1-8.7 20-22.9 20-39 0-26.5-21.5-48-48-48s-48 21.5-48 48c0 16.1 7.9 30.3 20 39z" p-id="8933"></path></svg>
                                                      </i>
                                                  </div>
                                                  <input id="password" type="password" class="input" name="password" placeholder="請輸入您的密碼">
                                              </div>
                                              <div id="account-err" class="err-msg" style="width: 100%; text-align: center;"></div>
                                              <button type="submit" class="login-btn" id="loginSubmit">立即登錄</button>
                                              <div class="forget" id="forget">忘記密碼?</div>
                                          </form>
                                      </div>
                                      <div class="login-form-container mobile-login" style="display: none;">
                                          <form class="form-horizontal mobile-form" th:action="@{/gitegg-oauth/phoneLogin}" method="post">
                                              <input id="tenantId" type="hidden" class="form-control" name="tenant_id" value="0">
                                              <input id="type" type="hidden" class="form-control" name="type" value="phone">
                                              <input id="mobileType" type="hidden" class="form-control" name="mobile" value="0">
                                              <input id="smsId" type="hidden" class="form-control" name="smsId">
                                              <div class="input-wrapper input-account-wrapper form-group input-phone-wrapper">
                                                  <div class="input-icon-wrapper">
                                                      <i class="input-icon">
                                                          <svg t="1646302822533" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9067" width="1.2em" height="1.2em" fill="currentColor"><path d="M744 62H280c-35.3 0-64 28.7-64 64v768c0 35.3 28.7 64 64 64h464c35.3 0 64-28.7 64-64V126c0-35.3-28.7-64-64-64z m-8 824H288V134h448v752z" p-id="9068"></path><path d="M512 784m-40 0a40 40 0 1 0 80 0 40 40 0 1 0-80 0Z" p-id="9069"></path></svg>
                                                      </i>
                                                  </div>
                                                  <input id="phone" type="text" class="input" name="phone" maxlength="11" placeholder="請輸入手機(jī)號(hào)">
                                              </div>
                                              <div class="code-form form-group sms-code-wrapper">
                                                  <div class="input-wrapper input-sms-wrapper">
                                                      <div class="input-icon-wrapper">
                                                          <i class="input-icon">
                                                              <svg t="1646302879723" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9203" width="1.2em" height="1.2em" fill="currentColor"><path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32z m-40 110.8V792H136V270.8l-27.6-21.5 39.3-50.5 42.8 33.3h643.1l42.8-33.3 39.3 50.5-27.7 21.5z" p-id="9204"></path><path d="M833.6 232L512 482 190.4 232l-42.8-33.3-39.3 50.5 27.6 21.5 341.6 265.6c20.2 15.7 48.5 15.7 68.7 0L888 270.8l27.6-21.5-39.3-50.5-42.7 33.2z" p-id="9205"></path></svg>
                                                          </i>
                                                      </div>
                                                      <input id="code" type="text" class="input-code" name="code" maxlength="6" placeholder="請輸入驗(yàn)證碼">
                                                  </div>
                                                  <div class="input-code-wrapper">
                                                      <a id="sendBtn" href="javascript:sendCode();">獲取驗(yàn)證碼</a>
                                                  </div>
                                              </div>
                                              <div id="mobile-err" class="err-msg" style="width: 100%; text-align: center;"></div>
                                              <button type="submit" class="login-btn" id="loginSubmitByCode">立即登錄</button>
                                          </form>
                                      </div>
                                  </div>
                              </div>
                          </div>
                      </div>
                  <div class="related">
                      Copyrights ? 2021 GitEgg All Rights Reserved. 
                  </div>
              </div>
              <script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/jquery-2.1.4.min.js}"></script>
              <script type="text/javascript" th:src="@{/gitegg-oauth/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js}"></script>
              <script type="text/javascript" th:src="@{/gitegg-oauth/assets/bootstrap-validator-0.5.3/js/bootstrapValidator.js}"></script>
              <script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/md5.js}"></script>
              <script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/jquery.form.js}"></script>
              <script type="text/javascript" th:src="@{/gitegg-oauth/assets/js/login.js}"></script>
          </body>
          </html>
          
          
          • 自定義登錄login.js代碼
          var countdown=60;
          jQuery(function ($) {
              countdown=60;
          
              $('.account-form').bootstrapValidator({
                  message: '輸入錯(cuò)誤',
                  feedbackIcons: {
                      valid: 'glyphicon glyphicon-ok',
                      invalid: 'glyphicon glyphicon-remove',
                      validating: 'glyphicon glyphicon-refresh'
                  },
                  fields: {
                      username: {
                          container: '.input-account-wrapper',
                          message: '輸入錯(cuò)誤',
                          validators: {
                              notEmpty: {
                                  message: '用戶賬號(hào)不能為空'
                              },
                              stringLength: {
                                  min: 2,
                                  max: 32,
                                  message: '賬號(hào)長度范圍2-32個(gè)字符。'
                              },
                              regexp: {
                                  regexp: /^[a-zA-Z0-9_\.]+$/,
                                  message: '用戶名只能由字母、數(shù)字、點(diǎn)和下劃線組成'
                              }
                          }
                      },
                      password: {
                          container: '.input-psw-wrapper',
                          validators: {
                              notEmpty: {
                                  message: '密碼不能為空'
                              },
                              stringLength: {
                                  min: 5,
                                  max: 32,
                                  message: '密碼長度范圍6-32個(gè)字符。'
                              }
                          }
                      }
                  }
              });
          
              $('.mobile-form').bootstrapValidator({
                  message: '輸入錯(cuò)誤',
                  feedbackIcons: {
                      valid: 'glyphicon glyphicon-ok',
                      invalid: 'glyphicon glyphicon-remove',
                      validating: 'glyphicon glyphicon-refresh'
                  },
                  fields: {
                      phone: {
                          message: '輸入錯(cuò)誤',
                          container: '.input-phone-wrapper',
                          validators: {
                              notEmpty: {
                                  message: '手機(jī)號(hào)不能為空'
                              },
                              regexp: {
                                  regexp: /^1\d{10}$/,
                                  message: '手機(jī)號(hào)格式錯(cuò)誤'
                              }
                          }
                      },
                      code: {
                          container: '.input-sms-wrapper',
                          validators: {
                              notEmpty: {
                                  message: '驗(yàn)證碼不能為空'
                              },
                              stringLength: {
                                  min: 6,
                                  max: 6,
                                  message: '驗(yàn)證碼長度為6位。'
                              }
                          }
                      }
                  }
              });
          
              var options={
                  beforeSerialize: beforeFormSerialize,
                  success: formSuccess,//提交成功后執(zhí)行的回掉函數(shù)
                  error: formError,//提交失敗后執(zhí)行的回掉函數(shù)
                  headers : {"TenantId" : 0},
                  clearForm: true,//提交成功后是否清空表單中的字段值
                  restForm: true,//提交成功后是否充值表單中的字段值,即恢復(fù)到頁面加載是的狀態(tài)
                  timeout: 6000//設(shè)置請求時(shí)間,超過時(shí)間后,自動(dòng)退出請求,單位(毫秒)
              }
          
              var mobileOptions={
                  success: mobileFormSuccess,//提交成功后執(zhí)行的回掉函數(shù)
                  error: mobileFormError,//提交失敗后執(zhí)行的回掉函數(shù)
                  headers : {"TenantId" : 0},
                  clearForm: true,//提交成功后是否清空表單中的字段值
                  restForm: true,//提交成功后是否充值表單中的字段值,即恢復(fù)到頁面加載是的狀態(tài)
                  timeout: 6000//設(shè)置請求時(shí)間,超過時(shí)間后,自動(dòng)退出請求,單位(毫秒)
              }
          
              function beforeFormSerialize(){
                  $("#account-err").html("");
                  $("#username").val($.trim($("#username").val()));
                  $("#password").val($.md5($.trim($("#password").val())));
              }
          
              function formSuccess(response){
                  $(".account-form").data('bootstrapValidator').resetForm();
                  if (response.success)
                  {
                      window.location.href=response.targetUrl;
                  }
                  else
                  {
                      $("#account-err").html(response.message);
                  }
              }
          
          
              function formError(response){
                  $("#account-err").html(response);
              }
          
              function mobileFormSuccess(response){
                  $(".mobile-form").data('bootstrapValidator').resetForm();
                  if (response.success)
                  {
                      window.location.href=response.targetUrl;
                  }
                  else
                  {
                      $("#mobile-err").html(response.message);
                  }
              }
          
          
              function mobileFormError(response){
                  $("#mobile-err").html(response);
              }
          
              $(".account-form").ajaxForm(options);
          
              $(".mobile-form").ajaxForm(mobileOptions);
          
              $(".nav-left a").click(function(e){
                  $(".account-login").show();
                  $(".mobile-login").hide();
              });
          
              $(".nav-right a").click(function(e){
                  $(".account-login").hide();
                  $(".mobile-login").show();
              });
          
              $("#forget").click(function(e){
                  window.location.href="/find/pwd";
              });
          
              $('.title-list li').click(function(){
                  var liindex=$('.title-list li').index(this);
                  $(this).addClass('on').siblings().removeClass('on');
                  $('.login_right div.login-form-container').eq(liindex).fadeIn(150).siblings('div.login-form-container').hide();
                  var liWidth=$('.title-list li').width();
          
                  if (liindex==0)
                  {
                      $('.login_right .title-list p').css("transform","translate3d(0px, 0px, 0px)");
                  }
                  else {
                      $('.login_right .title-list p').css("transform","translate3d("+liWidth+"px, 0px, 0px)");
                  }
          
              });
          
          });
          
          function sendCode(){
              $(".mobile-form").data('bootstrapValidator').validateField('phone');
              if(!$(".mobile-form").data('bootstrapValidator').isValidField("phone"))
              {
                  return;
              }
          
              if(countdown !=60)
              {
                  return;
              }
              sendmsg();
              var phone=$.trim($("#phone").val());
              var tenantId=$("#tenantId").val();
              $.ajax({
                  //請求方式
                  type : "POST",
                  //請求的媒體類型
                  contentType: "application/x-www-form-urlencoded;charset=UTF-8",
                  dataType: 'json',
                  //請求地址
                  url : "/code/sms/login",
                  //數(shù)據(jù),json字符串
                  data : {
                      tenantId: tenantId,
                      phoneNumber: phone,
                      code: "aliValidateLogin"
                  },
                  //請求成功
                  success : function(result) {
                      $("#smsId").val(result.data);
                  },
                  //請求失敗,包含具體的錯(cuò)誤信息
                  error : function(e){
                      console.log(e);
                  }
              });
          };
          
          function sendmsg(){
              if(countdown==0){
                  $("#sendBtn").css("color","#181818");
                  $("#sendBtn").html("獲取驗(yàn)證碼");
                  countdown=60;
                  return false;
              }
              else{
                  $("#sendBtn").css("color","#74777b");
                  $("#sendBtn").html("重新發(fā)送("+countdown+")");
                  countdown--;
              }
              setTimeout(function(){
                  sendmsg();
              },1000);
          }
          
          

          2、授權(quán)服務(wù)器配置

          • 修改web安全配置WebSecurityConfig,將靜態(tài)文件添加到不需要授權(quán)就能訪問
              @Override
              public void configure(WebSecurity web) throws Exception {
                  web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
              }
          
          • 修改Nacos配置,將新增頁面訪問路徑添加到訪問白名單,使資源服務(wù)器配置ResourceServerConfig中的配置不進(jìn)行鑒權(quán)就能夠訪問,同時(shí)增加tokenUrls配置,此配置在網(wǎng)關(guān)不進(jìn)行鑒權(quán),但是需要OAuth2進(jìn)行Basic鑒權(quán),授權(quán)碼模式必須要用到此鑒權(quán)。
          # 以下配置為新增
            whiteUrls:
              - "/gitegg-oauth/oauth/login"
              - "/gitegg-oauth/oauth/find/pwd"
              - "/gitegg-oauth/oauth/error"
            authUrls:
              - "/gitegg-oauth/oauth/index"
            whiteUrls:
              - "/*/v2/api-docs"
              - "/gitegg-oauth/oauth/public_key"
              - "/gitegg-oauth/oauth/token_key"
              - "/gitegg-oauth/find/pwd"
              - "/gitegg-oauth/code/sms/login"
              - "/gitegg-oauth/change/password"
              - "/gitegg-oauth/error"
              - "/gitegg-oauth/oauth/sms/captcha/send"
            # 新增OAuth2認(rèn)證接口,此處網(wǎng)關(guān)放行,由認(rèn)證中心進(jìn)行認(rèn)證
            tokenUrls:
              - "/gitegg-oauth/oauth/token"
          
          • 因GitEgg框架使用用戶名+密碼再加密存儲(chǔ)的密碼,所以這里需要自定義登錄過濾器來做相應(yīng)處理,也可以用同樣的方式新增手機(jī)驗(yàn)證碼登錄、掃碼登錄等功能。
          package com.gitegg.oauth.filter;
          
          import cn.hutool.core.bean.BeanUtil;
          import com.gitegg.oauth.token.PhoneAuthenticationToken;
          import com.gitegg.platform.base.constant.AuthConstant;
          import com.gitegg.platform.base.domain.GitEggUser;
          import com.gitegg.platform.base.result.Result;
          import com.gitegg.service.system.client.feign.IUserFeign;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.security.authentication.AbstractAuthenticationToken;
          import org.springframework.security.authentication.AuthenticationServiceException;
          import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
          import org.springframework.security.core.Authentication;
          import org.springframework.security.core.AuthenticationException;
          import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
          import org.springframework.util.StringUtils;
          
          
          import javax.servlet.http.HttpServletRequest;
          import javax.servlet.http.HttpServletResponse;
          
          /**
           * 自定義登陸
           * @author GitEgg
           */
          public class GitEggLoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
          
              public static final String SPRING_SECURITY_RESTFUL_TYPE_PHONE="phone";
          
              public static final String SPRING_SECURITY_RESTFUL_TYPE_QR="qr";
          
              public static final String SPRING_SECURITY_RESTFUL_TYPE_DEFAULT="user";
          
              //  登陸類型:user:用戶密碼登陸;phone:手機(jī)驗(yàn)證碼登陸;qr:二維碼掃碼登陸
              private static final String SPRING_SECURITY_RESTFUL_TYPE_KEY="type";
          
              //  登陸終端:1:移動(dòng)端登陸,包括微信公眾號(hào)、小程序等;0:PC后臺(tái)登陸
              private static final String SPRING_SECURITY_RESTFUL_MOBILE_KEY="mobile";
          
              private static final String SPRING_SECURITY_RESTFUL_USERNAME_KEY="username";
          
              private static final String SPRING_SECURITY_RESTFUL_PASSWORD_KEY="password";
          
              private static final String SPRING_SECURITY_RESTFUL_PHONE_KEY="phone";
          
              private static final String SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY="code";
          
              private static final String SPRING_SECURITY_RESTFUL_QR_CODE_KEY="qrCode";
          
              @Autowired
              private IUserFeign userFeign;
          
              private boolean postOnly=true;
          
              @Override
              public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
          
                  if (postOnly && !"POST".equals(request.getMethod())) {
                      throw new AuthenticationServiceException(
                              "Authentication method not supported: " + request.getMethod());
                  }
          
                  String type=obtainParameter(request, SPRING_SECURITY_RESTFUL_TYPE_KEY);
                  String mobile=obtainParameter(request, SPRING_SECURITY_RESTFUL_MOBILE_KEY);
                  AbstractAuthenticationToken authRequest;
                  String principal;
                  String credentials;
          
                  // 手機(jī)驗(yàn)證碼登陸
                  if(SPRING_SECURITY_RESTFUL_TYPE_PHONE.equals(type)){
                      principal=obtainParameter(request, SPRING_SECURITY_RESTFUL_PHONE_KEY);
                      credentials=obtainParameter(request, SPRING_SECURITY_RESTFUL_VERIFY_CODE_KEY);
          
                      principal=principal.trim();
                      authRequest=new PhoneAuthenticationToken(principal, credentials);
                  }
                  // 賬號(hào)密碼登陸
                  else {
                      principal=obtainParameter(request, SPRING_SECURITY_RESTFUL_USERNAME_KEY);
                      credentials=obtainParameter(request, SPRING_SECURITY_RESTFUL_PASSWORD_KEY);
          
                      Result<Object> result=userFeign.queryUserByAccount(principal);
                      if (null !=result && result.isSuccess()) {
                          GitEggUser gitEggUser=new GitEggUser();
                          BeanUtil.copyProperties(result.getData(), gitEggUser, false);
                          if (!StringUtils.isEmpty(gitEggUser.getAccount())) {
                              principal=gitEggUser.getAccount();
                              credentials=AuthConstant.BCRYPT + gitEggUser.getAccount() + credentials;
                          }
                      }
                      authRequest=new UsernamePasswordAuthenticationToken(principal, credentials);
                  }
          
                  // Allow subclasses to set the "details" property
                  setDetails(request, authRequest);
                  return this.getAuthenticationManager().authenticate(authRequest);
              }
          
              private void setDetails(HttpServletRequest request,
                                      AbstractAuthenticationToken authRequest) {
                  authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
              }
          
              private String obtainParameter(HttpServletRequest request, String parameter) {
                  String result=request.getParameter(parameter);
                  return result==null ? "" : result;
              }
          }
          

          四、實(shí)現(xiàn)單點(diǎn)登錄客戶端

          ?? spring-security-oauth2提供OAuth2授權(quán)服務(wù)器的同時(shí)也提供了單點(diǎn)登錄客戶端的實(shí)現(xiàn),通用通過幾行注解即可實(shí)現(xiàn)單點(diǎn)登錄功能。
          1、新建單點(diǎn)登錄客戶端工程,引入oauth2客戶端相關(guān)jar包

                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-oauth2-client</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-security</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.security.oauth.boot</groupId>
                      <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                  </dependency>
          

          2、新建WebSecurityConfig類,添加@EnableOAuth2Sso注解

          @EnableOAuth2Sso
          @Configuration
          public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
          
              @Override
              protected void configure(HttpSecurity http) throws Exception {
                      http.authorizeRequests()
                              .anyRequest().authenticated()
                              .and()
                              .csrf().disable();
              }
          }
          

          3、配置單點(diǎn)登錄服務(wù)端相關(guān)信息

          server:
            port: 8080
            servlet:
              context-path: /ssoclient1
          security:
            oauth2:
              client:
                # 配置在授權(quán)服務(wù)器配置的客戶端id和secret
                client-id: ssoclient
                client-secret: 123456
                # 獲取token的url
                access-token-uri: http://127.0.0.1/gitegg-oauth/oauth/token
                # 授權(quán)服務(wù)器的授權(quán)地址
                user-authorization-uri: http://127.0.0.1/gitegg-oauth/oauth/authorize
              resource:
                jwt:
                  # 獲取公鑰的地址,驗(yàn)證token需使用,系統(tǒng)啟動(dòng)時(shí)會(huì)初始化,不會(huì)每次驗(yàn)證都請求
                  key-uri: http://127.0.0.1/gitegg-oauth/oauth/token_key
          

          備注:

          1、GitEgg框架中自定義了token返回格式,SpringSecurity獲取token的/oauth/token默認(rèn)返回的是ResponseEntity,自有系統(tǒng)登錄和單點(diǎn)登錄時(shí)需要做轉(zhuǎn)換處理。

          2、Gateway網(wǎng)關(guān)鑒權(quán)需要的公鑰地址是gitegg-oauth/oauth/public_key,單點(diǎn)登錄客戶端需要公鑰地址
          /oauth/token_key,兩者返回的格式不一樣,需注意區(qū)分。

          3、請求/oauth/tonen和/oauth/token_key時(shí),默認(rèn)都需要使用Basic認(rèn)證,也就是請求時(shí)需添加client_id和client_security參數(shù)。

          源碼地址:

          GitEgg: GitEgg 是一款開源免費(fèi)的企業(yè)級(jí)微服務(wù)應(yīng)用開發(fā)框架,旨在整合目前主流穩(wěn)定的開源技術(shù)框架,集成常用的最佳項(xiàng)目解決方案,實(shí)現(xiàn)可直接使用的微服務(wù)快速開發(fā)框架。

          GitHub - wmz1930/GitEgg: GitEgg 是一款開源免費(fèi)的企業(yè)級(jí)微服務(wù)應(yīng)用開發(fā)框架,旨在整合目前主流穩(wěn)定的開源技術(shù)框架,集成常用的最佳項(xiàng)目解決方案,實(shí)現(xiàn)可直接使用的微服務(wù)快速開發(fā)框架。

          境準(zhǔn)備:

          事先安裝好,pycharm
          打開File——>Settings——>Projext——>Project Interpriter


          點(diǎn)擊加號(hào)(圖中紅圈的地方)


          點(diǎn)擊紅圈中的按鈕


          選中第一條,點(diǎn)擊鉛筆,將原來的鏈接替換為(這里已經(jīng)替換過了):
          https://pypi.tuna.tsinghua.edu.cn/simple/
          點(diǎn)擊OK后,輸入requests-html然后回車
          選中requests-html后點(diǎn)擊Install Package


          等待安裝成功,關(guān)閉

          通過解析網(wǎng)頁源代碼

          實(shí)例內(nèi)容:
          從某博主的所有文章爬取想要的內(nèi)容。
          實(shí)例背景:
          從(https://me.csdn.net/weixin_44286745)博主的所有文章獲取各文章的標(biāo)題,時(shí)間,閱讀量。

          1. 導(dǎo)入requests_html中HTMLSession方法,并創(chuàng)建其對象
          from requests_html import HTMLSession
          session=HTMLSession()
          
          123
          1. 使用get請求獲取要爬的網(wǎng)站,得到該網(wǎng)頁的源代碼。
          html=session.get("https://me.csdn.net/weixin_44286745").html
          
          12
          • 找到所有文章
            allBlog=html.xpath("//dl[@class='tab_page_list']") 
          1
          • 進(jìn)入網(wǎng)站主頁(本例: https://me.csdn.net/weixin_44286745)
          • 文章空白處右鍵檢查可以定位到這文章的標(biāo)簽

          • 其他文章一樣操作,然后找到所有文章共同的標(biāo)記(這里所有文章的class都是‘my_tab_page_con’)
          • xpath 可以遍歷html的各個(gè)標(biāo)簽和屬性,來定位到我們需要的信息的位置,并提取。
          • 網(wǎng)頁分析獲取標(biāo)題,閱讀量,日期。
          for i in allBlog:
              title=i.xpath("dl/dt/h3/a")[0].text
              views=i.xpath("//div[@class='tab_page_b_l fl']")[0].text
              date=i.xpath("//div[@class='tab_page_b_r fr']")[0].text
              print(title +' ' +views +' ' + date )
          12345

          網(wǎng)頁分析:

          • 因?yàn)橛卸嗥恼拢謩e獲取使用for循環(huán),上述代碼已得到所有文章所以i表示一篇文章
          • 第二行代碼獲取文章標(biāo)題,獲取文章類似,鼠標(biāo)放到標(biāo)題上右鍵檢查,因?yàn)槲恼轮挥幸粋€(gè)標(biāo)題所以用絕對路徑也可以按標(biāo)簽一層層進(jìn)到標(biāo)題位置。

            • xpath返回的是列表,我們要第一個(gè)所以要加下標(biāo)(列表里也只有一個(gè)元素),要輸出的是文本,所以,text獲取文本。
            • 閱讀量和時(shí)間也是重復(fù)的操作


            • 可以用相對路徑也可以用絕對路徑,一般都是用相對路徑,格式仿照代碼。
            • 第五行代碼,每得到一篇文章的信息就輸出,遍歷完就可以獲得全部的信息。


          完整代碼:

          from requests_html import HTMLSession
          session=HTMLSession()
          
          
          html=session.get("https://me.csdn.net/weixin_44286745").html
          
          allBlog=html.xpath("//dl[@class='tab_page_list']")
          
          for i in allBlog:
              title=i.xpath("dl/dt/h3/a")[0].text
              views=i.xpath("//div[@class='tab_page_b_l fl']")[0].text
              date=i.xpath("//div[@class='tab_page_b_r fr']")[0].text
              print(title +' ' +views +' ' + date )
          
          1234567891011121314
          • 可以自己爬其他東西,如文章圖片,動(dòng)手試試吧!!!
            未完待續(xù)

          通過html請求

          自動(dòng)化

          喜歡編程的小伙伴可以加一下小編的Q群867067945大家一起交流學(xué)習(xí),群里也有專業(yè)的大神給你解答難題

          本文的文字及圖片來源于網(wǎng)絡(luò)加上自己的想法,僅供學(xué)習(xí)、交流使用,不具有任何商業(yè)用途,版權(quán)歸原作者所有,如有問題請及時(shí)聯(lián)系我們以作處理。

          文來源于黑馬程序員技術(shù)論壇:http://bbs.itheima.com/thread-430137-1-2.html

          Struts2 的Action中若希望訪問Session對象,可采用兩種方式:

          1、從ActionContext中獲取;

          2、實(shí)現(xiàn)SessionAware接口。

          1、從ActionContext中獲取:

          import java.util.Map;

          import com.opensymphony.xwork2.ActionContext;

          import com.opensymphony.xwork2.ActionSupport;

          public class SessionTestAction extends ActionSupport {

          public String execute() {

          ActionContext actionContext=ActionContext.getContext();

          Map session=actionContext.getSession();

          session.put("USER_NAME", "Test User");

          return SUCCESS;

          }

          }

          import java.util.Map; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class SessionTestAction extends ActionSupport { public String execute() { ActionContext actionContext=ActionContext.getContext(); Map session=actionContext.getSession(); session.put("USER_NAME", "Test User"); return SUCCESS; } }

          2、實(shí)現(xiàn)SessionAware接口:

          import java.util.Map;

          import org.apache.struts2.interceptor.SessionAware;

          import com.opensymphony.xwork2.ActionSupport;

          public class SessionTest1Action extends ActionSupport implements SessionAware {

          private Map session;

          public void setSession(Map session) {

          this.session=session;

          }

          public String execute() {

          this.session.put("USER_NAME", "Test User 1");

          return SUCCESS;

          }

          }

          import java.util.Map; import org.apache.struts2.interceptor.SessionAware; import com.opensymphony.xwork2.ActionSupport; public class SessionTest1Action extends ActionSupport implements SessionAware { private Map session; public void setSession(Map session) { this.session=session; } public String execute() { this.session.put("USER_NAME", "Test User 1"); return SUCCESS; } }

          進(jìn)一步閱讀Struts2.1.8.1源碼,SessionAware接口的實(shí)現(xiàn)方式如下:

          struts-default.xml配置:

          <interceptors>

          <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/>

          </interceptors>

          <interceptor-stack name="defaultStack">

          <interceptor-ref name="servletConfig"/>

          </interceptor-stack>

          <interceptors> … <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/> … </interceptors> <interceptor-stack name="defaultStack"> … <interceptor-ref name="servletConfig"/> … </interceptor-stack>

          打開ServletConfigInterceptor.java源碼:

          public String intercept(ActionInvocation invocation) throws Exception {

          final Object action=invocation.getAction();

          final ActionContext context=invocation.getInvocationContext();

          if (action instanceof SessionAware) {

          ((SessionAware) action)。setSession(context.getSession());

          }

          return invocation.invoke();

          }

          public String intercept(ActionInvocation invocation) throws Exception { final Object action=invocation.getAction(); final ActionContext context=invocation.getInvocationContext(); … if (action instanceof SessionAware) { ((SessionAware) action)。setSession(context.getSession()); } … return invocation.invoke(); }

          即在攔截器處理過程中發(fā)現(xiàn)目標(biāo)Action實(shí)現(xiàn)了SessionAware接口,便會(huì)調(diào)用Action中已經(jīng)實(shí)現(xiàn)的setSession(…) 方法,將ActionContext中包裝的Session注入目標(biāo)Action中。目標(biāo)Action也就可以進(jìn)一步對Session進(jìn)行操作了。


          主站蜘蛛池模板: 激情内射日本一区二区三区| 91久久精品一区二区| 一区二区三区内射美女毛片| 一夲道无码人妻精品一区二区| 日韩精品人妻一区二区三区四区| 清纯唯美经典一区二区| 国产传媒一区二区三区呀| 亚洲一区二区三区免费视频| 国产伦精品一区二区三区视频金莲| 日本一区二区三区免费高清| 91精品国产一区| 亚洲综合国产一区二区三区| 日韩久久精品一区二区三区| 日本一区二区三区爆乳| 久久久久人妻一区精品色| 国产自产V一区二区三区C| 国产在线观看一区精品| 丝袜美腿高跟呻吟高潮一区| 香蕉久久ac一区二区三区| 99精品国产一区二区三区2021| 99久久人妻精品免费一区| 无码精品视频一区二区三区| 国产精品丝袜一区二区三区| 精品久久久久中文字幕一区| 日本视频一区二区三区 | 成人区精品一区二区不卡亚洲| 亚洲夜夜欢A∨一区二区三区| 蜜桃视频一区二区| 日本精品少妇一区二区三区 | 免费在线视频一区| 国产亚洲福利一区二区免费看 | 99热门精品一区二区三区无码 | 国产高清在线精品一区二区三区 | 成人久久精品一区二区三区| 五月婷婷一区二区| 成人无码精品一区二区三区| 精品国产日韩亚洲一区在线 | 亚洲国产精品无码第一区二区三区| 精品一区二区三区自拍图片区| 91视频一区二区| 无码av中文一区二区三区桃花岛|