本文介绍springboot项目采用spring-security认证,传递token,session的生成流程,分析源码创建过程。
最主要的入口在org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter
这个请求过滤器中。具体访问流程如下
Tomcat接收请求进入org.apache.catalina.authenticator.AuthenticatorBase#invoke这个方法。
流程进入下一个过滤器:
org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter#doFilter
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
ServletException {
final boolean debug = logger.isDebugEnabled();
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
try {
// 提取token值封装到PreAuthenticatedAuthenticationToken类中
Authentication authentication = tokenExtractor.extract(request);
if (authentication == null) {
//isAuthenticated()是否认证通过
if (stateless && isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}
// 清除context
SecurityContextHolder.clearContext();
}
if (debug) {
logger.debug("No token in request, will continue chain.");
}
}
else {
//设置request属性,token值
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
// 去调用 用户服务认证token信息。返回认证结果
Authentication authResult = authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}
//发布认证结果事件
eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}
catch (OAuth2Exception failed) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + failed);
}
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed),
new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
authenticationEntryPoint.commence(request, response,
new InsufficientAuthenticationException(failed.getMessage(), failed));
return;
}
chain.doFilter(request, response);
}
}
再进入org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager#authenticate
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
}
String token = (String) authentication.getPrincipal();
// 访问认证服务,返回认证用户信息
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
}
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");
}
checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
// Guard against a cached copy of the same details
if (!details.equals(auth.getDetails())) {
// Preserve the authentication details from the one loaded by token services
details.setDecodedDetails(auth.getDetails());
}
}
auth.setDetails(authentication.getDetails());
//设置认证成功true
auth.setAuthenticated(true);
return auth;
}
}
上边2张图是认证失败的,token过期了,
再次获取token。
认证成功后进入org.springframework.security.web.session.SessionManagementFilter#doFilter
这个类是管理session的
public class SessionManagementFilter extends GenericFilterBean {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (!securityContextRepository.containsContext(request)) {
//获取上次认证成功返回的信息,这里存入了ThreadLocal中
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication != null && !trustResolver.isAnonymous(authentication)) {
// The user has been authenticated during the current request, so call the
// session strategy
try {
//执行session管理策略,配置在
//org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer这个类中
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response);
}
catch (SessionAuthenticationException e) {
// The session strategy can reject the authentication
logger.debug(
"SessionAuthenticationStrategy rejected the authentication object",
e);
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
// Eagerly save the security context to make it available for any possible
// re-entrant
// requests which may occur before the current request completes.
// SEC-1396.
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
}
else {
// No security context or authentication present. Check for a session
// timeout
if (request.getRequestedSessionId() != null
&& !request.isRequestedSessionIdValid()) {
if (logger.isDebugEnabled()) {
logger.debug("Requested session ID "
+ request.getRequestedSessionId() + " is invalid.");
}
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
}
}
}
chain.doFilter(request, response);
}
}
首先进入session的组合管理策略,在进入自己配置的
org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#onAuthentication
再进入org.springframework.web.servlet.DispatcherServlet#doService
session的生成类org.apache.catalina.util.StandardSessionIdGenerator
session配置类
public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<SessionManagementConfigurer<H>, H> {
private final SessionAuthenticationStrategy DEFAULT_SESSION_FIXATION_STRATEGY = createDefaultSessionFixationProtectionStrategy();
//默认的session策略是ChangeSessionIdAuthenticationStrategy,每次都会生成一个新的
private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = this.DEFAULT_SESSION_FIXATION_STRATEGY;
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private SessionAuthenticationStrategy providedSessionAuthenticationStrategy;
//失效session策略
private InvalidSessionStrategy invalidSessionStrategy;
private SessionInformationExpiredStrategy expiredSessionStrategy;
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();
private SessionRegistry sessionRegistry;
//允许的最大session数量
private Integer maximumSessions;
private String expiredUrl;
private boolean maxSessionsPreventsLogin;
private SessionCreationPolicy sessionPolicy = SessionCreationPolicy.IF_REQUIRED;
private boolean enableSessionUrlRewriting;
//session失效需要跳转地址
private String invalidSessionUrl;
private String sessionAuthenticationErrorUrl;
private AuthenticationFailureHandler sessionAuthenticationFailureHandler;
}