spring security主要是依赖一系列的Filter来实现权限验证的,责任链设计模式是跑不了的。下面简单记录一下spring操作这些Filter的过程。
1. WebSecurityConfiguration.java
该类是spring security的一个配置类,里面定了一系列的Bean,咱主要是看springSecurityFilterChain这个bean, 就是它创建了FilterChain.
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { // 通过webSecurity来构建了FilterChain return webSecurity.build(); }
2. AbstractConfiguredSecurityBuilder.java
在该类中就加载我们配置权限规则,以及spring security默认的一系列Filter, 权限规则就是01 02篇中我们自定义的SecurityConfig.java类,示例代码如下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 配置的权限规则 */ @Override protected void configure(HttpSecurity http) throws Exception { // todo } }
再看看AbstractConfiguredSecurityBuilder.java中部分源码,就是在这段代码里面加载了屁多的东西,暂时还没看明白。。。
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit(); init(); buildState = BuildState.CONFIGURING; beforeConfigure(); // configure(); buildState = BuildState.BUILDING; // O result = performBuild(); buildState = BuildState.BUILT; return result; } }
3. SecurityContextPersistenceFilter.java
This filter will only execute once per request, to resolve servlet container(specifically Weblogic) incompatibilities.
该filter每个请求只执行一次,主要是解决servlet容器的兼容性问题。
This filter MUST be executed BEFORE any authentication processing mechanisms.
必须在权限认证之前执行
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) { // ensure that filter is only applied once per request chain.doFilter(request, response); return; } request.setAttribute(FILTER_APPLIED, Boolean.TRUE); // 设置security的上下文context HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,response); SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { SecurityContextHolder.setContext(contextBeforeChainExecution); // 开始执行chain上的filter chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { // 清除security的上下文context SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse()); request.removeAttribute(FILTER_APPLIED); } }
由上面的debug截图可知,FilterSecurityInterceptor.java是链上最后一个Filter,它会对权限进行验证
InterceptorStatusToken token = super.beforeInvocation(fi);
就是在它父类AbstractSecurityInterceptor#beforeInvocation(fi)方法中,进行权限验证,且继续看代码
这儿抛异常会被它前一个Filter捕获,也就是会被ExceptionTranslationFilter.java捕获。
在上面的异常处理中,如果没有权限就会进行重定向到登录页面
下面进入LoginUrlAuthenticationEntryPoint#commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException)
4. 登录页面
提交请求之后,又会执行整个filterchain, 此次我们重点分析UsernamePasswordAuthenticationFilter.java, 因为,使用Form表登录,会在该类进行权限验证,下面来看看具体的逻辑
5. UsernamePasswordAuthenticationFilter.java
该类主要就是进行权限验证
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 非post请求,直接抛异常 if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } // 从request对象中获取username , password String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); // 创建一个token,该token对象持有用户信息 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // 获取用登录的ip, session相关的信息 setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }
debug看一下return this.getAuthenticationManager().authenticate(authRequest)之前的情况
接着,看看this.getAuthenticationManager().authenticate(authRequest)该方法。
好, 下面进入AuthenticationManager接口的具体实现ProviderManager#authenticate(Authentication authentication)方法
跟踪代码,我们最终会看到这样一个方法DaoAuthenticationProvider#retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)
protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { // this.getUserDetailsService() 这个就是我们实现UserDetailsService接口的MyUserDetailsService.java 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); } }
该方法返回UserDetails 对象后,又会进入到AbstractUserDetailsAuthenticationProvider#authenticate(Authentication authentication)方法,然后对UserDetails 对象做些后置检测,
比如,账号是否锁定,是否过期,是否可用。。。
在最最后面,spring secutiry会再次创建一个UsernamePasswordAuthenticationToken对象的token,不过此次与前面创建的不同,前面创建的token中只有username和password ,
此次会调用UsernamePasswordAuthenticationToken 三个参数的构造器
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); // must use super, as we override }
再继续,代码会走到AbstractAuthenticationProcessingFilter#successfulAuthentication(...)方法,该方法内容,会调用我们自定义的成功的Handler处理器
如果中间抛出异常,会被catch捕获 ,然后走入到自定义的登录失败handler处理器
未完待续。。。