在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
。源码分析