文章目录
- 1. 授权服务器过滤器
- 1. 常用的过滤器
- 2. 工作原理
- 2. 密码模式获取访问令牌
- 1. 工作流程
- 2. 用户凭证验证
- 1. ResourceOwnerPasswordTokenGranter
- 2. ProviderManager
- 3. CustomAuthProvider
- 4. 认证后的结果
1. 授权服务器过滤器
在Spring Security中,OAuth2授权服务器的过滤器是处理OAuth2授权流程的核心组件之一。它们在请求进入授权服务器时被应用,以确保请求的合法性,并执行授权和令牌处理。
1. 常用的过滤器
AuthorizationEndpointFilter
:
- 处理
/oauth/authorize
请求,它是OAuth2授权流程的核心过滤器。 - 负责处理客户端请求授权码或访问令牌的过程。这个过滤器会检查请求的有效性、处理用户的认证信息、并将请求引导至授权页面(通常是一个登录页面或授权确认页面)。
TokenEndpointFilter
:
- 处理
/oauth/token
请求,这是获取访问令牌和刷新令牌的核心过滤器。 - 负责处理各种授权方式的令牌颁发过程,包括授权码模式、密码模式、客户端凭据模式等。
ClientCredentialsTokenEndpointFilter
:
- 专门处理客户端凭据授权模式的令牌请求。
- 负责验证客户端的身份并直接发放访问令牌,因为客户端凭据模式不涉及用户的授权确认。
CheckTokenEndpointFilter
:
- 处理令牌的检查请求,通常位于
/oauth/check_token
路径下。 - 用于验证令牌的有效性,通常是资源服务器用来验证访问令牌是否有效的一个过滤器。
OAuth2LoginAuthenticationFilter
:
- 处理OAuth2登录流程,处理授权码登录或隐式授权流程中的登录请求。
- 这个过滤器在接收到OAuth2的登录请求后,执行OAuth2的认证流程,并在认证成功后生成相应的OAuth2授权信息。
2. 工作原理
① 过滤器链:在Spring Security的配置中,这些过滤器通常被配置在一个过滤器链中,这样每个请求都会经过这些过滤器,并根据请求路径、请求参数和方法来决定该请求需要经过哪些过滤器的处理。
② 安全配置:Spring Security OAuth2的配置通过AuthorizationServerConfigurerAdapter
类来进行。在这个类中,你可以配置上述的过滤器,并指定授权路径、令牌路径、客户端详细信息服务、以及各种授权方式的处理逻辑。
③ 令牌存储:这些过滤器通常会结合令牌存储(例如内存存储、数据库存储或JWT存储)一起使用,以管理访问令牌和刷新令牌的生成、存储、验证和撤销。
2. 密码模式获取访问令牌
1. 工作流程
① 验证客户端:首先,TokenEndpointFilter
会调用 ClientCredentialsTokenEndpointFilter
验证 client_id
和 client_secret
,确保客户端的合法性。
② 用户凭证验证:如果客户端验证通过,ResourceOwnerPasswordTokenGranter
会验证 username
和 password
。如果用户凭证正确,则生成访问令牌。
③ 生成和返回令牌:如果所有验证通过,过滤器会生成访问令牌并返回给客户端。
这套流程确保了在密码模式下,只有正确的客户端和用户组合才能成功获取访问令牌。
2. 用户凭证验证
1. ResourceOwnerPasswordTokenGranter
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "password";
private final AuthenticationManager authenticationManager;
public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String username = parameters.get("username");
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
2. ProviderManager
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;
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, null);
}
public ProviderManager(List<AuthenticationProvider> providers,
AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
public void afterPropertiesSet() {
checkState();
}
private void checkState() {
if (parent == null && providers.isEmpty()) {
throw new IllegalArgumentException(
"A parent AuthenticationManager or a list "
+ "of AuthenticationProviders is required");
}
}
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;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
@SuppressWarnings("deprecation")
private void prepareException(AuthenticationException ex, Authentication auth) {
eventPublisher.publishAuthenticationFailure(ex, auth);
}
/**
* Copies the authentication details from a source Authentication object to a
* destination one, provided the latter does not already have one set.
*
* @param source source authentication
* @param dest the destination authentication object
*/
private void copyDetails(Authentication source, Authentication dest) {
if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;
token.setDetails(source.getDetails());
}
}
public List<AuthenticationProvider> getProviders() {
return providers;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setAuthenticationEventPublisher(
AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
this.eraseCredentialsAfterAuthentication = eraseSecretData;
}
public boolean isEraseCredentialsAfterAuthentication() {
return eraseCredentialsAfterAuthentication;
}
private static final class NullEventPublisher implements AuthenticationEventPublisher {
public void publishAuthenticationFailure(AuthenticationException exception,
Authentication authentication) {
}
public void publishAuthenticationSuccess(Authentication authentication) {
}
}
}
3. CustomAuthProvider
@Component
public class CustomAuthProvider implements AuthenticationProvider {
@Autowired
private CustomUserDetailService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
UserDetails userDetails = userDetailService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(authentication.getDetails());
return usernamePasswordAuthenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}