在Spring Security源碼分析三:Spring Social實現QQ社交登陸和Spring Security源碼分析四:Spring Social實現微信社交登陸這兩章中,咱們使用
Spring Social
已經實現了國內最經常使用的微信
社交登陸。本章咱們來簡單分析一下Spring Social
在社交登陸的過程當中作了哪些事情?(微博
社交登陸也已經實現,因爲已經連續兩篇介紹社交登陸,因此不在單開一章節描述)java
OAuth2是一種受權協議,簡單理解就是它可讓用戶在不將用戶名密碼交給第三方應用的狀況下,第三方應用有權訪問用戶存在服務提供商上面的數據。git
Authentication
放入SecurityContext中
若是在SecurityContext
中放入一個已經認證過的Authentication
實例,那麼對於Spring Security
來講,已經成功登陸Spring Social
就是爲咱們將OAuth2
認證流程封裝到SocialAuthenticationFilter
過濾器中,並根據返回的用戶信息構建Authentication
。而後使用Spring Security
的驗證邏輯從而實現使用社交登陸。github
啓動logback斷點調試;spring
ValidateCodeFilter
校驗驗證碼過濾器SocialAuthenticationFilter
社交登陸過濾器UsernamePasswordAuthenticationFilter
用戶名密碼登陸過濾器SmsCodeAuthenticationFilter
短信登陸過濾器AnonymousAuthenticationFilter
前面過濾器都沒校驗時匿名驗證的過濾器ExceptionTranslationFilter
處理FilterSecurityInterceptor
受權失敗時的過濾器FilterSecurityInterceptor
受權過濾器本章咱們主要講解SocialAuthenticationFilter
微信
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//#1.判斷用戶是否容許受權
if (detectRejection(request)) {
if (logger.isDebugEnabled()) {
logger.debug("A rejection was detected. Failing authentication.");
}
throw new SocialAuthenticationException("Authentication failed because user rejected authorization.");
}
Authentication auth = null;
//#2.獲取全部的社交配置providerId(本項目中三個:qq,weixin,weibo)
Set<String> authProviders = authServiceLocator.registeredAuthenticationProviderIds();
//#3.根據請求獲取當前的是那種類型的社交登陸
String authProviderId = getRequestedProviderId(request);
//#4.判斷是否系統中是否配置當前社交providerId
if (!authProviders.isEmpty() && authProviderId != null && authProviders.contains(authProviderId)) {
//#5.獲取當前社交的處理類即OAuth2AuthenticationService用於獲取Authentication
SocialAuthenticationService<?> authService = authServiceLocator.getAuthenticationService(authProviderId);
//#6.獲取SocialAuthenticationToken
auth = attemptAuthService(authService, request, response);
if (auth == null) {
throw new AuthenticationServiceException("authentication failed");
}
}
return auth;
}
private Authentication attemptAuthService(final SocialAuthenticationService<?> authService, final HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException, AuthenticationException {
//獲取SocialAuthenticationToken
final SocialAuthenticationToken token = authService.getAuthToken(request, response);
if (token == null) return null;
Assert.notNull(token.getConnection());
//#7.從SecurityContext獲取Authentication判斷是否定證
Authentication auth = getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
//#8.進行認證
return doAuthentication(authService, request, token);
} else {
//#9.返回當前的登陸帳戶的一些信息
addConnection(authService, request, token, auth);
return null;
}
}
複製代碼
OAuth2AuthenticationService
(用於獲取SocialAuthenticationToken
)SecurityContext
獲取Authentication
判斷是否受權public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
//#1. 獲取code
String code = request.getParameter("code");
//#2. 判斷code值
if (!StringUtils.hasText(code)) {
//#3.若是code不存在則拋出SocialAuthenticationRedirectException
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));
} else if (StringUtils.hasText(code)) {
try {
//#4.若是code存在則根據code得到access_token
String returnToUrl = buildReturnToUrl(request);
AccessGrant accessGrant = getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, null);
// TODO avoid API call if possible (auth using token would be fine)
//#5.用access_token獲取用戶的信息並返回spring Social標準信息模型
Connection<S> connection = getConnectionFactory().createConnection(accessGrant);
//#6.使用返回的用戶信息構建SocialAuthenticationToken
return new SocialAuthenticationToken(connection, null);
} catch (RestClientException e) {
logger.debug("failed to exchange for access", e);
return null;
}
} else {
return null;
}
}
複製代碼
code
code
是否存在值code
獲取access_token
access_token
返回用戶信息(該信息爲Spring Social
標準信息模型)SocialAuthenticationToken
private Authentication doAuthentication(SocialAuthenticationService<?> authService, HttpServletRequest request, SocialAuthenticationToken token) {
try {
if (!authService.getConnectionCardinality().isAuthenticatePossible()) return null;
token.setDetails(authenticationDetailsSource.buildDetails(request));
//#重點熟悉的AuhenticationManage
Authentication success = getAuthenticationManager().authenticate(token);
Assert.isInstanceOf(SocialUserDetails.class, success.getPrincipal(), "unexpected principle type");
updateConnections(authService, token, success);
return success;
} catch (BadCredentialsException e) {
// connection unknown, register new user?
if (signupUrl != null) {
// store ConnectionData in session and redirect to register page
sessionStrategy.setAttribute(new ServletWebRequest(request), ProviderSignInAttempt.SESSION_ATTRIBUTE, new ProviderSignInAttempt(token.getConnection()));
throw new SocialAuthenticationRedirectException(buildSignupUrl(request));
}
throw e;
}
}
複製代碼
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//#1.一些判斷信息
Assert.isInstanceOf(SocialAuthenticationToken.class, authentication, "unsupported authentication type");
Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
SocialAuthenticationToken authToken = (SocialAuthenticationToken) authentication;
//#2.從SocialAuthenticationToken中獲取providerId(表示當前是那個第三方登陸)
String providerId = authToken.getProviderId();
//#3.從SocialAuthenticationToken中獲取獲取用戶信息 即ApiAdapter設置的用戶信息
Connection<?> connection = authToken.getConnection();
//#4.從UserConnection表中查詢數據
String userId = toUserId(connection);
//#5.若是不存在拋出BadCredentialsException異常
if (userId == null) {
throw new BadCredentialsException("Unknown access token");
}
//#6.調用咱們自定義的MyUserDetailsService查詢
UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
if (userDetails == null) {
throw new UsernameNotFoundException("Unknown connected account id");
}
//#7.返回已經認證的SocialAuthenticationToken
return new SocialAuthenticationToken(connection, userDetails, authToken.getProviderAccountData(), getAuthorities(providerId, userDetails));
}
複製代碼
public List<String> findUserIdsWithConnection(Connection<?> connection) {
ConnectionKey key = connection.getKey();
List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, key.getProviderId(), key.getProviderUserId());
//# 重點conncetionSignUp
if (localUserIds.size() == 0 && connectionSignUp != null) {
String newUserId = connectionSignUp.execute(connection);
if (newUserId != null)
{
createConnectionRepository(newUserId).addConnection(connection);
return Arrays.asList(newUserId);
}
}
return localUserIds;
}
複製代碼
所以咱們自定義MyConnectionSignUp
實現ConnectionSignUp
接口後,Spring Social
會插入數據後返回userId
session
@Component
public class MyConnectionSignUp implements ConnectionSignUp {
@Override
public String execute(Connection<?> connection) {
//根據社交用戶信息,默認建立用戶並返回用戶惟一標識
return connection.getDisplayName();
}
}
複製代碼
至於OAuth2AuthenticationService
中獲取code
和AccessToken
,Spring Social
已經咱們提供了基本的實現。開發中,根據不通的服務提供商提供不通的實現,具體可參考如下類圖,代碼可參考logback項目social
包下面的類。 ide
以上即是使用Spring Social
實現社交登陸的核心類,其實和用戶名密碼登陸,短信登陸原理同樣.都有Authentication
,和實現認證的AuthenticationProvider
。源碼分析