SpringSecurity源码分析-认证逻辑
1. Spring-security-core包中的三个重要类
SecurityContext
- 这个类中就两个方法getAuthentication()和setAuthentication()
- 这个类用来存储Authentication对象
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication var1);
}
Authentication
- 这个类是贯穿SpringSecurity整个流程的一个类。
- 它是一个接口,它的实现类中的UsernamePasswordAuthenticationToken是通过用户名密码认证的实现
- 登录成功后用来存储当前的登录信息。
- 其中三个方法:
- getCredentials():获取当前用户凭证
- getDetails():获取当前登录用户详情
- getPrincipal():获取当前登录用户对象
- isAuthenticated():是否登录
- GrantedAuthority类是用来存储权限的,它是一个接口,常用的SimpleGrantedAuthority实现类,用来存储用户包含的权限
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
SecurityContextHolder
- 这个对象用来存储SecurityContext对象
- 其中有两个静态方法getContext()和setContext()
- 因此,获得SecurityContextHolder对象就能获得SecurityContext对象,也就可以获取Authentication对象,也就可以获取当前的登录信息。
- initialize():获取存储策略,全局、本地线程、父子线程三种,默认本地线程。
public class SecurityContextHolder {
public static SecurityContext getContext() {
return strategy.getContext();
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
strategyName = "MODE_THREADLOCAL";
}
if (strategyName.equals("MODE_THREADLOCAL")) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals("MODE_GLOBAL")) {
strategy = new GlobalSecurityContextHolderStrategy();
} else {
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
} catch (Exception var2) {
ReflectionUtils.handleReflectionException(var2);
}
}
++initializeCount;
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}
小结:Authentication用来存储认证信息,SecurityContext用来存储认证信息的容器,SecurityContextHolder用来定义容器的存储策略。
2. 基于用户名密码认证的流程
- 找到UsernamePasswordAuthenticationFilter,找到doFilter方法
- 发现没有doFilter方法,去父类AbstractAuthenticationProcessingFilter中查看
- 其实是这样一个逻辑
- 所有AbstractAuthenticationProcessingFilter的实现类都调用父类中的doFilter方法
- 在doFilter方法中调用了attemptAuthentication方法
- attemptAuthentication方法是一个抽象方法,子类去实现AbstractAuthenticationProcessingFilter抽象方法
UsernamePasswordAuthenticationFilter类
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
//…… ……
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//封装成Authentication对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
//认证操作,ProviderManager中执行
return this.getAuthenticationManager().authenticate(authRequest);
}
//…… ……
}
AbstractAuthenticationProcessingFilter类
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//认证逻辑
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//认证成功后的逻辑.......
successfulAuthentication(request, response, chain, authResult);
}
//认证逻辑的抽象方法,交给子类去实现
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
}
- 查看子类UsernamePasswordAuthenticationFilter中的attemptAuthentication方法
- 认证的逻辑在这里: this.getAuthenticationManager().authenticate(authRequest)
- 认证完毕后封装Authentication对象,返回。
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
- ProviderManager类中的authenticate方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
Iterator var8 = this.getProviders().iterator();
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
// 认证逻辑
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (InternalAuthenticationServiceException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
if (result == null && this.parent != null) {
try {
result = parentResult = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var11) {
} catch (AuthenticationException var12) {
parentException = var12;
lastException = var12;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
- AbstractUserDetailsAuthenticationProvider中的Authentication方法
public Authentication authenticate(Authentication authentication)
throws AuthenticationException{
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,authentication,
()->messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// 获取用户名
String username=(authentication.getPrincipal()==null)?"NONE_PROVIDED"
:authentication.getName();
boolean cacheWasUsed=true;
//缓存获取user
UserDetails user=this.userCache.getUserFromCache(username);
if(user==null){
cacheWasUsed=false;
try{
//自定义获取user,一般从数据库读取
user=retrieveUser(username,
(UsernamePasswordAuthenticationToken)authentication);
}
catch(UsernameNotFoundException notFound){
logger.debug("User '"+username+"' not found");
if(hideUserNotFoundExceptions){
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else{
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try{
preAuthenticationChecks.check(user);
//去比对密码
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken)authentication);
}
catch(AuthenticationException exception){
if(cacheWasUsed){
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed=false;
user=retrieveUser(username,
(UsernamePasswordAuthenticationToken)authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken)authentication);
}
else{
throw exception;
}
}
postAuthenticationChecks.check(user);
if(!cacheWasUsed){
this.userCache.putUserInCache(user);
}
Object principalToReturn=user;
if(forcePrincipalAsString){
principalToReturn=user.getUsername();
}
//封装Authentication对象
return createSuccessAuthentication(principalToReturn,authentication,user);
}
- DaoAuthenticationProvider中的retrieveUser方法
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//调用我们自己的loadUserByUsername方法获取user
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
小结:至此返回Authentication对象完成认证
3. 认证成功后的逻辑
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
//将认证后的对象放到SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authResult);
//记住我的执行逻辑
rememberMeServices.loginSuccess(request, response, authResult);
// 发布认证成功后的时间,可以自定义监听器
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
//认证成功后的执行逻辑,默认三种,可以通过实现AuthenticationSuccessHandler接口自定义认证成功后逻辑
successHandler.onAuthenticationSuccess(request, response, authResult);
}
4. 记住我是如何实现的
AbstractRememberMeServices的loginSuccess方法和rememberMeRequested方法
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
//判断是否勾选remember
if (!this.rememberMeRequested(request, this.parameter)) {
this.logger.debug("Remember-me login not requested.");
} else {
this.onLoginSuccess(request, response, successfulAuthentication);
}
}
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (this.alwaysRemember) {
return true;
} else {
String paramValue = request.getParameter(parameter);
//判断是否勾选remember,传入的值可以为以下内容
if (paramValue != null && (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on") || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1"))) {
return true;
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
}
return false;
}
}
}
PersistentTokenBasedRememberMeServices中的onLoginSuccess方法完成持久化操作
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
String username = successfulAuthentication.getName();
this.logger.debug("Creating new persistent login for user " + username);
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, this.generateSeriesData(), this.generateTokenData(), new Date());
try {
//持久化操作
this.tokenRepository.createNewToken(persistentToken);
//将token放到cookie中,可以自定义
this.addCookie(persistentToken, request, response);
} catch (Exception var7) {
this.logger.error("Failed to save persistent token ", var7);
}
}
持久化操作有两个实现JdbcTokenRepositoryImpl存到数据库,InMemoryTokenRepositoryImpl存到内存中
5.Security中的ExceptionTranslationFilter过滤器
- 这个过滤器不处理逻辑
- 只捕获Security中的异常
- 捕获异常后处理异常信息
public class ExceptionTranslationFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private RequestCache requestCache = new HttpSessionRequestCache();
private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
this(authenticationEntryPoint, new HttpSessionRequestCache());
}
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
RequestCache requestCache) {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint cannot be null");
Assert.notNull(requestCache, "requestCache cannot be null");
this.authenticationEntryPoint = authenticationEntryPoint;
this.requestCache = requestCache;
}
// ~ Methods
// ========================================================================================================
@Override
public void afterPropertiesSet() {
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
/** 交给下一个过滤器处理*/
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
/** 捕获Security中的异常,处理异常*/
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
/** 处理异常*/
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
return authenticationEntryPoint;
}
protected AuthenticationTrustResolver getAuthenticationTrustResolver() {
return authenticationTrustResolver;
}
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
/** 根据不同的异常,做出不同的处理*/
if (exception instanceof AuthenticationException) {
logger.debug(
"Authentication exception occurred; redirecting to authentication entry point",
exception);
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug(
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
exception);
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
protected void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, response);
logger.debug("Calling Authentication entry point.");
authenticationEntryPoint.commence(request, response, reason);
}
public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
this.accessDeniedHandler = accessDeniedHandler;
}
public void setAuthenticationTrustResolver(
AuthenticationTrustResolver authenticationTrustResolver) {
Assert.notNull(authenticationTrustResolver,
"authenticationTrustResolver must not be null");
this.authenticationTrustResolver = authenticationTrustResolver;
}
public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null");
this.throwableAnalyzer = throwableAnalyzer;
}
/**
* Default implementation of <code>ThrowableAnalyzer</code> which is capable of also
* unwrapping <code>ServletException</code>s.
*/
private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
/**
* @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
*/
protected void initExtractorMap() {
super.initExtractorMap();
registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
public Throwable extractCause(Throwable throwable) {
ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
ServletException.class);
return ((ServletException) throwable).getRootCause();
}
});
}
}
}
6.登录页面是如何产生的
答案在最后一个过滤器DefaultLoginPageGeneratingFilter中
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
boolean loginError = this.isErrorPage(request);
boolean logoutSuccess = this.isLogoutSuccess(request);
if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
chain.doFilter(request, response);
} else {
//拼接生产html登录页面
String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
}
}