在具体学习Spring Security各种方法用法之前,我们先介绍下Spring Security中常见的概念,以及认证、授权的思路,方便读者整体把握Spring Security架构,这里涉及的所有组件在后面的章节中还会详细介绍。
认证
Spring Security在架构设计中认证(Authentication)和授权(Authorization)是分开的后面我们会学习到。无论采用什么样的认证方式都不会影响到授权,这是两个独立的存在。这种独立带来的好处之一就是Spring Security可以非常方便的整合一些外部的认证方案。
在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;
}
- getAuthorities()方法:用来获取用户权限
- getCredentials()方法:用来获取用户凭证,一般来说是用户的密码
- getDetails()方法:用来获取用户携带的详细信息,可能是当前请求之类等
- getPrincipal()方法:用来获取当前用户,例如一个用户名或者一个用户对象
- isAuthenticated()方法:当前用户是否认证成功
当用户使用用户名和密码登录或者采用Remember-me登录时,都会对应一个不同的Authentication对象实例。
Spring Security中的认证工作主要由AuthenticationManager接口来负责,我们看看该接口的定义:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
AuthenticationManager就只有一个认证方法,返回一个认证对象Authentication。
AuthenticationManager的主要实现类是ProviderManager。ProviderManager管理了很多的AuthenticationProvider实例,AuthenticationProvider有点类似AuthenticationManager,但是它多了一个supports方法来判断是否支持给定的Authentication类型。由于Authentication拥有众多不同的实现类型,这些不同的实现类又由不同的AuthenticationProvider来处理,所以这里需要一个supports是方法来判断当前的AuthenticationProvider是否支持对应的Authentication。再一次完成认证流程中,可能会同时存在多个AuthenticationProvider,例如项目中同时支持表单登录和短信验证码登录。多个AuthenticationProvider统一由ProviderManager来管理。同时,ProviderManager具有一个可选的parent,如果所有的AuthenticationProvider都认证失败的话就会调用parent进行认证。parent相当于一个备用的认证方式。
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
我们来看看ProviderManager的认证方法:authenticate()
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
//这里会遍历所有的AuthenticationProvider判断当前的Authentication是否支持
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
//如果支持的话,就调用AuthenticationProvider的认证方法
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
if (result == null && this.parent != null) {
try {
//没有AuthenticationProvider处理的话就就调用ProviderManager在构造函数中传进来的一个名字叫parent的AuthenticationProvider来认证
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
}
授权
当人成认证之后接下来就是授权了。在Spring Security的授权体系中有两个关键的接口:
- AccessDecisionMamager
- AccessDecisionVoter
AccessDecisionVoter是一个投票器,投票器会检查用户是否具备应 有的角色,进而投出赞成、反对或者弃权票;AccessDecisionManager则 是一个决策器,来决定此次访问是否被允许。AccessDecisionVoter和 AccessDecisionManager都有众多的实现类,在 AccessDecisionManager 中会挨个遍历AccessDecisionVoter ,进而决定是否允许用户访问,因而 AccessDecisionVoter和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider和 ProviderManager的关系。
在Spring Security中,用户请求一个资源(通常是一个网络接口或 者一个Java 方法)所需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute中只有一个 getAttribute 方法,该方法返回一个 String 字符 串,就是角色的名称。一般来说,角色名称都带有一个ROLE_ 前缀,投票器AccessDecisionVoter 所做的事情,其实就是比较用户所具备的角色 和请求某个资源所需的ConfigAttribute之间的关系。
小结
看完的小伙伴可以在脑海里一边想一遍动手画一下Authentication、AuthenticationManager、AuthenticationProvider、ProviderManager、AccessDecisionMamager、AccessDecisionVoter它们之间的关系。