最近用spring security框架来做认证授权工作,搞得头有点大了,借此机会,小结学习一番
spring security是一个安全访问控制框架,其工作是基于一系列的过滤器来实现的,这些过滤器是框架的核心,但是他们不直接处理用户的认证和授权,而是通过AuthenticationManager(认证管理器)和AccessDecisionManager(授权管理器)来进行处理
认证工作流程图:
当初始化spring security的时候,会创建一个名字SpringSecurityFilterChain的过滤器,其类型为FilterChainProxy,这个过滤器实现了javax.servlet.Filter,所以可以拦截到外部的请求。FilterChainProxy是一个代理,真正起作用的是SecurityFilterChain这个,我们找到SecurityFilterChain的实现类:DefaultSecurityFilterChain,如下代码所示,可以看到其内部维护了一个Filter集合
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List filters;
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List filters) {
logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
this.requestMatcher = requestMatcher;
this.filters = new ArrayList(filters);
}
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
public List getFilters() {
return this.filters;
}
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
public String toString() {
return this.getClass().getSimpleName() + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters + "]";
}
}
在这个过滤器集合处中打上断点,debug运行,如下图所示:
下面列举几个比较主要的过滤器:
SecurityContextPersistenceFilter:
//整个拦截链的入口和出口
//请求开始的时候:
//1、从SecurityContextRepository获取SecurityContext对象
//2、把SecurityContext对像设置到SecurityContextHolder中
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
SecurityContextHolder.setContext(contextBeforeChainExecution);
//请求结束的时候
//1、从SecurityContextHolder中取出SecurityContext对象,并清理掉SecurityContextHolder中的
//2、将SecurityContext设置回SecurityContextRepository中
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
LogoutFilter:
//拦截器主要拦截logout请求,也就是退出请求
//并调用一下方法
this.handler.logout(request, response, auth);
//logoutSuccessHandler这个可以通过自定义
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
AbstractAuthenticationProcessingFilter(UsernamePasswordAuthenticationFilter的父类):
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
try {
//attemptAuthentication这个在子类UsernamePasswordAuthenticationFilter中实现
//具体的可以看下面的代码
//子类返回的认证结果
Authentication authenticationResult = this.attemptAuthentication(request, response);
if (authenticationResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
this.successfulAuthentication(request, response, chain, authenticationResult);
} catch (InternalAuthenticationServiceException var5) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
this.unsuccessfulAuthentication(request, response, var5);
} catch (AuthenticationException var6) {
this.unsuccessfulAuthentication(request, response, var6);
}
}
}
//认证成功处理
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//将我们的用户信息保存在全局中,后面就可以方便取到
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
//认证失败处理
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
this.rememberMeServices.loginFail(request, response);
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
UsernamePasswordAuthenticationFilter:
//从这一句也可以看出这个过滤器是拦截请求路径为login的POST请求
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
//获取我们请求中的密码和用户名,并封装成UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
//AuthenticationManager(认证管理器)调用authenticate方法,对UsernamePasswordAuthenticationToken进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
}
ExceptionTranslationFilter:
异常过滤器,用来处理在认证过程中抛出的异常
看完几个主要的过滤器,接下来还是把重心放在UsernamePasswordAuthenticationFilter中this.getAuthenticationManager().authenticate(authRequest);认证的过程上,我们通过debug来调通这个过程:
1、在UsernamePasswordAuthenticationFilter中加入断点
2、从上述断点进去,进到ProviderManager类的authenticate方法
从上述断点的地方进来,则到这个类AbstractUserDetailsAuthenticationProvider的authenticate方法
首先看看系统用户信息的获取过程,从上述第一个断点进去,进到DaoAuthenticationProvider类中
接下来看看密码的匹配过程,则到DaoAuthenticationProvider的additionalAuthenticationChecks方法
密码匹配成功则继续运行,失败则抛出BadCredentialsException异常
流程大概总结:
1、UsernamePasswordAuthenticationFilter会将我们输入的用户名账号信息,封装成UsernamePasswordAuthenticationToken,并调用认证管理器AuthenticationManager对UsernamePasswordAuthenticationToken进行认证。2、AuthenticationManager的认证方法会调用DaoAuthenticationProvider的retrieveUser(根据用户名)来获取系统的用户信息(通过UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);),然后再将获取的系统对象的密码和我们传入的UsernamePasswordAuthenticationToken密码进行比对,如果比对符合,则认证成功。
补充:Authentication(认证信息)的结构(UsernamePasswordAuthenticationToken就是它的子接口)
public interface Authentication extends Principal, Serializable {
Collection getAuthorities();获取一些权限信息
Object getCredentials();//凭证信息,用户输入的密码,一般在认证后就会删除掉
Object getDetails();//获取一些细节信息,比如用户ip或者session等
Object getPrincipal();//身份信息,一般返回的就是UserDetails的实现类,代表着用户的详细信息
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
如有错漏,还望大佬指出