Spring Security实战之认证
本文是基于Security 6版本进行讲解
一、认证流程
1.基本组件
SecurityContextHolder
Spring Security用来存储身份认证者详细信息的地方。对于SecurityContextHolder,Spring Security不关心它是如何被填充的,即若是它包含一个值,它就被当做当前认证的用户。所以简单来说,我们可以直接设置SecurityContextHolder来表示以认证的用户(当然没人会这么做)。
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
默认情况下,SecurityContextHolder是使用ThreadLocal来存储SecurityContext的,即在同一个线程中,始终可以使用SecurityContext。
SecurityContext
定义与当前执行线程最小的 security 信息。
Authentication
用来存储用户认证信息。主要有两个作用
- 用户未认证时,用来封装用户提供的认证凭据,然后作为参数传递给AuthenticationManager进行身份认证。
- 用户已认证时,Authentication就是用来存储用户的认证信息,可以通过
SecurityContextHolder.getContext().getAuthentication()
获取。
它又包含三部分:
- principal:用户的识别。
- credentials:通常表示密码,当用户身份认证通过后被清除。
- authorities:权限,通常就是角色或范围。
AuthenticationManager
用来定义Spring Security的过滤器如何执行身份认证的API。
public interface AuthenticationManager {
//身份认证方法,认证成功则会返回完全填充的Authentication,简单来说认证成功Authentication中的isAuthenticated会返回true
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
ProviderManager
是AuthenticationManager
最常用的实现类。ProviderManager内部维护了一个AuthenticationProvider类型的列表,认证时会遍历调用这个列表中的所有AuthenticationProvider,即所有的AuthenticationProvider都有机会表明身份认证结果是成功、失败还是无法判断,并交给下游的AuthenticationProvider做出判断。若所有的AuthenticationProvider都无法验证,则抛出ProviderNotFoundException异常。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
//认证逻辑
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//获取需要进行认证的 Authentication
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();
//遍历调用providers中的每一个AuthenticationProvider进行认证
for (AuthenticationProvider provider : getProviders()) {
//判断该AuthenticationProvider是否支持此类型的Authentication进行认证认证
//不同的方式可以有不同的Authentication,不同的Authentication又对于不同的AuthenticationProvider
if (!provider.supports(toTest)) {
continue;
}
try {
//如果认证成功,就返回填充满的Authentication
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
}
//兜底方案,如果上面都没认证成功,则使用parent进行认证
if (result == null && this.parent != null) {
// Allow the parent to try.