1. 程式人生 > >Spring Boot+Spring Security+Spring Social專案開發(六):開發APP認證框架、Spring Security OAuth核心原始碼、重構三種登入方式、重構社交登入

Spring Boot+Spring Security+Spring Social專案開發(六):開發APP認證框架、Spring Security OAuth核心原始碼、重構三種登入方式、重構社交登入

說在前面

博主最近會有很多專案跟大家一起分享,做完後會上傳github上的,希望讀友們能給博主提提意見哈哈

這個專案是第三方登入和安全方面的,關於後臺與app和網站的登入連線操作的實戰專案

各位如果可以就給我star哈哈謝謝啦

Spring Security OAuth開發APP認證框架

  • 開發繁瑣,自己處理cookie的儲存再讀出來
  • 安全性和客戶體驗差,驗證工作伺服器自己做,直接拿sessionid就可以獲取使用者身份,設定超時時間的話會讓使用者頻繁登入,使用者體驗差
  • 有些前端技術不支援cookie ,如小程式.

Token方式開發

  • refresh_token 重新整理令牌
  • access_token 認證令牌
  • Cookie的方式是往瀏覽器裡寫一個sessionId
  • Token方式是直接發給使用者一個token,使用者訪問時要帶著令牌上來,應用伺服器不再把使用者資訊儲存在session裡,根據使用者帶著的token來判斷使用者是誰,它能幹什麼等等
  • 令牌的表現形式就是字串,使用者帶著令牌的方式不是通過cookie來帶的,而是http請求引數的形式
    可以在令牌上加一些技術手段增加安全性,用token重新整理的機制

搭建服務提供商

  • 認證伺服器:4種授權模式,token的生成儲存
  • 資源伺服器:資源(rest服務),Spring Security過濾器鏈加上OAuth2AuthenticationProcessingFilter:把token拿出來,通過配置的儲存策略去對應的儲存中拿到token的資訊,根據資訊是否存在和是否有許可權等等判斷來決定是否能訪問資源
    • 不希望走四種授權模式,自定義認證模式
    • 實現一個標準的OAuth2協議中Provider角色的主要功能
    • 重構之前的三種認證方式的程式碼,使其支援Token
  • 高階特性:token生成方式(JWT,SSO單點登入)

從認證伺服器入手寫程式碼

在app中新建authentication包,裡面放自定義成功處理器和失敗處理器

@Configuration
@EnableAuthorizationServer //加上這句註解就已經實現了認證伺服器

介紹一個很優秀的除錯工具RestLet Client

使用授權碼模式和密碼模式獲取token
附上兩個截圖,在這上面可以模擬請求檢視返回的結果
這裡寫圖片描述
這裡寫圖片描述

建TiHomResourceServerConfig類配置資源伺服器,只需要加上兩個註解

@Configuration
@EnableResourceServer

Spring Security OAuth核心原始碼

這裡寫圖片描述
綠色是類,藍色是介面

  • /oauth/token請求令牌
  • TokenEndpoint,負責處理上面那個請求,當它收到請求之後,調ClientDetailsService
  • UserDetailsService是用來讀取使用者資訊的,ClientDetailsService是讀取第三方應用的資訊的.這個介面通過傳遞過來的client_id來獲取ClientDetails
  • ClientDetails封裝第三方應用的資訊
  • TokenEndpoint會建立一個TokenRequest物件,這個物件封裝了/oauth/token這個請求中其他的幾個引數的資訊,把ClientDetails也放進TokenRequest中,TokenRequest調TokenGranter令牌授權者這個介面
  • TokenGranter這個介面後面封裝了四種授權模式,以請求傳上來的grant_type去找一種實現,無論哪種實現方式最終都會生成後面兩個物件
  • OAuth2Requset是ClientDetails和TokenRequest這兩個物件的資訊整合;Authentication封裝的是當前授權使用者的資訊
  • OAuth2Requset與Authentication這兩個物件組合成OAuth2Authentication,包含了現在是哪個第三方應用,請求哪個使用者的授權,用的什麼授權模式等資訊都封裝在這裡面
  • OAuth2Authentication這個物件會傳給AuthorizationServerTokenServices這個物件,認證令牌資訊的服務
  • TokenStore定製令牌的儲存方式,TokenEnhancer是令牌增強器,當令牌生成出來以後,可以改造令牌,加上一些自己想加的東西在上面

這裡寫圖片描述

重構三種登入方式

這裡寫圖片描述

  • 寫程式碼的地方在AuthenticationSuccessHandler中,目標是構建出OAuth2Requset這個物件,Authentication這個物件已經有了
  • 去BasicAuthenticationFilter中擷取一段程式碼到自定義的TiHomAuthenticationSuccessHandler中onAuthenticationSuccess方法
String header = httpServletRequest.getHeader("Authorization");
  if (header == null || !header.startsWith("Basic ")) {
      throw new UnapprovedClientAuthenticationException("請求頭中無client資訊");
  }
  //抽取並且解碼請求頭裡的字串
  String[] tokens = this.extractAndDecodeHeader(header, httpServletRequest);

  assert tokens.length == 2;

  String clientId = tokens[0];
  String clientSecret = tokens[1];

  //通過clientId獲取clientDetails
  ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
  if(clientDetails==null){
      throw new UnapprovedClientAuthenticationException("clientId對應的配置資訊不存在"+clientId);
  }else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
      throw new UnapprovedClientAuthenticationException("clientSecret不匹配"+clientSecret);
  }

  //map是儲存authentication內屬性的,因為我們這裡自帶authentication,所以傳空map即可
  TokenRequest tokenRequest = new TokenRequest
          (MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom");

  //clientDetails和tokenRequest合成OAuth2Request
  OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
  //oAuth2Request和authentication合成OAuth2Authentication
  OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);

  //拿認證去獲取令牌
  OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

大致的修改就是這樣

  • 在資源伺服器TiHomResourceServerConfig上做配置
@Override
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
                .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
                .successHandler(tihomAuthenticationSuccessHandler)
                .failureHandler(tihomAuthenticationFailureHandler);

        http//.apply(validateCodeSecurityConfig)
                //    .and()
                //簡訊驗證相關的配置
            .apply(smsCodeAuthenticationSecurityConfig)
                .and()
                //apply的作用就是往當前的過濾鏈上加過濾器,過濾器會攔截某些特定的請求,收到請求後引導使用者去做社交登入
            .apply(tihomSocialSecurityConfig)
                .and()
            .authorizeRequests()   //認證請求
                .antMatchers(
                    SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                    SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
                    securityProperties.getBrowser().getLoginPage(),
                    SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*",
                    securityProperties.getBrowser().getSignUpUrl(),
                    securityProperties.getBrowser().getSession().getSessionInvalidUrl(),
                    securityProperties.getBrowser().getSignOutUrl(),
                    "/user/regist")
                    .permitAll() //當我訪問這個url的時候,我不需要身份認證就可以訪問,其他的都需要認證
                .anyRequest()   //任何請求
                .authenticated()   //認證
                .and()
                .csrf().disable();  //防護的功能關閉
    }
  • 去RestLet Client中測試能否正常的拿到token,並且通過token拿到使用者資訊
  • 這樣就可以做到自定義認證過程獲取token了

重構驗證碼儲存邏輯

  • 在APP下定義RedisValidateCodeRepository類繼承ValidateCodeRepository
    • APP環境下不能拿session策略存驗證碼
    • 在生成和校驗驗證碼的請求時都帶上deviceId
  • 在瀏覽器類下定義SessionValidateCodeRepository類繼承ValidateCodeRepository

重構社交登入

  • 瀏覽器走的是標準的OAuth2流程
  • APP不是訪問應用裡的請求路徑,而是訪問的是服務提供商提供的SDK,會引導使用者去走認證流程
    • 第一種場景:
      這裡寫圖片描述
      • OpenIdAuthenticationToken封裝登入資訊
      • OpenIdAuthenticationFilter繼承AbstractAuthenticationProcessingFilter抽象的認證處理的filter,從請求裡面去獲取openId,然後獲取providerId,然後重新整合請求,然後將請求token交給AuthenticationManager,AuthenticationManager根據型別去找一個OpenIdAuthenticationProvider來校驗
      • OpenIdAuthenticationProvider的作用就是去校驗我們傳進來的OpenIdAuthenticationToken,校驗的方法就是引入UsersConnectionRepository類去查資料庫中的providerId和openId是否有記錄,查出userId,呼叫userDetailsService把使用者資訊讀出來,然後整合成新token返回
      • 在資源伺服器TiHomResourceServerConfig上引進我們寫的OpenIdAuthenticationSecurityConfig

        @Autowired
        private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;
        //下面configure中加上
        .apply(openIdAuthenticationSecurityConfig)
        .and()
    • 第二種場景:
      這裡寫圖片描述
      • 伺服器提供商提供的標準SDK,它走的是標準的授權碼流程,如圖
      • 在TihomSpringSocialConfigurer中postProcess方法中沒有去指定成功處理器,導致沒有使用APP模組中的自定義返回令牌的成功處理器;而是使用的預設的處理器,根據請求做跳轉的那個處理器.在瀏覽器情況下,我們微信登入然後掃碼成功之後應該進入我們網站的首頁裡面去,而APP要求的拿到授權碼後不是跳轉而是需要拿到一個令牌
      • 宣告一個後處理器SocialAuthenticationFilterPostProcessor介面
      • 在TihomSpringSocialConfigurer配置類中引入後處理器SocialAuthenticationFilterPostProcessor,做get/set方法
      • 在後處理方法裡面處理一下,如果我們的後處理器不為空就呼叫後處理器的處理方法

        if(socialAuthenticationFilterPostProcessor != null){
        socialAuthenticationFilterPostProcessor.process(filter);
        }
      • 在SocialConfig中注入SocialAuthenticationFilterPostProcessor後處理器介面

        @Autowired(required = false)
        private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

        在tihomSocialSecurityConfig方法中設定一下配置

        //配置後處理器
        configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
      • 在APP模組上加SocialAuthenticationFilterPostProcessor介面的實現類AppSocialAuthenticationFilterPostProcessor達到APP使用時的返回令牌行為,把這個類的成功處理器設定成我們自定義的返回令牌的成功處理器tiHomAuthenticationSuccessHandler,process方法直接調成功處理器方法
雖然這個流程跑通了,但是是建立在我們已經有使用者繫結的基礎上,我從第三方拿到資料然後從資料庫查到資料返回令牌,如果使用者是第一次登入的時候應該如何處理呢,之前在瀏覽器採用的寫一個註冊頁,通過訪問/social/user這個服務把使用者資訊從session中拿出來引導使用者去註冊,一旦使用者註冊或者繫結完成之後就會拿到一個使用者的唯一標識,再通過providerSignUtils在第三方應用中拿到資料再結合session中資料繫結寫入資料庫中

重構註冊邏輯

  • 在app中定義一個AppSignUpUtils的自定義app註冊工具類,宣告為spring元件,裡面就是把瀏覽器對session的操作換成app對redis的操作,具體細節我的程式碼中解釋的十分清楚
  • 自定義AppSecretException異常處理類
  • 寫一個SpringSocialConfigurerPostProcessor類實現BeanPostProcessor這個介面,實現這個介面的bena的作用就是Spring容器在初始化之前和初始化之後都要經過下面兩個方法,實現在tihomSocialSecurityConfig初始化好之後將signupUrl改掉的作用
  • 我們要做的是在SocialConfig類初始化Bean->tihomSocialSecurityConfig時將signupUrl改掉
  • 定義一個AppSecretController,在方法getSocialUserInfo中在返回前加上
//從connection中拿出資料存入redis中,做轉存
appSignUpUtils.saveConnectionData(new ServletWebRequest(request),connection.createData());
  • 在app資源伺服器TiHomResourceServerConfig中加上靜態資源”/social/signUp”