AuthenticationProvider是Spring Security中的一个核心接口,用于定义验证用户的协议。它主要负责接收Authentication对象(表示用户凭据)并返回已经认证的Authentication对象,如果凭据有效。如果凭据无效,则应该抛出AuthenticationException。 AuthenticationProvider接口包含两个方法:supports和.authenticate。supports方法用于判断当前的AuthenticationProvider是否支持提供的Authentication对象,而authenticate方法则用于执行具体的认证逻辑。
在Spring Security的架构中,AuthenticationManager接收来自HTTP过滤器层的请求,并将认证用户的责任委托给AuthenticationProvider。默认情况下,Spring Security使用ProviderManager类,该类会查询配置的多个AuthenticationProvider,以查看是否有任何一个能够执行认证。这意味着可以配置多个AuthenticationProvider,并且它们会被按声明的顺序查询.
Spring Security提供了多种实现AuthenticationProvider的方式,例如:DaoAuthenticationProvider,它通过UserDetailsService和PasswordEncoder来认证用户名和密码。开发者也可以创建自定义的AuthenticationProvider来实现自己的认证逻辑和用户信息来源.
Spring Security认证流程
在Spring Security中,认证流程通常遵循以下步骤:
- 认证请求:用户通过登录表单提交用户名和密码等认证信息。
- 创建
Authentication对象:Spring Security的SecurityContextHolder会根据提交的信息创建一个Authentication对象。 - 认证管理器(
AuthenticationManager) :SecurityContextHolder中的AuthenticationManager负责处理认证逻辑。 AuthenticationProvider的调用:AuthenticationManager会根据配置的AuthenticationProvider来验证Authentication对象。
AuthenticationProvider接口定义
AuthenticationProvider接口定义了两个主要的方法:
boolean supports(Class<?> authentication):该方法用于确定AuthenticationProvider是否支持给定类型的Authentication对象。Authentication authenticate(Authentication authentication):该方法是认证逻辑的核心,负责验证Authentication对象中的凭据。
认证过程详解
- 提交认证信息:用户提交登录信息(如用户名和密码)。
- 创建
Authentication对象:Spring Security根据提交的信息创建一个Authentication对象,通常是UsernamePasswordAuthenticationToken。 - 调用
AuthenticationManager的authenticate方法:这个调用会触发认证过程。 AuthenticationManager遍历AuthenticationProvider:AuthenticationManager内部维护了一个AuthenticationProvider列表。对于列表中的每一个AuthenticationProvider,AuthenticationManager会调用其supports方法来判断是否能够处理传入的Authentication类型。AuthenticationProvider的authenticate方法:一旦找到支持该类型的AuthenticationProvider,AuthenticationManager会调用它的authenticate方法。- 验证凭据:在
authenticate方法内,AuthenticationProvider会根据具体的认证策略来验证凭据。例如,对于用户名和密码,可能会查询数据库来确认凭据的有效性。 - 返回认证结果:如果凭据验证成功,
AuthenticationProvider会返回一个被赋予了权限信息的Authentication对象;如果失败,则抛出AuthenticationException异常。
Security框架中原始的Provider:客户端校验
public final class ClientSecretAuthenticationProvider implements AuthenticationProvider {
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
private final Log logger = LogFactory.getLog(getClass());
private final RegisteredClientRepository registeredClientRepository;
private final CodeVerifierAuthenticator codeVerifierAuthenticator;
private PasswordEncoder passwordEncoder;
/**
* Constructs a {@code ClientSecretAuthenticationProvider} using the provided parameters.
*
* @param registeredClientRepository the repository of registered clients
* @param authorizationService the authorization service
*/
public ClientSecretAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
OAuth2AuthorizationService authorizationService) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
Assert.notNull(authorizationService, "authorizationService cannot be null");
this.registeredClientRepository = registeredClientRepository;
this.codeVerifierAuthenticator = new CodeVerifierAuthenticator(authorizationService);
this.passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/**
* Sets the {@link PasswordEncoder} used to validate
* the {@link RegisteredClient#getClientSecret() client secret}.
* If not set, the client secret will be compared using
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}.
*
* @param passwordEncoder the {@link PasswordEncoder} used to validate the client secret
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2ClientAuthenticationToken clientAuthentication =
(OAuth2ClientAuthenticationToken) authentication;
if (!ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientAuthentication.getClientAuthenticationMethod()) &&
!ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthentication.getClientAuthenticationMethod())) {
return null;
}
String clientId = clientAuthentication.getPrincipal().toString();
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
if (registeredClient == null) {
throwInvalidClient(OAuth2ParameterNames.CLIENT_ID);
}
//省略,感兴趣可以自己去看源码...
return new OAuth2ClientAuthenticationToken(registeredClient,
clientAuthentication.getClientAuthenticationMethod(), clientAuthentication.getCredentials());
}
@Override
public boolean supports(Class<?> authentication) {
return OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication);
}
private static void throwInvalidClient(String parameterName) {
OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_CLIENT,
"Client authentication failed: " + parameterName,
ERROR_URI
);
throw new OAuth2AuthenticationException(error);
}
}
如何自定义AuthenticationProvider?
开发者可以根据需要实现自定义的AuthenticationProvider,以支持特殊的认证机制,如OAuth、JWT、自定义数据库查询等。自定义AuthenticationProvider需要实现AuthenticationProvider接口,并在Spring Security配置中注册。
Spring Security支持多种不同的认证方式,每种认证方式对应不同的身份类型。为了支持这些不同的认证方式,每个AuthenticationProvider需要实现supports()方法来表明自己支持的认证类型7。例如,如果一个AuthenticationProvider专门用于短信登录,那么它就需要实现该方法来指定它只支持短信登录的认证方式。
在Spring Security的整体架构中,AuthenticationManager接收来自HTTP过滤器层的请求,并将认证用户的责任委托给AuthenticationProvider。如果请求中的用户不是已知的,则需要进行认证。 默认情况下,Spring Security使用ProviderManager类作为AuthenticationManager的实现,它会委托给一系列配置好的AuthenticationProvider去执行认证操作。