Spring Social 认证流程源码详解
Spring Security 过滤链
认证流程源码详解
SocialAuthenticationFilter会将请求拦截下来然后将整个流程走完。进而去实现第三方登录。详细流程如下。
我们来看一下SocialAuthenticationFilter的源码
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.detectRejection(request)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("A rejection was detected. Failing authentication.");
}
throw new SocialAuthenticationException("Authentication failed because user rejected authorization.");
} else {
Authentication auth = null;
//已注册服务提供商Id
Set<String> authProviders = this.authServiceLocator.registeredAuthenticationProviderIds();
//请求中的providerId
String authProviderId = this.getRequestedProviderId(request);
if (!authProviders.isEmpty() && authProviderId != null && authProviders.contains(authProviderId)) {
//查找处理该ProviderId的 SocialAuthenticationService
SocialAuthenticationService<?> authService = this.authServiceLocator.getAuthenticationService(authProviderId);
auth = this.attemptAuthService(authService, request, response);
if (auth == null) {
throw new AuthenticationServiceException("authentication failed");
}
}
return auth;
}
}
接着调用
this.attemptAuthService(authService, request, response);
看一下源码
private Authentication attemptAuthService(SocialAuthenticationService<?> authService, HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException, AuthenticationException {
// 封装SocialAuthenticationToken对象
SocialAuthenticationToken token = authService.getAuthToken(request, response);
if (token == null) {
return null;
} else {
Assert.notNull(token.getConnection());
Authentication auth = this.getAuthentication();
//判断是否认证通过
if (auth != null && auth.isAuthenticated()) { //认证通过
this.addConnection(authService, request, token, auth);
return null;
} else {
//进行认证
return this.doAuthentication(authService, request, token);
}
}
}
接下来调用
authService.getAuthToken(request, response);
这里具体会调用 OAuth2AuthenticationService的getAuthToken(request, response)方法。
我们来看一下getAuthToken的源码,这里会涉及到OAauth协议的授权码模式。
public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
String code = request.getParameter("code");
//判断受否获得授权码
if (!StringUtils.hasText(code)) { //没有获取授权码
OAuth2Parameters params = new OAuth2Parameters();
//设置认证服务器地址
params.setRedirectUri(this.buildReturnToUrl(request));
this.setScope(request, params);
params.add("state", this.generateState(this.connectionFactory, request));
this.addCustomParameters(params);
//这个异常会把用户导向认证服务器
throw new SocialAuthenticationRedirectException(this.getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params));
} else if (StringUtils.hasText(code)) { //已经获取了授权码
try {
String returnToUrl = this.buildReturnToUrl(request);
// 申请令牌
AccessGrant accessGrant = this.getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, (MultiValueMap)null);
// 获取用户信息封装到Connection对象中
Connection<S> connection = this.getConnectionFactory().createConnection(accessGrant);
//把Connection封装到SocialAuthenticationToken中,并设置为未认证
return new SocialAuthenticationToken(connection, (Map)null);
} catch (RestClientException var7) {
this.logger.debug("failed to exchange for access", var7);
return null;
}
} else {
return null;
}
}
其中 this.getConnectionFactory() 将会调用用户自己实现的针对具体服务提供商的ConnectionFactory。具体可参考
SpringSocial原理:https://blog.csdn.net/weixin_39869513/article/details/105547445
接下来调用,进行认证
this.doAuthentication(authService, request, token);
看一下源码
private Authentication doAuthentication(SocialAuthenticationService<?> authService, HttpServletRequest request, SocialAuthenticationToken token) {
try {
if (!authService.getConnectionCardinality().isAuthenticatePossible()) {
return null;
} else {
token.setDetails(this.authenticationDetailsSource.buildDetails(request));
//验证逻辑
Authentication success = this.getAuthenticationManager().authenticate(token);
Assert.isInstanceOf(SocialUserDetails.class, success.getPrincipal(), "unexpected principle type");
this.updateConnections(authService, token, success);
return success;
}
} catch (BadCredentialsException var5) {
//导向注册页
if (this.signupUrl != null) {
this.sessionStrategy.setAttribute(new ServletWebRequest(request), ProviderSignInAttempt.SESSION_ATTRIBUTE, new ProviderSignInAttempt(token.getConnection()));
throw new SocialAuthenticationRedirectException(this.buildSignupUrl(request));
} else {
throw var5;
}
}
}
接着调用
this.getAuthenticationManager().authenticate(token);
这里有一个AuthenticationManager,但是真正调用的是ProviderManager。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
// 1.判断是否有provider支持该Authentication
if (provider.supports(toTest)) {
// 2. 真正的逻辑判断
result = provider.authenticate(authentication);
}
}
}
- 这里首先通过provider判断是否支持当前传入进来的Authentication,目前我们使用的是SocialAuthenticationToken。
- 根据我们目前所使用的SocialAuthenticationToken,provider对应的是SocialAuthenticationProvider。
下面接着看 provider.authenticate(authentication)方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(SocialAuthenticationToken.class, authentication, "unsupported authentication type");
Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
SocialAuthenticationToken authToken = (SocialAuthenticationToken)authentication;
String providerId = authToken.getProviderId();
Connection<?> connection = authToken.getConnection();
// 1.去数据库查询服务提供商在第三方应用里对应用户id
String userId = this.toUserId(connection)
// 如果为空,BadCredentialsException异常被捕获后,将会把用户重定向到注册页
if (userId == null) {
throw new BadCredentialsException("Unknown access token");
} else {
//2.去获取UserDetails
UserDetails userDetails = this.userDetailsService.loadUserByUserId(userId);
if (userDetails == null) {
throw new UsernameNotFoundException("Unknown connected account id");
} else {
//3. 返回真正的经过认证的Authentication
return new SocialAuthenticationToken(connection, userDetails, authToken.getProviderAccountData(), this.getAuthorities(providerId, userDetails));
}
}
}
- 看一下 this.toUserId(connection)
protected String toUserId(Connection<?> connection) {
List<String> userIds = this.usersConnectionRepository.findUserIdsWithConnection(connection);
return userIds.size() == 1 ? (String)userIds.iterator().next() : null;
}
这里调用
this.usersConnectionRepository.findUserIdsWithConnection(connection)
我们这里以UsersConnectionRepository的实现类JdbcUsersConnectionRepositor来举例
public List<String> findUserIdsWithConnection(Connection<?> connection) {
ConnectionKey key = connection.getKey();
//通过providerId和 providerUserId查询 userId
List<String> localUserIds = this.jdbcTemplate.queryForList("select userId from " + this.tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, new Object[]{key.getProviderId(), key.getProviderUserId()});
//如果第三应用中还没有该用户信息,可以提供 connectionSignUp的实现来指定该用户的userId,实现自动注册用户,不会跳到注册页面
if (localUserIds.size() == 0 && this.connectionSignUp != null) {
String newUserId = this.connectionSignUp.execute(connection);
if (newUserId != null) {
//把用户信息存到数据库中
this.createConnectionRepository(newUserId).addConnection(connection);
return Arrays.asList(newUserId);
}
}
return localUserIds;
}
- 去调用自己实现的SocialUserDetailsService,返回UserDetails
- 返回真正的经过认证的Authentication
拿到经过认证的Authentication之后,会再去调用successHandler。或者未通过认证,去调用failureHandler
上一篇: AI绘制一套扁平化风格的马戏团图标
下一篇: Ai怎么画一个涂口红的卡通小猫咪?
推荐阅读
-
详解Spring Boot实战之Filter实现使用JWT进行接口认证
-
oracle数据库启动流程及登录认证方式详解
-
详解Spring IOC 容器启动流程分析
-
Spring MVC源码(二) ----- DispatcherServlet 请求处理流程 面试必问
-
Spring Security登录验证流程源码解析
-
Python confluent kafka客户端配置kerberos认证流程详解
-
详解Android布局加载流程源码
-
Hi3518EV200实现H264视频采集的源码及流程详解(不依赖SAMPLE库)
-
Spring IoC源码分析-02深入源码流程分析
-
Spring IOC 容器预启动流程源码探析