在Java企业级开发中,安全管理方面的框架非常少,一般来说,主要是三种方案:
Spring Security基于Spring框架,提供了一套Web应用安全性的完整解决方案。一般来说,Web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
Spring Security集成的主流认证机制包括:
用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security 还提供了很多按钮全管理的“周边功能”,例如,CSRF攻击、会话固定攻击等,同时Spring Security 还提供了 HTTP 防火墙来拦截大量的非法请求。
在 Spring Security 中,用户的认证信息主要由 Authentication 的实现类来保存,Authentication 接口定义如下:
public interface Authentication extends Principal, Serializable {
// 用来获取用户的权限
Collection<? extends GrantedAuthority> getAuthorities();
// 用来获取用户凭证,一般来说就是密码
Object getCredentials();
// 用来获取用户携带的详细信息,可能是当前请求之类等
Object getDetails();
// 有用来获取当前用户,例如一个用户名或者一个用户对象
Object getPrincipal();
// 当前用户是否认证成功
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
当用户使用用户名/密码登录或者使用 Remember-me 登录时,都会对应一个不同的 Authentication 实例。
Spring Security 中的认证工作主要是由 AuthenticationManager接口来负责:
public interface AuthenticationManager {
/** AuthenticationManager 只有一个 authenticate 方法可以用来做认证,该方法有三个不同的返回值:
* 1. 返回 Authentication,表示认证成功
* 2. 抛出 AuthenticationException 异常,表示用户输入了无效的凭证。
* 3. 返回null,表示不能断定。
*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
在介绍了 AuthenticationManager 之后,我们介绍 AuthenticationManager 的主要实现类 ProviderManager,ProviderManager 管理了众多的 AuthenticationProvider 实例。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private List<AuthenticationProvider> providers;
public ProviderManager(AuthenticationProvider... providers) {
this(Arrays.asList(providers), (AuthenticationManager)null);
}
public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) {
this.eventPublisher = new NullEventPublisher();
this.providers = Collections.emptyList();
this.messages = SpringSecurityMessageSource.getAccessor();
this.eraseCredentialsAfterAuthentication = true;
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
this.checkState();
}
}
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
// supports 方法用来判断是否支持给定的 Authentication 类型
boolean supports(Class<?> authentication);
}
由于 Authentication 拥有众多的实现类,这些不同的实现类又有不同的 AuthenticationProvider 来处理,所以 AuthenticationProvider 会有一个 supports 方法来判断当前的 AuthenticationProvider 是否支持对应的 Authentication。
再一次完整的认证过程中,可能会存在多个 AuthenticationProvider (比如一个项目同时存在form表单登录和短信验证码登录),多个AuthenticationProvider 统一由 ProviderManager 来管理。如果所有的 AuthenticationProvider 都认证失败了,那么就会电泳 parent 进行认证,相当于是一个备用认证方式。
在 Sping Security 的授权体系中,有两个关键接口:
AccessDecisionManager
它是一个决策器,来决定此次访问是否被允许。
AccessDecisionVoter
它是一个投票器,投票器会检查用户是否具备应有的角色,进而投出赞成、反对或弃权。
它们都有众多的实现类,在 AccessDecisionManager 会挨个访问 AccessDecisionVoter,进而决定是否允许用户访问,因此,AccessDecisionVoter 和 AccessDecisionManager 的关系似于 AuthenticationProvider 和 ProviderManager 之间的关系。
在 Spring Security 中,认证和授权都是基于过滤器来完成的。这些过滤器按照既定的优先级排列,最终形成一个过滤器链。开发者也可以通过自定义过滤器,并通过@Order注解来调整自定义过滤器在过滤器链中的位置,并通过FiliterChainProxy来统一管理。Spring Security 中的过滤器链通过FiliterChainProxy 嵌入到 Web 项目的原生过滤器链中。
当用户登录成功后,Spring Security会将登录成功的用户信息保存到 SecurityContextHolder 中。存入其中的数据默认通过ThreadLocal 来实现的。用户数据是和当前请求线程绑定在一起的。当登录请求处理完毕后, Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到Session 中, 同时,将 SecurityContextHolder 中的数据清空。
《深入浅出 Spring Security》 王松 著