认证和授权
应用安全性大致归结为两个独立的问题:认证(Authentication)和授权(Authorization)。认证相当于“你是谁”,授权相当于“你被允许做什么”。授权还有另外一个叫法,访问控制(Access Control)。Spring Security的架构设计将这两部分区分开来,并且两者都有自己的策略以及拓展点。
认证
认证主要的策略接口是AuthenticationManager
1 | public interface AuthenticationManager { |
authenticate()**方法返回一个Authentication时代表认证通过,认证不通过时抛出 AuthenticationException,无法判定时返回null。AuthenticationException 是一个运行时异常。抛出异常时,一般前台页面会提示认证失败并且后端服务会返回401并且响应头中会有名为WWW-Authenticate的header。
**AuthenticationManager的默认实现是ProviderManager,它内部实现是委托一组AuthenticationProvider的实例完成authenticate方法逻辑。AuthenticationProvider接口类似于AuthenticationManager接口,它提供一个额外的support方法来表示自己支持哪种Authentication的认证。
1 | public interface AuthenticationProvider { |
supports()方法中的Class<?> 参数实际上是 Class<? extends Authentication>。ProviderManager通过委托一组AuthenticationProvider实现在同一个应用中支持多种不同的认证机制。如果某一个Authentication无法被ProviderManager识别,那么这个Authentication将被跳过。
一个ProviderManager有一个可选的parent,当所有的providers返回null时,它可以咨询他的parent。如果没有配parent,当所有providers返回null时,ProviderManager会抛出AuthenticationException。
有时,一个应用中受保护资源具有逻辑组关系时(比如所有的web资源匹配/api/ ),每个组都可以拥有他们各自的 AuthenticationManager。这些 ProviderManager拥有同一个parent,这个parent代表一些全局的资源,作为所有providers的fallback。
自定义Authentication Managers
Spring Security 提供了一些configuration helpers以快速帮你配置一些常用的authentication manager功能。最常用的helper是AuthenticationManagerBuilder,它可以创建内存、JDBC或者LDAP userdetails,或者添加自定义的UserDetailsService。下面这个例子是配置一个global(parent) AuthenticationManager:
1 |
|
注意:AuthenticationManagerBuilder是被*@Autowired到一个@Bean*的方法中。这个AuthenticationManagerBuilder是配置global(parent)的AuthenticationManager。
相反:
1 |
|
重写Configurer的方法,此时的AuthenticationManagerBuilder配置的是local的AuthenticationManager,它是globalAuthenticationManager的子级。在Spring Boot 应用中,可以 @Autowired global的AuthenticationManager到某个bean中,但是local的AuthenticationManager不可以这样做。通常情况下,只需要配置localAuthenticationManager就可以。
授权(Access Control)
授权的核心策略接口是AccessDecisionManager。框架提供了三种实现,并且它们都委托一组AccessDecisionVoter,就像ProviderManager委托AuthenticationProviders一样。
1 | public interface AccessDecisionVoter<S> { |
Authentication代表一个用户凭证(同时也是授权逻辑的调用者),object是被保护的对象(用户想要访问的资源,可以是一个web资源也可以是一个java方法),attributes是与object相关联的配置属性(例如,角色名)。
Web 安全性
Spring Security在web层(UI和接口层)是基于Servlet Filters。Spring Security仅仅是Filter链中的一个Filter,具体的类型是FilterChainProxy。在Spring Boot应用中,security filter是上下文中的一个@Bean,SecurityProperties.DEFAULT_FILTER_ORDER是它的默认顺序。在web容器的角度看,Spring Security是一个单独的Filter。但是从内部看,它包含很多不同角色的Filter。
事实上,security filter和filter chain中还会有一个中间层,通常是DelegatingFilterProxy,它可以不是上下文中的一个bean。DelegatingFilterProxy委托FilterChainProxy(上下文中的一个bean,有固定的名字:springSecurityFilterChain),FilterChainProxy包含所有spring security filter。所有的spring security filter都实现servlet规范中的filter接口。一个FilterChainProxy可以包含多个filter chain,他们被spring security管理,web容器无法感知他们的存在。
创建和自定义Filter Chains
Spring Boot应用中默认的fallback filter chain 匹配url /** ,可以通过security.basic.enabled=false关闭它,或者可以把它作为一个fallback,用更低的order定义自己的规则(添加一组filter chain):添加一个类型为 WebSecurityConfigurerAdapter (或者 WebSecurityConfigurer)的@Bean,并在类上注明order。
1 |
|
分发和授权的请求匹配
一个security filter chain(或者说WebSecurityConfigurerAdapter)有一个request matcher用来决定是否匹配某个HTTP请求,一旦匹配,其他的security filter chain不会再匹配这个HTTP请求。但是在一个filter chain内部,可以通过向HttpSecurity添加额外的matchers实现更细粒度的授权控制。
1 |
|
注意:方法中第一行和第三、四行方法名是不同的。第一行是整个filter chain的request matcher。
方法安全
Spring Security同样可以保护Java方法的调用。通过如下方法开启:
1 |
|
然后可以直接在要被保护的方法上添加注释:
1 |
|
线程相关
Spring Security是与线程绑定的。想要在代码中手动获取Authentication可以通过以下方式:
1 | SecurityContext context = SecurityContextHolder.getContext(); |
controller层:
1 |
|
异步处理Secure Methods
因为SecurityContext是线程绑定的,所以任何对Secure Methods异步的调用,需要主要上文的传递。这就需要将SecurityContext包装进任务中(Runnable, Callable等等)。Spring Security提供了一些helpers。
1 |
|