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 }
好,暂时就到这儿吧
原文地址:https://www.cnblogs.com/z-qinfeng/p/11783672.html