Spring Security OAuth2 源码阅读笔记
一、架构概述
Spring Security OAuth2 是基于Spring Security构建的OAuth2实现,提供了完整的授权服务器、资源服务器和客户端支持。通过阅读源码,我们可以深入理解OAuth2的工作原理和Spring的实现方式。
1.1 主要模块
- Authorization Server: 授权服务器,负责颁发令牌
- Resource Server: 资源服务器,负责保护资源
- Client: OAuth2客户端,用于请求访问受保护的资源
1.2 核心接口
- TokenStore: 令牌存储
- ClientDetailsService: 客户端详情服务
- UserDetailsService: 用户详情服务
- TokenGranter: 令牌授予器
- OAuth2RequestFactory: OAuth2请求工厂
二、授权服务器源码分析
2.1 AuthorizationServerSecurityConfigurer
public final class AuthorizationServerSecurityConfigurer extends
SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private AuthenticationManager authenticationManager;
private AuthenticationEntryPoint authenticationEntryPoint;
private PasswordEncoder passwordEncoder;
private String realm = "oauth2/client";
private boolean allowFormAuthenticationForClients = false;
private String tokenKeyAccess = "denyAll()";
private String checkTokenAccess = "denyAll()";
// ...
@Override
public void configure(HttpSecurity http) throws Exception {
// 配置/oauth/token_key和/oauth/check_token端点的安全性
ClientDetailsService clientDetailsService = clientDetailsServiceBuilder.build();
clientDetailsService = new ClientDetailsUserDetailsService(clientDetailsService);
// ...
// 设置OAuth2的客户端认证过滤器
http.addFilterBefore(new ClientCredentialsTokenEndpointFilter(http.getSharedObject(AuthenticationManager.class)),
BasicAuthenticationFilter.class);
// ...
}
// ...
}
AuthorizationServerSecurityConfigurer配置授权服务器的安全性,包括客户端身份验证、端点访问控制等。
2.2 AuthorizationServerEndpointsConfigurer
public final class AuthorizationServerEndpointsConfigurer {
private AuthenticationManager authenticationManager;
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
private TokenStore tokenStore;
private TokenGranter tokenGranter;
private ConsumerTokenServices consumerTokenServices;
private AuthorizationCodeServices authorizationCodeServices;
private UserDetailsService userDetailsService;
private OAuth2RequestFactory requestFactory;
// ...
public TokenGranter getTokenGranter() {
if (tokenGranter == null) {
tokenGranter = new CompositeTokenGranter(getDefaultTokenGranters());
}
return tokenGranter;
}
private List<TokenGranter> getDefaultTokenGranters() {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails, this.requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, this.requestFactory));
tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetails, this.requestFactory));
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, this.requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetails, this.requestFactory));
}
return tokenGranters;
}
// ...
}
AuthorizationServerEndpointsConfigurer配置授权服务器的端点,包括令牌授予器、令牌服务等。
2.3 TokenEndpoint
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
// ...
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
// ...
}
TokenEndpoint是OAuth2 /oauth/token端点的实现,负责处理令牌请求。
2.4 CompositeTokenGranter
public class CompositeTokenGranter implements TokenGranter {
private final List<TokenGranter> tokenGranters;
public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
}
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
for (TokenGranter granter : tokenGranters) {
OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
if (grant != null) {
return grant;
}
}
return null;
}
// ...
}
CompositeTokenGranter是一个组合模式的实现,它包含多个TokenGranter,用于支持不同的授权类型。
2.5 DefaultTokenServices
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices {
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
private TokenEnhancer accessTokenEnhancer;
private AuthenticationManager authenticationManager;
private boolean supportRefreshToken = false;
private boolean reuseRefreshToken = true;
private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
@Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
// The token store could remove the refresh token when the
// access token is removed, but we want to
// be sure...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
// Re-store the access token in case the authentication has changed
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
// 创建刷新令牌
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
// But the refresh token itself might need to be re-issued if it has
// expired.
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
// 创建访问令牌
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
// ...
}
DefaultTokenServices是令牌服务的默认实现,负责创建、刷新和存储令牌。
三、资源服务器源码分析
3.1 ResourceServerSecurityConfigurer
public final class ResourceServerSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private AuthenticationManager authenticationManager;
private ResourceServerTokenServices resourceTokenServices;
private TokenStore tokenStore;
private String resourceId = "oauth2-resource";
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
@Override
public void configure(HttpSecurity http) throws Exception {
// 配置OAuth2AuthenticationProcessingFilter
// ...
}
// ...
}
ResourceServerSecurityConfigurer配置资源服务器的安全性,包括令牌服务、资源ID等。
3.2 OAuth2AuthenticationProcessingFilter
public class OAuth2AuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
private AuthenticationManager authenticationManager;
private AuthenticationEntryPoint authenticationEntryPoint;
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
private OAuth2AuthenticationDetails.TokenExtractor tokenDetailsExtractor = new OAuth2AuthenticationDetails.TokenExtractor();
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2Authentication authentication = loadAuthentication(request);
if (authentication == null) {
throw new BadCredentialsException("Invalid token");
}
return authentication;
}
protected OAuth2Authentication loadAuthentication(HttpServletRequest request) {
final OAuth2AccessToken token = tokenExtractor.extract(request);
if (token == null) {
throw new InvalidTokenException("Token not found");
}
OAuth2Authentication auth = tokenServices.loadAuthentication(token.getValue());
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token.getValue());
}
return auth;
}
// ...
}
OAuth2AuthenticationProcessingFilter负责从请求中提取令牌并验证其有效性。
3.3 BearerTokenExtractor
public class BearerTokenExtractor implements TokenExtractor {
private static final Pattern AUTHORIZATION_PATTERN = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$",
Pattern.CASE_INSENSITIVE);
@Override
public OAuth2AccessToken extract(HttpServletRequest request) {
String tokenValue = extractToken(request);
if (tokenValue != null) {
return new OAuth2AccessToken(tokenValue);
}
return null;
}
protected String extractToken(HttpServletRequest request) {
// 从Authorization头部中提取Bearer令牌
String header = request.getHeader("Authorization");
if (header != null) {
Matcher matcher = AUTHORIZATION_PATTERN.matcher(header);
if (matcher.matches()) {
return matcher.group("token");
}
}
// 从请求参数中提取令牌
String param = request.getParameter("access_token");
if (param != null) {
return param;
}
return null;
}
// ...
}
BearerTokenExtractor从请求中提取Bearer令牌。
四、OAuth2客户端源码分析
4.1 OAuth2ClientContextFilter
public class OAuth2ClientContextFilter extends OncePerRequestFilter {
private OAuth2ClientContext clientContext;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
// 存储当前请求和响应,以便后续使用
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, null);
try {
// 清除现有上下文
OAuth2ClientContext context = new DefaultOAuth2ClientContext(clientContext);
OAuth2ClientContextHolder.setContext(context);
// 执行过滤器链
filterChain.doFilter(servletRequest, servletResponse);
} catch (OAuth2Exception e) {
// 处理OAuth2异常
if (e instanceof UserRedirectRequiredException) {
UserRedirectRequiredException redirect = (UserRedirectRequiredException) e;
String redirectUri = redirect.getRedirectUri();
// 保存当前请求信息,用于重定向后恢复
// 重定向到授权服务器
// ...
}
else {
throw e;
}
} finally {
// 清除上下文
OAuth2ClientContextHolder.clearContext();
}
}
// ...
}
OAuth2ClientContextFilter管理OAuth2客户端上下文,处理重定向等操作。
4.2 OAuth2RestTemplate
public class OAuth2RestTemplate extends RestTemplate {
private final OAuth2ClientContext context;
private final AccessTokenProvider accessTokenProvider;
private final OAuth2ProtectedResourceDetails resource;
@Override
protected ClientHttpRequest createRequest(URI uri, HttpMethod method) throws IOException {
OAuth2AccessToken accessToken = getAccessToken();
ClientHttpRequest request = super.createRequest(uri, method);
request.getHeaders().set("Authorization", "Bearer " + accessToken.getValue());
return request;
}
protected OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
OAuth2AccessToken accessToken = context.getAccessToken();
if (accessToken == null || accessToken.isExpired()) {
try {
accessToken = acquireAccessToken(context);
}
catch (UserRedirectRequiredException e) {
context.setAccessToken(null);
throw e;
}
}
return accessToken;
}
protected OAuth2AccessToken acquireAccessToken(OAuth2ClientContext oauth2Context)
throws UserRedirectRequiredException {
AccessTokenRequest accessTokenRequest = oauth2Context.getAccessTokenRequest();
// 通过AccessTokenProvider获取令牌
OAuth2AccessToken accessToken = accessTokenProvider.obtainAccessToken(resource, accessTokenRequest);
oauth2Context.setAccessToken(accessToken);
return accessToken;
}
// ...
}
OAuth2RestTemplate是RestTemplate的扩展,自动处理OAuth2令牌的获取和使用。
五、令牌存储实现分析
5.1 InMemoryTokenStore
public class InMemoryTokenStore implements TokenStore {
private final ConcurrentHashMap<String, OAuth2AccessToken> accessTokenStore = new ConcurrentHashMap<String, OAuth2AccessToken>();
private final ConcurrentHashMap<String, OAuth2Authentication> authenticationStore = new ConcurrentHashMap<String, OAuth2Authentication>();
private final ConcurrentHashMap<String, OAuth2RefreshToken> refreshTokenStore = new ConcurrentHashMap<String, OAuth2RefreshToken>();
private final ConcurrentHashMap<String, String> accessTokenToRefreshToken = new ConcurrentHashMap<String, String>();
private final ConcurrentHashMap<String, String> refreshTokenToAccessToken = new ConcurrentHashMap<String, String>();
private final ConcurrentHashMap<String, Collection<OAuth2AccessToken>> authenticationToAccessTokenStore = new ConcurrentHashMap<String, Collection<OAuth2AccessToken>>();
private final ConcurrentHashMap<String, Collection<OAuth2AccessToken>> clientIdToAccessTokenStore = new ConcurrentHashMap<String, Collection<OAuth2AccessToken>>();
private final ConcurrentHashMap<String, Collection<OAuth2AccessToken>> userNameToAccessTokenStore = new ConcurrentHashMap<String, Collection<OAuth2AccessToken>>();
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
// 根据认证信息查找访问令牌
// ...
}
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
// 存储访问令牌
String refreshToken = null;
if (token.getRefreshToken() != null) {
refreshToken = token.getRefreshToken().getValue();
}
if (refreshToken != null) {
// 关联访问令牌和刷新令牌
accessTokenToRefreshToken.put(token.getValue(), refreshToken);
refreshTokenToAccessToken.put(refreshToken, token.getValue());
}
// 存储令牌和认证信息
accessTokenStore.put(token.getValue(), token);
authenticationStore.put(token.getValue(), authentication);
// 添加到认证信息、客户端ID和用户名到访问令牌的映射
// ...
}
@Override
public void removeAccessToken(OAuth2AccessToken token) {
// 删除访问令牌
// ...
}
// 其他方法省略
// ...
}
InMemoryTokenStore是使用内存存储令牌的实现,适用于单实例应用。
5.2 JdbcTokenStore
public class JdbcTokenStore implements TokenStore {
private final JdbcTemplate jdbcTemplate;
private static final String DEFAULT_ACCESS_TOKEN_INSERT_STATEMENT = "insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)";
private static final String DEFAULT_ACCESS_TOKEN_SELECT_STATEMENT = "select token_id, token from oauth_access_token where token_id = ?";
// 其他SQL语句
// ...
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
String refreshToken = null;
if (token.getRefreshToken() != null) {
refreshToken = token.getRefreshToken().getValue();
}
// 使用JDBC存储访问令牌
jdbcTemplate.update(insertAccessTokenSql,
new Object[] {
extractTokenKey(token.getValue()),
serializeAccessToken(token),
authenticationKeyGenerator.extractKey(authentication),
authentication.isClientOnly() ? null : authentication.getName(),
authentication.getOAuth2Request().getClientId(),
serializeAuthentication(authentication),
extractTokenKey(refreshToken)
});
}
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
OAuth2AccessToken accessToken = null;
try {
accessToken = jdbcTemplate.queryForObject(selectAccessTokenSql,
new RowMapper<OAuth2AccessToken>() {
public OAuth2AccessToken mapRow(ResultSet rs, int rowNum) throws SQLException {
return deserializeAccessToken(rs.getBytes(2));
}
}, extractTokenKey(tokenValue));
}
catch (EmptyResultDataAccessException e) {
// 令牌不存在
}
return accessToken;
}
// 其他方法省略
// ...
}
JdbcTokenStore使用关系型数据库存储令牌,适用于集群环境。
六、授权类型实现分析
6.1 AuthorizationCodeTokenGranter
public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
private final AuthorizationCodeServices authorizationCodeServices;
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = tokenRequest.getRequestParameters();
String authorizationCode = parameters.get("code");
String redirectUri = parameters.get("redirect_uri");
// 校验授权码
if (authorizationCode == null) {
throw new InvalidRequestException("An authorization code must be supplied.");
}
// 消费授权码,同时获取认证信息
OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
if (storedAuth == null) {
throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
}
// 校验重定向URI
OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();
String pendingClientId = pendingOAuth2Request.getClientId();
String clientId = tokenRequest.getClientId();
if (clientId != null && !clientId.equals(pendingClientId)) {
throw new InvalidGrantException("Client ID mismatch");
}
String pendingRedirectUri = pendingOAuth2Request.getRedirectUri();
if (pendingRedirectUri != null && redirectUri != null && !pendingRedirectUri.equals(redirectUri)) {
throw new RedirectMismatchException("Redirect URI mismatch.");
}
// 创建新的OAuth2Request,继承原有的OAuth2Request中的参数
OAuth2Request oauth2Request = pendingOAuth2Request.createOAuth2Request(client);
// 创建OAuth2Authentication
OAuth2Authentication authentication = new OAuth2Authentication(oauth2Request, storedAuth.getUserAuthentication());
// 创建访问令牌
OAuth2AccessToken accessToken = getTokenServices().createAccessToken(authentication);
return accessToken;
}
// ...
}
AuthorizationCodeTokenGranter实现了授权码授权类型。
6.2 RefreshTokenGranter
public class RefreshTokenGranter extends AbstractTokenGranter {
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
String refreshTokenValue = tokenRequest.getRequestParameters().get("refresh_token");
if (refreshTokenValue == null) {
throw new InvalidRequestException("Missing refresh token");
}
// 加载刷新令牌
OAuth2RefreshToken refreshToken = tokenServices.getRefreshToken(refreshTokenValue);
if (refreshToken == null) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
}
// 校验刷新令牌是否过期
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
if (expiringToken.getExpiration() != null
&& expiringToken.getExpiration().before(new Date())) {
tokenServices.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Invalid refresh token (expired): " + refreshTokenValue);
}
}
// 获取认证信息
OAuth2Authentication authentication = tokenServices.loadAuthentication(refreshTokenValue);
// 校验客户端ID
String clientId = authentication.getOAuth2Request().getClientId();
if (clientId != null && !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
}
// 创建新的访问令牌
return tokenServices.refreshAccessToken(refreshTokenValue, tokenRequest);
}
// ...
}
RefreshTokenGranter实现了刷新令牌授权类型。
七、OAuth2异常处理
7.1 OAuth2Exception
public class OAuth2Exception extends RuntimeException {
private String summary;
private String oAuth2ErrorCode;
private int httpStatusCode;
// 构造方法和getter/setter方法
// ...
}
OAuth2Exception是OAuth2错误的基类。
7.2 OAuth2ExceptionRenderer
public class DefaultOAuth2ExceptionRenderer implements OAuth2ExceptionRenderer {
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
@Override
public void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest) throws Exception {
if (responseEntity == null) {
return;
}
HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
// 查找适合的消息转换器
Class<?> clazz = responseEntity.getBody().getClass();
List<MediaType> acceptedMediaTypes = webRequest.getRequest().getHeaders("Accept");
MediaType.sortByQualityValue(acceptedMediaTypes);
// 使用消息转换器将响应实体输出到响应流中
for (MediaType acceptedMediaType : acceptedMediaTypes) {
for (HttpMessageConverter messageConverter : messageConverters) {
if (messageConverter.canWrite(clazz, acceptedMediaType)) {
messageConverter.write(responseEntity.getBody(), acceptedMediaType, outputMessage);
return;
}
}
}
// 如果没有找到适合的消息转换器,使用默认的JSON转换器
// ...
}
// ...
}
OAuth2ExceptionRenderer负责将OAuth2异常渲染成HTTP响应。
八、总结与心得
通过对Spring Security OAuth2源码的阅读和分析,我对OAuth2的实现机制有了更深入的理解:
-
Spring Security OAuth2通过各种组件(授权服务器、资源服务器、客户端)协同工作,实现完整的OAuth2流程。
-
令牌的生成、存储和验证是OAuth2的核心功能,Spring Security OAuth2提供了多种令牌存储实现(内存、数据库等)。
-
授权服务器支持多种授权类型(授权码、隐式授权、密码、客户端凭证、刷新令牌),每种授权类型都有独立的实现类。
-
Spring Security OAuth2使用过滤器链来处理OAuth2请求,例如TokenEndpointAuthenticationFilter、OAuth2AuthenticationProcessingFilter等。
-
Spring Security OAuth2的异常处理机制非常完善,针对OAuth2规范中定义的各种错误情况都有相应的处理。
评论