Spring Security 源码阅读笔记

jonathan
2018-11-12 / 0 评论

Spring Security 源码阅读笔记

一、核心架构概述

Spring Security的核心是基于过滤器链(Filter Chain)的认证和授权机制。通过分析源码,我们可以看到它的主要组件和执行流程。

1.1 核心组件

  • SecurityContextHolder: 安全上下文的存储策略
  • Authentication: 认证信息的抽象
  • AuthenticationManager: 认证管理器
  • SecurityFilterChain: 安全过滤器链
  • UserDetailsService: 用户信息获取服务
  • PasswordEncoder: 密码编码器
  • AccessDecisionManager: 访问决策管理器

二、认证流程源码分析

2.1 SecurityContextHolder

public class SecurityContextHolder {
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
    
    public static SecurityContext getContext() {
        SecurityContext ctx = contextHolder.get();
        if (ctx == null) {
            ctx = createEmptyContext();
            contextHolder.set(ctx);
        }
        return ctx;
    }
    
    // 其它方法...
}

SecurityContextHolder负责存储当前用户的安全上下文,默认使用ThreadLocal存储,确保每个线程都有独立的安全上下文。

2.2 UsernamePasswordAuthenticationFilter

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        
        UsernamePasswordAuthenticationToken authRequest = 
            new UsernamePasswordAuthenticationToken(username, password);
        
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    
    // 其它方法...
}

这个过滤器负责处理表单登录的认证请求,从请求中获取用户名和密码,然后创建认证令牌交给AuthenticationManager处理。

2.3 ProviderManager

public class ProviderManager implements AuthenticationManager {
    
    private List<AuthenticationProvider> providers;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        
        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }
            
            try {
                Authentication result = provider.authenticate(authentication);
                if (result != null) {
                    copyDetails(authentication, result);
                    return result;
                }
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }
        
        if (lastException != null) {
            throw lastException;
        }
        
        throw new ProviderNotFoundException("No AuthenticationProvider found for " + toTest.getName());
    }
    
    // 其它方法...
}

ProviderManager是AuthenticationManager的实现,它委托一系列AuthenticationProvider来处理认证请求。

2.4 DaoAuthenticationProvider

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    
    private UserDetailsService userDetailsService;
    private PasswordEncoder passwordEncoder;
    
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            throw new BadCredentialsException("Bad credentials");
        }
        
        String presentedPassword = authentication.getCredentials().toString();
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            throw new BadCredentialsException("Bad credentials");
        }
    }
    
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        try {
            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 notFound) {
            throw notFound;
        }
        catch (Exception repositoryProblem) {
            throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
        }
    }
    
    // 其它方法...
}

DaoAuthenticationProvider通过UserDetailsService获取用户信息,然后使用PasswordEncoder验证密码。

三、授权流程源码分析

3.1 FilterSecurityInterceptor

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    
    private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        invoke(new FilterInvocation(request, response, chain));
    }
    
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            if (fi.getRequest() != null && observeOncePerRequest) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }
            
            InterceptorStatusToken token = super.beforeInvocation(fi);
            
            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }
            
            super.afterInvocation(token, null);
        }
    }
    
    // 其它方法...
}

FilterSecurityInterceptor是过滤器链中最后一个过滤器,负责对请求进行访问控制决策。

3.2 AccessDecisionManager

public interface AccessDecisionManager {
    
    void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException;
    
    boolean supports(ConfigAttribute attribute);
    
    boolean supports(Class<?> clazz);
}

AccessDecisionManager接口定义了授权决策的方法,由具体实现类决定是否允许访问。

3.3 AffirmativeBased

public class AffirmativeBased extends AbstractAccessDecisionManager {
    
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException {
        int deny = 0;
        
        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);
            
            switch (result) {
                case AccessDecisionVoter.ACCESS_GRANTED:
                    return;
                case AccessDecisionVoter.ACCESS_DENIED:
                    deny++;
                    break;
                default:
                    break;
            }
        }
        
        if (deny > 0) {
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }
        
        // 如果没有voter投赞成票,根据allowIfAllAbstainDecisions决定是否允许访问
        checkAllowIfAllAbstainDecisions();
    }
    
    // 其它方法...
}

AffirmativeBased是AccessDecisionManager的一个实现,只要有一个投票器投票通过,就允许访问。

四、过滤器链构建过程

4.1 WebSecurity

public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
        implements SecurityBuilder<Filter>, ApplicationContextAware {
    
    private List<SecurityFilterChain> securityFilterChains = new ArrayList<>();
    
    @Override
    protected Filter performBuild() throws Exception {
        // ...
        int chainSize = this.securityFilterChains.size();
        if (chainSize > 0) {
            return VirtualFilterChain.createChainProxy(this.securityFilterChains);
        }
        // ...
    }
    
    // 其它方法...
}

WebSecurity负责构建过滤器链。

4.2 HttpSecurity

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
    
    @Override
    protected DefaultSecurityFilterChain performBuild() {
        filters.sort(comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }
    
    // 其它方法...
}

HttpSecurity负责配置单个SecurityFilterChain中的过滤器。

五、常见过滤器解析

在Spring Security中,请求会经过一系列过滤器,下面是一些关键过滤器的源码分析:

5.1 SecurityContextPersistenceFilter

public class SecurityContextPersistenceFilter extends GenericFilterBean {
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
        SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
        
        try {
            SecurityContextHolder.setContext(contextBeforeChainExecution);
            
            chain.doFilter(holder.getRequest(), holder.getResponse());
            
        }
        finally {
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            SecurityContextHolder.clearContext();
            repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
        }
    }
    
    // 其它方法...
}

SecurityContextPersistenceFilter负责在请求处理前加载SecurityContext,在请求处理后保存SecurityContext。

5.2 LogoutFilter

public class LogoutFilter extends GenericFilterBean {
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        if (requiresLogout(request, response)) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            
            if (logger.isDebugEnabled()) {
                logger.debug("Logging out user '" + auth + "' and transferring to logout destination");
            }
            
            this.handler.logout(request, response, auth);
            
            logoutSuccessHandler.onLogoutSuccess(request, response, auth);
            
            return;
        }
        
        chain.doFilter(request, response);
    }
    
    // 其它方法...
}

LogoutFilter处理用户退出登录的请求。

六、注解式安全配置实现原理

6.1 @EnableWebSecurity

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class, HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
    
    boolean debug() default false;
}

@EnableWebSecurity导入了WebSecurityConfiguration等配置类。

6.2 WebSecurityConfiguration

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration {
    
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});
            webSecurity.apply(adapter);
        }
        
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            webSecurity.apply(webSecurityConfigurer);
        }
        
        return webSecurity.build();
    }
    
    // 其它方法...
}

WebSecurityConfiguration负责创建springSecurityFilterChain。

6.3 @PreAuthorize源码

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Repeatable(PreAuthorizes.class)
public @interface PreAuthorize {
    
    String value();
}

@PreAuthorize是方法级的安全注解,用于指定访问控制表达式。

6.4 MethodSecurityInterceptor

public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {
    
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        InterceptorStatusToken token = super.beforeInvocation(mi);
        
        Object result;
        try {
            result = mi.proceed();
        }
        finally {
            super.finallyInvocation(token);
        }
        
        return super.afterInvocation(token, result);
    }
    
    // 其它方法...
}

MethodSecurityInterceptor拦截带有安全注解的方法调用,进行访问控制决策。

七、OAuth2集成源码分析

7.1 OAuth2LoginConfigurer

public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
        extends AbstractAuthenticationFilterConfigurer<B, OAuth2LoginConfigurer<B>, OAuth2LoginAuthenticationFilter> {
    
    @Override
    public void init(B http) throws Exception {
        OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
                this.authorizationRequestRepository, this.authorizationRequestRepository);
        
        authenticationFilter.setAuthenticationManager(
                this.authenticationManager(http));
        
        // 设置各种属性...
        
        this.setAuthenticationFilter(authenticationFilter);
        super.init(http);
    }
    
    // 其它方法...
}

OAuth2LoginConfigurer配置OAuth2登录流程。

7.2 OAuth2AuthorizationRequestRedirectFilter

public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String registrationId = this.resolveRegistrationId(request);
        if (registrationId == null) {
            filterChain.doFilter(request, response);
            return;
        }
        
        ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
        if (clientRegistration == null) {
            throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
        }
        
        OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver
                .resolve(request, clientRegistration);
        if (authorizationRequest == null) {
            throw new IllegalStateException(
                    "Unable to resolve OAuth2 Authorization Request for Client Registration: " + registrationId);
        }
        
        this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
        
        URI redirectUri = URI.create(authorizationRequest.getAuthorizationRequestUri());
        this.authorizationRedirectStrategy.sendRedirect(request, response, redirectUri.toString());
    }
    
    // 其它方法...
}

OAuth2AuthorizationRequestRedirectFilter负责处理OAuth2授权请求的重定向。

八、总结与心得

通过对Spring Security源码的阅读和分析,我对其核心工作流程有了更深入的理解:

  1. 安全过滤器链是Spring Security的核心,它通过一系列过滤器来处理HTTP请求的安全性。
  2. 认证流程主要通过AuthenticationManager和AuthenticationProvider来实现,支持多种认证方式。
  3. 授权决策由AccessDecisionManager负责,通过投票机制来决定是否允许访问。
  4. Spring Security的配置非常灵活,可以通过Java配置、XML配置或注解来实现。
  5. Spring Security与OAuth2的集成是通过专门的过滤器和配置类来实现的。

评论

博主关闭了当前页面的评论