Spring Boot+Spring Security+Spring Social專案開發(六):開發APP認證框架、Spring Security OAuth核心原始碼、重構三種登入方式、重構社交登入
阿新 • • 發佈:2019-02-05
說在前面
博主最近會有很多專案跟大家一起分享,做完後會上傳github上的,希望讀友們能給博主提提意見哈哈
這個專案是第三方登入和安全方面的,關於後臺與app和網站的登入連線操作的實戰專案
各位如果可以就給我star哈哈謝謝啦
Spring Security OAuth開發APP認證框架
cookie-session方式
- 開發繁瑣,自己處理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”