SpringSecurity隨筆(2)-OAuth2協議
簡訊登入
參考密碼登入過程
- 編寫簡訊登入過濾器,驗證簡訊驗證碼
- 編寫未認證得SmsAuthenticationToken
- 將未認證的SmsAuthenticationToken傳遞給AuthenticationManager
- 編寫一個SmsAuthenticationProvider
- 呼叫UserDetialsService獲取使用者資訊
OAuth協議
OAUTH協議為使用者資源的授權提供了一個安全的、開放而又簡易的標準。
認證伺服器:認證使用者身份,生成令牌。 資源伺服器:儲存使用者資源,驗證令牌。
授權模式
- 授權碼模式
- 密碼模式
- 客戶端模式
- 簡化模式
授權碼模式
Spring Social
核心元件
OAuth2Template:OAuth協議核心流程的封裝
AbstractOAuth2ApiBinding:不同服務提供商的使用者資訊
OAuth2Connection:封裝獲取到的使用者資訊
OAuth2ConnectionFactory:建立OAuth2Connection例項
ServiceProvider:呼叫OAuth2Template獲取使用者資訊
ApiAdapter:將獲取的使用者資訊封裝為標準的OAuth2Connection
UsersConnectionRepository:應用使用者資訊和服務提供商使用者資訊的對映
SpringSocial元件
SpringSocialConfigurer:核心配置元件
SpringSocialConfigurer#configure:建立SocialAuthenticationFilter
- org.springframework.social.security.provider.OAuth2AuthenticationService#getAuthToken 取授權碼
public SocialAuthenticationToken getAuthToken(HttpServletRequest request,HttpServletResponse response) throws SocialAuthenticationRedirectException {
//獲取授權碼
String code = request.getParameter("code" );
//授權碼為空 導向認證伺服器,獲取授權碼
if (!StringUtils.hasText(code)) {
OAuth2Parameters params = new OAuth2Parameters();
params.setRedirectUri(buildReturnToUrl(request));
setScope(request,params);
params.add("state",generateState(connectionFactory,request));
addCustomParameters(params);
throw new SocialAuthenticationRedirectException(getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params)); // 拼URL
// 拿這授權碼換令牌
} else if (StringUtils.hasText(code)) {
try {
String returnToUrl = buildReturnToUrl(request);
// 獲取token
AccessGrant accessGrant = getConnectionFactory().getOAuthOperations().exchangeForAccess(code,returnToUrl,null);
// TODO avoid API call if possible (auth using token would be fine)
Connection<S> connection = getConnectionFactory().createConnection(accessGrant);
return new SocialAuthenticationToken(connection,null);
} catch (RestClientException e) {
logger.debug("failed to exchange for access",e);
return null;
}
} else {
return null;
}
}
複製程式碼
- org.springframework.social.oauth2.OAuth2Template#exchangeForAccess 取token
public AccessGrant exchangeForAccess(String authorizationCode,String redirectUri,MultiValueMap<String,String> additionalParameters) {
MultiValueMap<String,String> params = new LinkedMultiValueMap<String,String>();
if (useParametersForClientAuthentication) {
params.set("client_id",clientId);
params.set("client_secret",clientSecret);
}
params.set("code",authorizationCode);
params.set("redirect_uri",redirectUri);
params.set("grant_type","authorization_code");
if (additionalParameters != null) {
params.putAll(additionalParameters);
}
return postForAccessGrant(accessTokenUrl,params);
}
//取toknen json格式
protected AccessGrant postForAccessGrant(String accessTokenUrl,String> parameters) {
return extractAccessGrant(getRestTemplate().postForObject(accessTokenUrl,parameters,Map.class));
}
複製程式碼
- 建立RestTemplate模板
protected RestTemplate createRestTemplate() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactorySelector.getRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(2);
converters.add(new FormHttpMessageConverter());
converters.add(new FormMapHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
restTemplate.setMessageConverters(converters);
restTemplate.setErrorHandler(new LoggingErrorHandler());
if (!useParametersForClientAuthentication) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors == null) { // defensively initialize list if it is null. (See SOCIAL-430)
interceptors = new ArrayList<ClientHttpRequestInterceptor>();
restTemplate.setInterceptors(interceptors);
}
interceptors.add(new PreemptiveBasicAuthClientHttpRequestInterceptor(clientId,clientSecret));
}
return restTemplate;
}
複製程式碼
-
重寫org.springframework.social.oauth2.OAuth2Template#postForAccessGrant,定製AccessGrant
-
org.springframework.social.security.SocialAuthenticationProvider#authenticate 獲取到qq使用者資訊,進行許可權認證
org.springframework.social.security.SocialAuthenticationFilter#doAuthentication
QQ登入和微信登入的不同之處,QQ是拿到accessToken用accessToken去換openId,微信在返回accessToken的同時會返回opendId。
QQ在申請授權碼的時候通過OAuth2Template#buildAuthenticateUrl(OAuth2Parameters)拼裝了URL
例如:https://graph.qq.com/oauth2.0/authorize? client_id=101087& response_type=code& redirect_uri=http://www.sdasda.cn/qqLogin/qq& state=9c103c5a-34a8-4bf5-82df-c0eca91e8f4a
微信的授權碼url不是OAuth2標準的