# 概述
Oauth2是让第三方应用在用户授权的情况下,通过认证服务器的认证,从而安全的在资源服务器上获得对应的用户资源的流程指导。
# Oauth2的流程
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
- 第三方应用向资源持有者请求资源
- 资源持有者授权给予第三方应用一个许可
- 第三方应用将该许可给予认证服务器进行认证,认证成功则返回一个AccessToken
- 第三分应用根据AccessToken到资源服务器,获取对应的资源 在上面的流程中,第二步,资源持有者如何授权给予第三方应用许可,是最关键的地方
# 四种权限授予方式
参考文章Oauth2.0 ( RFC-6749 ) 中文译文 (opens new window)
授权码模式(Authorization Code) 授权码是通过授权服务器获取的,授权服务器是客户端和资源拥有者之间的媒介。与客户端直接向资源拥有者申请权限不同,客户端通过将资源拥有者引向授权服务器,然后授权服务器反过来将资源拥有者redirect到客户端,并带上授权码
授权服务器在将资源拥有者redirect到客户端(并带上授权码)之前,会验证资源拥有者,并获取资源拥有者的授权。因为资源拥有者仅与授权服务器进行身份认证,所以资源拥有者的凭证(用户名,密码等)永远不会泄露给第三方客户端。
授权码模式有一些重要的安全优势,比如验证客户端的能力比如直接将accessToken传送给客户端而不是通过资源拥有者传送给客户端。简化模式(implicit) 简化模式是为在浏览器中,使用诸如javaScript之类的脚本语言而优化的一种简化的授权码流程。简化模式中,直接将accessToken而不是authorizationCode颁发给客户端。 简化模式中颁发accessToken时,授权服务器没有对客户端进行验证。
密码模式(resource owner password credentials) 资源拥有者密码凭据,直接用来当作一种获取accessToken的权限授予方式。
客户端模式 客户端凭据用作权限授予。
# 源码
# oauth2集成逻辑设计
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}
@Configuration
@Order(0)
@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
/*xxx: 注入所有的 授权服务配置器*/
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
@Autowired
/*xxx: 客户端详情信息服务*/
private ClientDetailsService clientDetailsService;
@Autowired
/*xxx: 授权服务器服务端口配置*/
private AuthorizationServerEndpointsConfiguration endpoints;
@Autowired
/*xxx: 客户端服务配置器对授权服务配置器进行配置*/
public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
for (AuthorizationServerConfigurer configurer : configurers) {
configurer.configure(clientDetails);
}
}
@Override
/*xxx: 单条过滤链自定义配置*/
protected void configure(HttpSecurity http) throws Exception {
/*xxx: 授权服务器*/
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
/*xxx: 对授权服务器进行配置*/
configure(configurer);
/*xxx: 进行配置收集(此处是配置单条过滤链)*/
http.apply(configurer);
http
/*xxx: 进行授权配置*/
.authorizeRequests()
/*xxx: 获取授权码,需要进行完全授权*/
.antMatchers("/oauth/token").fullyAuthenticated()
/*xxx: 根据配置的表达式决定是否放行*/
.antMatchers("/oauth/token_key").access(configurer.getTokenKeyAccess())
/*xxx: 根据配置的表达式决定是否放行*/
.antMatchers("/oauth/check_token").access(configurer.getCheckTokenAccess())
.and()
/*xxx: 过滤链匹配逻辑配置,只有以上配置的三个端口匹配时,才进入本次过滤链*/
.requestMatchers()
.antMatchers("/oauth/token", "/oauth/token_key", "/oauth/check_token")
.and()
/*xxx: 配置不创建session*/
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
/*xxx: 共享客户端服务实例*/
http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}
/*xxx: 配置单条过滤链*/
protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
for (AuthorizationServerConfigurer configurer : configurers) {
configurer.configure(oauthServer);
}
}
}
# oauth2配置项设计
@Import(TokenKeyEndpointRegistrar.class)
/*xxx: 授权服务器端点配置*/
public class AuthorizationServerEndpointsConfiguration {
@Bean
/*xxx: 授权端口配置: /oauth/authorize
授权通过的地址:/oauth/confirm_access
授权失败的地址:/oauth/error
*/
public AuthorizationEndpoint authorizationEndpoint() throws Exception {
/*xxx: 该端口具有两个用于授权的控制器,一个用于返回用户确认授权的ui,一个用于授权后的重定向*/
AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
/*xxx: 授权通过地址*/
authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
/*xxx: 授权失败地址*/
authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
/*xxx: 令牌授权颁发器*/
authorizationEndpoint.setTokenGranter(tokenGranter());
/*xxx: 客户端服务*/
authorizationEndpoint.setClientDetailsService(clientDetailsService);
/*xxx: 授权码服务*/
authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
/*xxx: oauth2请求工程*/
authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
/*xxx: oauth2请求验证器*/
authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
return authorizationEndpoint;
}
@Bean
/*xxx: 获取授权码端口配置: /oauth/token */
public TokenEndpoint tokenEndpoint() throws Exception {
TokenEndpoint tokenEndpoint = new TokenEndpoint();
/*xxx: 省略其它抽象...*/
return tokenEndpoint;
}
@Bean
/*xxx: 查验令牌端口: /oauth/check_token*/
public CheckTokenEndpoint checkTokenEndpoint() {
CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
/*xxx: 设置accessToken转换器*/
endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
return endpoint;
}
}
@Component
/*xxx: 默认注册了jwt的令牌端口*/
protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
/*xxx: 如果上下文中,存在 jwtAccessToken转换器,就对jwt令牌端口进行注册 */
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory,
JwtAccessTokenConverter.class, false, false);
if (names.length > 0) {
/*xxx: 将 jwt的令牌端口,注册到 容器中 */
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TokenKeyEndpoint.class);
builder.addConstructorArgReference(names[0]);
registry.registerBeanDefinition(TokenKeyEndpoint.class.getName(), builder.getBeanDefinition());
}
}
}
/*xxx: jwt令牌端口*/
@FrameworkEndpoint
public class TokenKeyEndpoint {
/*xxx: jwt令牌转换器*/
private JwtAccessTokenConverter converter;
@RequestMapping(value = "/oauth/token_key", method = RequestMethod.GET)
@ResponseBody
/*xxx: 获取令牌信息*/
public Map<String, String> getKey(Principal principal) {
/*xxx: 从converter中获取相关信息*/
Map<String, String> result = converter.getKey();
return result;
}
}
@Configuration
public class ClientDetailsServiceConfiguration {
private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
@Bean
@Lazy
@Scope(proxyMode=ScopedProxyMode.INTERFACES)
public ClientDetailsService clientDetailsService() throws Exception {
return configurer.and().build();
}
}
public class ClientDetailsServiceConfigurer extends
SecurityConfigurerAdapter<ClientDetailsService, ClientDetailsServiceBuilder<?>> {
@Override
public void init(ClientDetailsServiceBuilder<?> builder) throws Exception {
}
@Override
public void configure(ClientDetailsServiceBuilder<?> builder) throws Exception {
}
/*xxx: 内存数据库*/
public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory();
setBuilder(next);
return next;
}
/*xxx: jdbc数据库*/
public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception {
JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource);
setBuilder(next);
return next;
}
}
# oauth2抽象层设计
# 抽象端口逻辑设计
/*xxx: 抽象端点*/
public class AbstractEndpoint implements InitializingBean {
/*xxx: 令牌颁发器*/
private TokenGranter tokenGranter;
/*xxx: 客户端服务*/
private ClientDetailsService clientDetailsService;
/*xxx: oauth2请求工厂*/
private OAuth2RequestFactory oAuth2RequestFactory;
/*xxx: oauth2默认工厂*/
private OAuth2RequestFactory defaultOAuth2RequestFactory;
public void afterPropertiesSet() throws Exception {
/*xxx: oauth2请求工厂 依赖客户端服务*/
defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(getClientDetailsService());
}
}
# 令牌颁发器--逻辑设计
/*xxx: 令牌颁发器*/
public interface TokenGranter {
/*xxx: 颁发访问令牌*/
/*xxx: 参数:授权类型,令牌请求实例*/
OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);
}
public abstract class AbstractTokenGranter implements TokenGranter {
/*xxx: 授权服务器令牌服务*/
private final AuthorizationServerTokenServices tokenServices;
/*xxx: 颁发令牌 */
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
String clientId = tokenRequest.getClientId();
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
/*xxx: 验证客户端,针对当前授权模式的合法性*/
validateGrantType(grantType, client);
return getAccessToken(client, tokenRequest);
}
/*xxx: 获取访问令牌 */
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
/*xxx: 通过 令牌服务创建令牌 */
/*xxx: 根据服务端令牌,创建客户端访问令牌*/
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
/*xxx: 除了 客户端授权模式,以及刷新令牌外,其它授权模式,均被重写*/
/*xxx: 获取认证信息,由子类实现*/
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, null);
}
}
# 授权码模式--令牌颁发器
/*xxx: 授权码模式,令牌颁发器*/
public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
/*xxx: 授权码服务*/
private final AuthorizationCodeServices authorizationCodeServices;
@Override
/*xxx: 获取oauth2认证信息*/
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = tokenRequest.getRequestParameters();
/*xxx: 从请求中,获取授权码*/
String authorizationCode = parameters.get("code");
/*xxx: 通过授权码服务,通过授权码 获取 认证信息*/
OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
/*xxx: 验证本次请求的重定向地址,与之前用于请求授权的重定向地址是否一致 */
if ((redirectUri != null || redirectUriApprovalParameter != null)
&& !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) {
/*xxx: 不一致,则需要抛错...*/
}
/*xxx: 验证本次请求的 clientId,与之前获取认证的 clientId 是否匹配 */
if (clientId != null && !clientId.equals(pendingClientId)) {
/*xxx: 不一致,则需要抛错...*/
}
/*xxx: 创建认证信息,将用户认证信息,与oauth2流程进行绑定*/
return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);
}
}
# 授权码模式--授权码服务
/*xxx: 授权码服务,默认具有两个实现: 内存,以及 jdbc */
public interface AuthorizationCodeServices {
/*xxx: 生成授权码*/
String createAuthorizationCode(OAuth2Authentication authentication);
/*xxx: 消费授权码,返回认证信息*/
OAuth2Authentication consumeAuthorizationCode(String code)
throws InvalidGrantException;
}
public abstract class RandomValueAuthorizationCodeServices implements AuthorizationCodeServices {
/*xxx: 随机数生成器*/
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
/*xxx: 存储授权码*/
protected abstract void store(String code, OAuth2Authentication authentication);
/*xxx: 移除授权码*/
protected abstract OAuth2Authentication remove(String code);
/*xxx: 创建授权码,默认情况下,是根据随机数生成的*/
public String createAuthorizationCode(OAuth2Authentication authentication) {
String code = generator.generate();
/*xxx: 对于生成的授权码,需要对其进行保存*/
/*xxx: 保存授权码,默认情况下,支持两种: inMemory,inJdbc*/
store(code, authentication);
return code;
}
public OAuth2Authentication consumeAuthorizationCode(String code)
throws InvalidGrantException {
/*xxx: 消费授权码时,需要对其移除*/
OAuth2Authentication auth = this.remove(code);
return auth;
}
}
/*xxx: 内存授权码服务*/
public class InMemoryAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {
protected final ConcurrentHashMap<String, OAuth2Authentication> authorizationCodeStore = new ConcurrentHashMap<String, OAuth2Authentication>();
}
/*xxx: jdbc授权码服务*/
/*xxx: 保存授权码的表为: oauth_code(code,authentication)*/
public class JdbcAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {
private final JdbcTemplate jdbcTemplate;
}
# 隐式授权模式--令牌颁发器
/*xxx: 隐式授权码令牌 颁发器*/
public class ImplicitTokenGranter extends AbstractTokenGranter {
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest clientToken) {
/*xxx: 直接从上下文中获取认证信息*/
Authentication userAuth = SecurityContextHolder.getContext().getAuthentication();
OAuth2Request requestForStorage = ((ImplicitTokenRequest)clientToken).getOAuth2Request();
return new OAuth2Authentication(requestForStorage, userAuth);
}
}
# 密码授权模式--令牌颁发器
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
@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");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
/*xxx: 直接采用原生 springSecurity进行认证*/
userAuth = authenticationManager.authenticate(userAuth);
/*xxx: 返回认证信息*/
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
# 授权服务器令牌服务--逻辑设计
/*xxx: 授权服务器令牌服务*/
public interface AuthorizationServerTokenServices {
/*xxx: 创建访问令牌*/
OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
/*xxx: 刷新访问令牌*/
OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
throws AuthenticationException;
/*xxx: 获取访问令牌*/
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
}
/*xxx: 默认的令牌服务*/
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
/*xxx: 令牌存储器*/
private TokenStore tokenStore;
/*xxx: 令牌增强器*/
private TokenEnhancer accessTokenEnhancer;
@Transactional
/*xxx: 创建访问令牌*/
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
/*xxx: 从令牌存储器中,获取之前存在的 访问令牌*/
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
/*xxx: 如果之前的令牌存在,但是过期了,则进行清理*/
/*xxx: 同时要把刷新令牌也清理掉*/
tokenStore.removeRefreshToken(refreshToken);
tokenStore.removeAccessToken(existingAccessToken);
}else{
/*xxx: 没过期,则重新存储令牌,刷新访问时间*/
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
/*xxx: 否则*/
/*xxx: 没有刷新令牌,则创建刷新令牌*/
/*xxx: 根据刷新令牌,创建 访问令牌*/
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
/*xxx: 最后,将访问令牌以及刷新令牌进行存储*/
tokenStore.storeAccessToken(accessToken, authentication);
tokenStore.storeRefreshToken(refreshToken, authentication);
}
@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
throws AuthenticationException {
/*xxx: 根据刷新令牌获取访问令牌,略*/
}
/*xxx: 获取访问令牌*/
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
return tokenStore.getAccessToken(authentication);
}
/*xxx: 创建访问令牌*/
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
/*xxx: 通过uuid,创建一个 默认的访问令牌*/
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
/*xxx: 如果由令牌增强器,则对令牌进行增强*/
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
}
# 令牌存储器--逻辑设计
/*xxx: 令牌存储 */
public interface TokenStore {
/*xxx: 存储访问令牌*/
void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
/*xxx: 移除访问令牌*/
void removeAccessToken(OAuth2AccessToken token);
/*xxx: 省略其它抽象...*/
}
# 内存令牌存储器
/*xxx: 将token访问令牌及其对应的认证信息,存储在内存中, 也就是 concurrentHashMap中*/
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, OAuth2AccessToken> authenticationToAccessTokenStore = new ConcurrentHashMap<String, OAuth2AccessToken>();
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
this.accessTokenStore.put(token.getValue(), token);
this.authenticationStore.put(token.getValue(), authentication);
this.authenticationToAccessTokenStore.put(authenticationKeyGenerator.extractKey(authentication), token);
}
}
# 数据库令牌存储器
/*xxx: jdbc的令牌存储实现,涉及到的表为:oauth_access_token,oauth_refresh_token */
public class JdbcTokenStore implements TokenStore {
private final JdbcTemplate jdbcTemplate;
}
# redis令牌存储器
/*xxx: 采用redis存储访问令牌*/
public class RedisTokenStore implements TokenStore {
private final RedisConnectionFactory connectionFactory;
}
# Java Web Token令牌存储器
/*xxx: 采用jwt存储的 令牌存储器 */
public class JwtTokenStore implements TokenStore {
/*xxx: jwt访问令牌转换器*/
private JwtAccessTokenConverter jwtTokenEnhancer;
@Override
/*xxx: jwtTokenStore 不需要存储 令牌与认证信息*/
/*xxx: 因为它是客户端存储方案*/
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
}
@Override
public void removeAccessToken(OAuth2AccessToken token) {
/*xxx: do nothing*/
}
@Override
public OAuth2Authentication readAuthentication(String token) {
return jwtTokenEnhancer.extractAuthentication(jwtTokenEnhancer.decode(token));
}
}
# 令牌增强器--逻辑设计
public interface TokenEnhancer {
/*xxx: 增强令牌*/
OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication);
}
/*xxx: jwt令牌增强器*/
public class JwtAccessTokenConverter implements TokenEnhancer, AccessTokenConverter, InitializingBean {
private String verifierKey = new RandomValueStringGenerator().generate();
private Signer signer = new MacSigner(verifierKey);
private SignatureVerifier verifier;
public void setKeyPair(KeyPair keyPair) {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
verifier = new RsaVerifier(publicKey);
}
/*xxx: 对生成的访问令牌进行增强,使其变为 jwt,并 携带认证信息*/
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
/*xxx: 将 认证信息进行编码,编码成 jwt */
result.setValue(encode(result, authentication));
/*xxx: 对刷新令牌也进行jwt编码*/
DefaultOAuth2RefreshToken token = new DefaultOAuth2RefreshToken(
encode(encodedRefreshToken, authentication));
result.setRefreshToken(token);
return result;
}
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content = objectMapper.formatMap(tokenConverter.convertAccessToken(accessToken, authentication));
String token = JwtHelper.encode(content, signer).getEncoded();
return token;
}
protected Map<String, Object> decode(String token) {
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
String content = jwt.getClaims();
Map<String, Object> map = objectMapper.parseMap(content);
return content;
}
}
/*xxx: 令牌增强链*/
public class TokenEnhancerChain implements TokenEnhancer {
private List<TokenEnhancer> delegates = Collections.emptyList();
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
OAuth2AccessToken result = accessToken;
for (TokenEnhancer enhancer : delegates) {
result = enhancer.enhance(result, authentication);
}
return result;
}
}
# 客户端服务
/*xxx: 客户端服务*/
public interface ClientDetailsService {
/*xxx: 通过id查询客户端*/
ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;
}
/*xxx: 内存客户端服务*/
public class InMemoryClientDetailsService implements ClientDetailsService {
private Map<String, ClientDetails> clientDetailsStore = new HashMap<String, ClientDetails>();
}
/*xxx: 数据库客户端服务,涉及到表: oauth_client_details*/
public class JdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService {
private final JdbcTemplate jdbcTemplate;
}
# oauth请求工厂
public interface OAuth2RequestFactory {
OAuth2Request createOAuth2Request(AuthorizationRequest request);
/*xxx: 授权请求*/
AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters);
/*xxx: 令牌请求*/
TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient);
}
OAuth2Request与传统的request不同,它更像是一个本地实体,仅能存储信息
# 用户授权处理器
/*xxx: 用户授权处理器*/
public interface UserApprovalHandler {
/*xxx: 当前oauth是否已授权 */
boolean isApproved(AuthorizationRequest authorizationRequest,
Authentication userAuthentication);
/*xxx: 查询之前的授权状态 */
AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
Authentication userAuthentication);
/*xxx: 更新之后的授权状态*/
AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest,
Authentication userAuthentication);
/*xxx: 获取用户授权状态 */
Map<String, Object> getUserApprovalRequest(AuthorizationRequest authorizationRequest,
Authentication userAuthentication);
}
/*xxx: 默认的授权状态,存储在 http请求过程中,user_oauth_approval作为参数进行传递*/
public class DefaultUserApprovalHandler implements UserApprovalHandler {
}
/*xxx: 通过tokenStore存储的用户授权管理处理*/
public class TokenStoreUserApprovalHandler implements UserApprovalHandler, InitializingBean {
private TokenStore tokenStore;
@Override
public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
String clientId = authorizationRequest.getClientId();
Set<String> scopes = authorizationRequest.getScope();
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
approved = true;
for (String scope : scopes) {
if (!client.isAutoApprove(scope)) {
approved = false;
}
}
if (approved) {
authorizationRequest.setApproved(true);
return authorizationRequest;
}
}
}
/*xxx: 用户授权存储器的方案实现*/
public class ApprovalStoreUserApprovalHandler implements UserApprovalHandler, InitializingBean {
private ApprovalStore approvalStore;
/*xxx: 略*/
}
# 访问令牌转换器
/*xxx: 访问令牌转换器 */
public interface AccessTokenConverter {
/*xxx: 根据 访问令牌,以及 认证信息,转换为 map对象*/
Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
/*xxx: 从map中 提取访问令牌*/
OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);
/*xxx: 从解析的令牌值中,提取认证信息*/
OAuth2Authentication extractAuthentication(Map<String, ?> map);
}
public class DefaultAccessTokenConverter implements AccessTokenConverter {
private UserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
}
/*xxx: 用户认证转换器 */
public interface UserAuthenticationConverter {
Map<String, ?> convertUserAuthentication(Authentication userAuthentication);
}
public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
}
# oauth2授权端口设计
# 授权流程
@FrameworkEndpoint
@SessionAttributes("authorizationRequest")
public class AuthorizationEndpoint extends AbstractEndpoint {
@RequestMapping(value = "/oauth/authorize")
/*xxx: 进行授权,获取授权页面*/
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
SessionStatus sessionStatus, Principal principal) {
/*xxx: 根据请求参数,创建 oauthRequest实例*/
AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
/*xxx: 对授权模式进行验证,如果是授权码模式,则会返回 code*/
/*xxx: 获取令牌或者获取授权码,不能同时为空,即springSecurityOauth2需要进行授权的 只有 授权码,以及隐式授权码模式*/
/*xxx: 密码模式,以及客户端模式,不需要走授权服务器*/
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
}
/*xxx: 如果当前用户未认证, 则抛错*/
if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
throw new InsufficientAuthenticationException(
"User must be authenticated with Spring Security before authorization can be completed.");
}
/*xxx: 通过客户端服务,查询到客户端信息*/
ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
/*xxx: 获取客户端重定向地址*/
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
authorizationRequest.setRedirectUri(resolvedRedirect);
/*xxx: 检查之前的授权结果*/
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
(Authentication) principal);
/*xxx: 查看当前请求是否已经被某个用户授权过*/
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
/*xxx: 已经授权过,则返回授权结果*/
if (authorizationRequest.isApproved()) {
/*xxx: 如果包含 token ,则采用隐式授权码模式*/
if (responseTypes.contains("token")) {
/*xxx: 重定向到客户端地址,带有accessToken,token_type,state,expires_in,scope,extra等参数*/
return getImplicitGrantResponse(authorizationRequest);
}
/*xxx: 如果含有code,则采用 授权码模式*/
if (responseTypes.contains("code")) {
/*xxx: 重定向到客户端地址,带有 code,state, 同时生成授权码 */
return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
(Authentication) principal));
}
}
/*xxx: 首次进行授权*/
model.put("authorizationRequest", authorizationRequest);
return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
}
private ModelAndView getUserApprovalPageResponse(Map<String, Object> model,
AuthorizationRequest authorizationRequest, Authentication principal) {
/*xxx: 用户信息,授权信息,客户端信息等*/
model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal));
/*xxx: 返回用户授权页面*/
return new ModelAndView("forward:/oauth/confirm_access", model);
}
}
# 授权结果处理
@FrameworkEndpoint
@SessionAttributes("authorizationRequest")
public class AuthorizationEndpoint extends AbstractEndpoint {
/*xxx: 处理授权结果*/
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
SessionStatus sessionStatus, Principal principal) {
/*xxx: 如果当前用户未认证,则进行抛错*/
if (!(principal instanceof Authentication)) {
}
/*xxx: 获取授权请求实例对象*/
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
/*xxx: 获取授权类型*/
Set<String> responseTypes = authorizationRequest.getResponseTypes();
/*xxx: 更新授权请求的 用户授权状态*/
authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,
(Authentication) principal);
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
if (!authorizationRequest.isApproved()) {
/*xxx: 如果用户授权未通过,则定向到授权失败的ui*/
}
if (responseTypes.contains("token")) {
/*xxx: 隐式授权码模式,直接返回accessToken,完成授权流程*/
return getImplicitGrantResponse(authorizationRequest).getView();
}
/*xxx: 授权码模式,生成授权码,并将授权码+state,返回给客户端*/
return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
}
}
# oauth2获取令牌端口设计
# get方式验证授权码
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
@RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
/*xxx: get方式,返回客户端认证令牌*/
public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
/*xxx: 是否允许get请求,不允许则会直接抛错*/
return postAccessToken(principal, parameters);
}
}
# post方式验证授权码
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
/*xxx: post方式,返回客户端认证令牌*/
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
/*xxx: 获取到客户端信息*/
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
/*xxx: 创建token 请求*/
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
/*xxx: 验证 scope*/
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
/*xxx: 隐式授权码模式,在此处不支持*/
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
/*xxx: 如果是刷新令牌,需要进行相应的设置*/
if (isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
/*xxx: 通过 令牌颁发器,颁发令牌*/
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
/*xxx: 禁用缓存*/
return getResponse(token);
}
}
# oauth2验证令牌端口设计
# 验证令牌
/*xxx: 资源服务器,拿着令牌,通过这个接口,来进行验证*/
@FrameworkEndpoint
public class CheckTokenEndpoint {
@RequestMapping(value = "/oauth/check_token")
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
/*xxx: 通过资源服务器,获取到资源服务令牌*/
/*xxx: 资源服务器与 授权服务器的 唯一交叉点就在于此,默认情况下,资源服务器令牌服务,与授权服务器令牌服务用的是同一个*/
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
/*xxx: 通过令牌服务,获取认证信息*/
OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, ?> response = accessTokenConverter.convertAccessToken(token, authentication);
return response;
}
}
# oauth2授权服务器认证配置
# 默认认证配置
@Configuration
@ConditionalOnClass(EnableAuthorizationServer.class) /*xxx: 开启了授权服务器时,才生效*/
@ConditionalOnMissingBean(AuthorizationServerConfigurer.class) /*xxx: 没有自定义 授权服务器配置时,才生效*/
@ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class) /*xxx: 配置了授权服务过滤链时,才生效*/
@EnableConfigurationProperties(AuthorizationServerProperties.class)
@Import(AuthorizationServerTokenServicesConfiguration.class) /*xxx: 引入授权服务器令牌服务配置*/
public class OAuth2AuthorizationServerConfiguration
extends AuthorizationServerConfigurerAdapter {
/*xxx: 配置客户端服务*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/*xxx: 默认配置了一个在内存中的 client,并配置其 密钥,授权类型,权限,令牌有效时长,重定向地址等*/
builder.secret(this.details.getClientSecret())
.resourceIds(this.details.getResourceIds().toArray(new String[0]))
.authorizedGrantTypes(
this.details.getAuthorizedGrantTypes().toArray(new String[0]))
.authorities(
AuthorityUtils.authorityListToSet(this.details.getAuthorities())
.toArray(new String[0]))
.scopes(this.details.getScope().toArray(new String[0]));
}
@Override
/*xxx: 配置授权服务端口地址信息*/
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
if (this.tokenConverter != null) {
endpoints.accessTokenConverter(this.tokenConverter);
}
if (this.tokenStore != null) {
endpoints.tokenStore(this.tokenStore);
}
if (this.details.getAuthorizedGrantTypes().contains("password")) {
endpoints.authenticationManager(this.authenticationManager);
}
}
@Override
/*xxx: 配置授权服务器 认证配置*/
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.passwordEncoder(NoOpPasswordEncoder.getInstance());
if (this.properties.getCheckTokenAccess() != null) {
security.checkTokenAccess(this.properties.getCheckTokenAccess());
}
if (this.properties.getTokenKeyAccess() != null) {
security.tokenKeyAccess(this.properties.getTokenKeyAccess());
}
if (this.properties.getRealm() != null) {
security.realm(this.properties.getRealm());
}
}
@Configuration
@ConditionalOnMissingBean(BaseClientDetails.class)
protected static class BaseClientDetailsConfiguration {
private final OAuth2ClientProperties client;
protected BaseClientDetailsConfiguration(OAuth2ClientProperties client) {
this.client = client;
}
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public BaseClientDetails oauth2ClientDetails() {
BaseClientDetails details = new BaseClientDetails();
if (this.client.getClientId() == null) {
this.client.setClientId(UUID.randomUUID().toString());
}
details.setClientId(this.client.getClientId());
details.setClientSecret(this.client.getClientSecret());
details.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
"password", "client_credentials", "implicit", "refresh_token"));
details.setAuthorities(
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
details.setRegisteredRedirectUri(Collections.<String>emptySet());
return details;
}
}
}
# 资源服务器抽象设计
# 资源服务器令牌服务
/*xxx: 资源服务器 令牌服务*/
public interface ResourceServerTokenServices {
/*xxx: 通过指定的 令牌值,获取认证信息*/
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;
/*xxx: 通过访问令牌的值,获取访问令牌实例*/
OAuth2AccessToken readAccessToken(String accessToken);
}
/*xxx: 默认的资源服务器令牌服务*/
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
/*xxx: 令牌存储器*/
private TokenStore tokenStore;
/*xxx: 根据令牌,获取认证信息*/
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
/*xxx: 从令牌存储器中,获取 访问令牌实例 */
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
/*xxx: 从令牌存储器中,根据访问令牌实例,获取认证信息*/
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
/*xxx: 验证客户端是否合法,如三方取消绑定等*/
if (clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
}
catch (ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
}
}
return result;
}
public OAuth2AccessToken readAccessToken(String accessToken) {
/*xxx: 通过令牌存储器,获取令牌实例*/
return tokenStore.readAccessToken(accessToken);
}
}
/*xxx: 远程令牌服务 */
public class RemoteTokenServices implements ResourceServerTokenServices {
private RestOperations restTemplate;
/*xxx: 授权服务器-验证令牌的地址*/
private String checkTokenEndpointUrl;
@Override
/*xxx: 通过 /oauth/token-check接口,获取认证信息*/
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add(tokenName, accessToken);
HttpHeaders headers = new HttpHeaders();
/*xxx: 通过basic头部认证,去授权服务器验证token*/
/*xxx: 这个具体是否需要授权,以及何种授权,均由授权服务器进行配置*/
headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);
return tokenConverter.extractAuthentication(map);
}
@Override
/*xxx: 根据token值,获取token实例,不提供支持*/
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}
/*xxx: 通过用户信息验证token */
public class UserInfoTokenServices implements ResourceServerTokenServices {
/*xxx: 用户信息端口*/
private final String userInfoEndpointUrl;
private OAuth2RestOperations restTemplate;
@Override
/*xxx: 通过 user-info 接口,获取认证信息*/
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
return extractAuthentication(map);
}
/*xxx: 实际上是获取远程用户信息,然后在本地组装令牌*/
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
List<GrantedAuthority> authorities = this.authoritiesExtractor
.extractAuthorities(map);
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
principal, "N/A", authorities);
token.setDetails(map);
/*xxx: 根据oauth2和authorities组装oauth2令牌*/
return new OAuth2Authentication(request, token);
}
@Override
/*xxx: 不支持该操作*/
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}
/*xxx: springSocial提供的资源服务器令牌服务*/
public class SpringSocialTokenServices implements ResourceServerTokenServices {
@Override
/*xxx: 通过social请求,获取认证信息*/
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
AccessGrant accessGrant = new AccessGrant(accessToken);
Connection<?> connection = this.connectionFactory.createConnection(accessGrant);
/*xxx: 可用于获取远程用户信息*/
/*xxx: 最终通过一个叫 ApiAdapter的接口,完成请求,并将用户信息封装起来*/
UserProfile user = connection.fetchUserProfile();
return extractAuthentication(user);
}
/*xxx: 本地组装 oauth2认证*/
private OAuth2Authentication extractAuthentication(UserProfile user) {
String principal = user.getUsername();
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
OAuth2Request request = new OAuth2Request(null, this.clientId, null, true, null,
null, null, null, null);
return new OAuth2Authentication(request,
new UsernamePasswordAuthenticationToken(principal, "N/A", authorities));
}
/*xxx: 不支持*/
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
throw new UnsupportedOperationException("Not supported: read access token");
}
}
# 令牌存储器
同授权服务器-令牌存储器,略
注意jwt令牌存储器在此处的作用
# 授权服务器端点配置服务
略,见授权服务器
# 资源服务器资源拒绝策略
/*xxx: 内部处理 accessDenied的策略*/
public interface AccessDeniedHandler {
void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException;
}
public class OAuth2AccessDeniedHandler extends AbstractOAuth2SecurityExceptionHandler implements AccessDeniedHandler {
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException)
throws IOException, ServletException {
doHandle(request, response, authException);
}
}
public abstract class AbstractOAuth2SecurityExceptionHandler {
protected final void doHandle(HttpServletRequest request, HttpServletResponse response, Exception authException)
throws IOException, ServletException {
result = enhanceResponse(result, authException);
exceptionRenderer.handleHttpEntityResponse(result, new ServletWebRequest(request, response));
}
/*xxx: 子类有用于维持basic登录信息*/
protected ResponseEntity<OAuth2Exception> enhanceResponse(ResponseEntity<OAuth2Exception> result,
Exception authException) {
return result;
}
}
public class OAuth2AuthenticationEntryPoint extends AbstractOAuth2SecurityExceptionHandler implements
AuthenticationEntryPoint {
@Override
protected ResponseEntity<OAuth2Exception> enhanceResponse(ResponseEntity<OAuth2Exception> response, Exception exception) {
if (headers.containsKey("WWW-Authenticate")) {
existing = extractTypePrefix(headers.getFirst("WWW-Authenticate"));
}
HttpHeaders update = new HttpHeaders();
update.putAll(response.getHeaders());
update.set("WWW-Authenticate", builder.toString());
return new ResponseEntity<OAuth2Exception>(response.getBody(), update, response.getStatusCode());
}
}
# oauth2资源服务器流程设计
# 资源服务器集成流程设计
@Import(ResourceServerConfiguration.class)
public @interface EnableResourceServer {
}
@Configuration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {
/*xxx: 默认的过滤链优先级*/
private int order = 3;
@Autowired(required = false)
/*xxx: 令牌存储器*/
private TokenStore tokenStore;
@Autowired(required = false)
/*xxx: 资源服务器令牌服务*/
private Map<String, ResourceServerTokenServices> tokenServices;
/*xxx: 资源服务器配置器*/
private List<ResourceServerConfigurer> configurers = Collections.emptyList();
@Autowired(required = false)
/*xxx: 授权服务器端点配置服务*/
private AuthorizationServerEndpointsConfiguration endpoints;
@Override
protected void configure(HttpSecurity http) throws Exception {
ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
/*xxx: 从多个资源服务器令牌服务中选择一个合适的*/
ResourceServerTokenServices services = resolveTokenServices();
/*xxx: 资源服务器令牌服务,与 令牌存储器 二者选其一*/
/*xxx: 资源服务器令牌服务 优于 令牌存储器*/
if (services != null) {
resources.tokenServices(services);
}
else {
if (tokenStore != null) {
resources.tokenStore(tokenStore);
}
else if (endpoints != null) {
resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
}
}
/*xxx: 资源服务器配置器进行配置*/
for (ResourceServerConfigurer configurer : configurers) {
configurer.configure(resources);
}
/*xxx: 匿名令牌认证器,匿名令牌,也算认证通过*/
http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
.exceptionHandling()
/*xxx: 配置资源拒绝策略*/
.accessDeniedHandler(resources.getAccessDeniedHandler()).and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();
/*xxx: 配置收集*/
http.apply(resources);
/*xxx: 配置了资源服务器端口*/
/*xxx: 则当前的过滤器链,仅对oauth2请求有效*/
if (endpoints != null) {
/*xxx: 资源服务器过滤链生效规则*/
http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
}
/*xxx: 资源服务器配置器对过滤链进行配置*/
for (ResourceServerConfigurer configurer : configurers) {
/*xxx: 实际上是配置了受保护的资源*/
configurer.configure(http);
}
/*xxx: 如果没有资源服务器配置器,则所有的资源都需要进行保护*/
if (configurers.isEmpty()) {
http.authorizeRequests().anyRequest().authenticated();
}
}
}
# 资源服务器认证配置器
/*xxx: 资源服务器配置*/
public final class ResourceServerSecurityConfigurer extends
SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
/*xxx: oauth2认证端口*/
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
/*xxx: oauth2认证处理过滤器*/
private OAuth2AuthenticationProcessingFilter resourcesServerFilter;
@Override
public void init(HttpSecurity http) throws Exception {
registerDefaultAuthenticationEntryPoint(http);
}
private void registerDefaultAuthenticationEntryPoint(HttpSecurity http) {
ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling = http
.getConfigurer(ExceptionHandlingConfigurer.class);
/*xxx: 省略其它抽象...*/
exceptionHandling.defaultAuthenticationEntryPointFor(postProcess(authenticationEntryPoint), preferredMatcher);
}
@Override
public void configure(HttpSecurity http) throws Exception {
/*xxx: 新增oauth认证管理器*/
AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
/*xxx: 设置oauth2认证过滤器*/
resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();
/*xxx: 设置认证端口*/
resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
/*xxx: 设置认证管理器*/
resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);
/*xxx: 省略其它抽象...*/
http
.authorizeRequests().expressionHandler(expressionHandler)
.and()
/*xxx: 资源服务器过滤器,就是 oauth2认证过滤器*/
.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
}
# oauth2单点登录
# 单点登录集成设计
@EnableOAuth2Client
@EnableConfigurationProperties(OAuth2SsoProperties.class)
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,
ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {
}
# oauth2单点登录--默认配置器
# 默认配置器设计
@Configuration
@Conditional(NeedsWebSecurityCondition.class)/*xxx: 存在@EnableOauth2Sso注解*/
/*xxx: oauth2单点登录的默认配置,生成一条过滤链*/
public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter {
@Override
/*xxx: 所有的请求,都需要进行认证*/
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
new SsoSecurityConfigurer(this.applicationContext).configure(http);
}
}
/*xxx: 单点登录配置器 */
class SsoSecurityConfigurer {
public void configure(HttpSecurity http) throws Exception {
/*xxx: 获取sso配置属性 */
OAuth2SsoProperties sso = this.applicationContext
.getBean(OAuth2SsoProperties.class);
/*xxx: 为过滤链添加 认证过滤器*/
http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
addAuthenticationEntryPoint(http, sso);
}
/*xxx: 添加 OAuth2ClientAuthenticationProcessingFilter,oauth客户端认证过滤器*/
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
OAuth2SsoProperties sso) {
/*xxx: 从容器中,获取 userInfoRestTemplateFacotry*/
OAuth2RestOperations restTemplate = this.applicationContext
.getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
/*xxx: 从容器中,获取 userInfoRestTemplateFacotry*/
OAuth2RestOperations restTemplate = this.applicationContext
.getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
/*xxx: 从容器中获取 资源服务器令牌服务*/
ResourceServerTokenServices tokenServices = this.applicationContext
.getBean(ResourceServerTokenServices.class);
/*xxx: 实例化 oauth2客户端认证过滤器*/
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
sso.getLoginPath());
filter.setRestTemplate(restTemplate);
/*xxx: 设置资源服务器令牌服务*/
filter.setTokenServices(tokenServices);
return filter;
}
/*xxx: 配置认证端口*/
private void addAuthenticationEntryPoint(HttpSecurity http, OAuth2SsoProperties sso)
throws Exception {
ExceptionHandlingConfigurer<HttpSecurity> exceptions = http.exceptionHandling();
/*xxx: 重定向到登录地址 */
exceptions.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint(sso.getLoginPath()),
preferredMatcher);
/*xxx: 返回401状态码*/
exceptions.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
}
}
private static class OAuth2ClientAuthenticationConfigurer
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private OAuth2ClientAuthenticationProcessingFilter filter;
@Override
public void configure(HttpSecurity builder) throws Exception {
OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
/*xxx: 配置过滤器的 session认证策略*/
ssoFilter.setSessionAuthenticationStrategy(
builder.getSharedObject(SessionAuthenticationStrategy.class));
/*xxx: 将过滤器添加进入过滤链中*/
builder.addFilterAfter(ssoFilter,
AbstractPreAuthenticatedProcessingFilter.class);
}
}
# oauth2客户端认证过滤器设计
/*xxx: oauth2客户端认证处理器*/
public class OAuth2ClientAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
/*xxx: oauth2认证数据源*/
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
public OAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
/*xxx: 单点登录的地址 由 sso进行配置,可以与 登录地址不一致 */
super(defaultFilterProcessesUrl);
/*xxx: 空认证处理器 */
setAuthenticationManager(new NoopAuthenticationManager());
/*xxx: oauth2认证数据源*/
setAuthenticationDetailsSource(authenticationDetailsSource);
}
@Override
/*xxx: 尝试进行认证*/
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
OAuth2AccessToken accessToken;
/*xxx: 从 restTemplate中,获取访问令牌 */
/*xxx: 自环: 第一环:获取授权码; 第二环: 获取 访问码*/
accessToken = restTemplate.getAccessToken();
try {
/*xxx: 从 restTemplate中,获取访问令牌 */
/*xxx: 自环: 第一环:获取授权码; 第二环: 获取 访问码*/
accessToken = restTemplate.getAccessToken();
} catch (OAuth2Exception e) {
/*xxx: 获取不到则发布 oauth2认证失败事件,并抛出异常,终止后续流程*/
BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
try {
/*xxx: 通过 资源服务器令牌服务,获取 服务端认证信息*/
OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());
/*xxx: 将认证信息设置到 request中 略*/
/*xxx: 发布 oauth2认证成功事件*/
publish(new AuthenticationSuccessEvent(result));
return result;
}catch (InvalidTokenException e) {
/*xxx: 发布oauth2认证失败事件,并抛出异常*/
BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
publish(new OAuth2AuthenticationFailureEvent(bad));
throw bad;
}
}
}
public interface OAuth2RestOperations extends RestOperations {
/*xxx: 获取令牌*/
OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException;
}
public class OAuth2RestTemplate extends RestTemplate implements OAuth2RestOperations {
/*xxx: oauth2客户端上下文*/
private OAuth2ClientContext context;
public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
/*xxx: 从上下文中,获取客户端令牌*/
OAuth2AccessToken accessToken = context.getAccessToken();
/*xxx: 如果上下文中,accessToken非法,或不存在*/
if (accessToken == null || accessToken.isExpired()) {
try {
/*xxx: 尝试获取accessToken*/
accessToken = acquireAccessToken(context);
}catch (UserRedirectRequiredException e) {
/*xxx: 获取不到accessToken,则抛出异常,并存储 state值*/
context.setPreservedState(stateKey, stateToPreserve);
throw e;
}
}
}
/*xxx: 获取 accessToken */
protected OAuth2AccessToken acquireAccessToken(OAuth2ClientContext oauth2Context)
throws UserRedirectRequiredException {
/*xxx: 从上下文中,获取 accessTokenRequest实例 */
AccessTokenRequest accessTokenRequest = oauth2Context.getAccessTokenRequest();
/*xxx: 从请求中,获取state值*/
String stateKey = accessTokenRequest.getStateKey();
/*xxx: 如果存在 stateKey,则将其移除掉,相当于消费掉*/
if (stateKey != null) {
accessTokenRequest.setPreservedState(oauth2Context.removePreservedState(stateKey));
}
/*xxx: 省略其它抽象...*/
/*xxx: 创建客户端认证令牌,此处会抛出异常,交由 Oauth2ClientContextFilter进行处理*/
OAuth2AccessToken accessToken = null;
/*xxx: 通过accessToken提供器,获取accessToken*/
accessToken = accessTokenProvider.obtainAccessToken(resource, accessTokenRequest);
/*xxx: 将获取到的 accessToken,保存至上下文*/
oauth2Context.setAccessToken(accessToken);
return accessToken;
}
}
# oauth2客户端上下文
/*xxx: oauth2客户端上下文*/
public interface OAuth2ClientContext {
/*xxx: 获取accessToken*/
OAuth2AccessToken getAccessToken();
/*xxx: 设置accessToken*/
void setAccessToken(OAuth2AccessToken accessToken);
/*xxx: 保存 state 值*/
void setPreservedState(String stateKey, Object preservedState);
/*xxx 移除 state 值*/
Object removePreservedState(String stateKey);
}
public class DefaultOAuth2ClientContext implements OAuth2ClientContext, Serializable {
/*xxx: 该参数存储了 state 与 客户端请求的 uri 映射信息*/
private Map<String, Object> state = new HashMap<String, Object>();
/*xxx: accessToken实体*/
private OAuth2AccessToken accessToken;
/*xxx: accessToken请求实体*/
private AccessTokenRequest accessTokenRequest;
public void setPreservedState(String stateKey, Object preservedState) {
state.put(stateKey, preservedState);
}
}
# 获取令牌失败的处理
- 抛出
UserRedirectRequiredException
,该异常内部,存储了授权服务器的地址
/*xxx: oauth2用户重定向异常*/
public class UserRedirectRequiredException extends RuntimeException {
/*xxx: 重定向地址*/
private final String redirectUri;
/*xxx: 请求参数*/
private final Map<String, String> requestParams;
/*xxx: state值*/
private String stateKey;
}
- oauth2客户端上下文过滤链
OAuth2ClientContextFilter
,对该异常进行处理
public class OAuth2ClientContextFilter implements Filter, InitializingBean {
/*xxx: 当前的的地址*/
public static final String CURRENT_URI = "currentUri";
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
.getFirstThrowableOfType(
UserRedirectRequiredException.class, causeChain);
if (redirect != null) {
redirectUser(redirect, request, response);
}
}
}
protected void redirectUser(UserRedirectRequiredException e,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
/*xxx: 获取重定向地址*/
String redirectUri = e.getRedirectUri();
UriComponentsBuilder builder = UriComponentsBuilder
.fromHttpUrl(redirectUri);
/*xxx: 省略其它抽象...*/
/*xxx: 构建参数,设置 state*/
if (e.getStateKey() != null) {
builder.queryParam("state", e.getStateKey());
}
this.redirectStrategy.sendRedirect(request, response, builder.build()
.encode().toUriString());
}
}
# 访问令牌提供器
/*xxx: accessToken提供器*/
public interface AccessTokenProvider {
/*xxx: 获取 accessToken,获取失败,抛出重定向错误*/
OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest parameters)
throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException;
}
public abstract class OAuth2AccessTokenSupport {
private RestOperations restTemplate;
/*xxx: 认证处理器*/
private ClientAuthenticationHandler authenticationHandler = new DefaultClientAuthenticationHandler();
/*xxx: 请求增强器*/
private RequestEnhancer tokenRequestEnhancer = new DefaultRequestEnhancer();
/*xxx: 获取accessToken*/
protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource,
MultiValueMap<String, String> form, HttpHeaders headers) throws OAuth2AccessDeniedException {
/*xxx: 认证前置处理,将参数设置好*/
/*xxx: 设置参数时,根据配置项进行设置,包括:(header) 以及 (form 跟 query) */
authenticationHandler.authenticateTokenRequest(resource, form, headers);
/*xxx: 根据接口,对 form 或者 header 自定义*/
tokenRequestEnhancer.enhance(request, resource, form, headers);
final AccessTokenRequest copy = request;
final ResponseExtractor<OAuth2AccessToken> delegate = getResponseExtractor();
/*xxx: 将响应结果中的cookie信息拷贝至 oauth2Request实体对象中*/
/*xxx: 从结果对象中提取 accessToken*/
ResponseExtractor<OAuth2AccessToken> extractor = new ResponseExtractor<OAuth2AccessToken>() {
@Override
public OAuth2AccessToken extractData(ClientHttpResponse response) throws IOException {
if (response.getHeaders().containsKey("Set-Cookie")) {
copy.setCookie(response.getHeaders().getFirst("Set-Cookie"));
}
return delegate.extractData(response);
}
};
/*xxx: 执行获取 accessToken令牌*/
return getRestTemplate().execute(getAccessTokenUri(resource, form), getHttpMethod(),
getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap());
}
/*xxx: 获取 accessToken的接口地址*/
protected String getAccessTokenUri(OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form) {
StringBuilder builder = new StringBuilder(accessTokenUri);
if (getHttpMethod() == HttpMethod.GET) {
String separator = "?";
if (accessTokenUri.contains("?")) {
separator = "&";
}
for (String key : form.keySet()) {
builder.append(separator);
builder.append(key + "={" + key + "}");
separator = "&";
}
}
return builder.toString();
}
}
# oauth2客户端认证处理器
/*xxx: 客户端认证处理器*/
public interface ClientAuthenticationHandler {
/*xxx: 配置认证参数*/
void authenticateTokenRequest(OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form,
HttpHeaders headers);
}
public class DefaultClientAuthenticationHandler implements ClientAuthenticationHandler {
public void authenticateTokenRequest(OAuth2ProtectedResourceDetails resource, MultiValueMap<String, String> form,
HttpHeaders headers) {
/*xxx: 默认采用头部认证*/
AuthenticationScheme scheme = AuthenticationScheme.header;
/*xxx: 配置客户端认证模式*/
if (resource.getClientAuthenticationScheme() != null) {
scheme = resource.getClientAuthenticationScheme();
}
switch (scheme) {
case header:
form.remove("client_secret");
headers.add(
"Authorization",
String.format(
"Basic %s",
new String(Base64.encode(String.format("%s:%s", resource.getClientId(),
clientSecret).getBytes("UTF-8")), "UTF-8")));
break;
case form:
case query:
form.set("client_id", resource.getClientId());
if (StringUtils.hasText(clientSecret)) {
form.set("client_secret", clientSecret);
}
break;
default:
throw new IllegalStateException(
"Default authentication handler doesn't know how to handle scheme: " + scheme);
}
}
}
# 授权码模式认证提供器
public class AuthorizationCodeAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException,
OAuth2AccessDeniedException {
/*xxx: 获取授权码资源信息*/
AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) details;
/*xxx: oauth2请求中不包含 授权码时*/
if (request.getAuthorizationCode() == null) {
/*xxx: 请求中,不包含 stateKey,则抛错*/
if (request.getStateKey() == null) {
throw getRedirectForAuthorization(resource, request);
}
/*xxx: 请求授权码,并将之保存在 oauth2请求中*/
/*xxx: 如果没有获取到授权码,则会抛出异常,阻断流程*/
obtainAuthorizationCode(resource, request);
}
/*xxx: 在已经获得授权码的情况下,提取客户端认证令牌*/
return retrieveToken(request, resource, getParametersForTokenRequest(resource, request),
getHeadersForTokenRequest(request));
}
private MultiValueMap<String, String> getParametersForTokenRequest(AuthorizationCodeResourceDetails resource,
AccessTokenRequest request) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.set("grant_type", "authorization_code");
form.set("code", request.getAuthorizationCode());
form.set("redirect_uri", redirectUri);
}
public String obtainAuthorizationCode(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException,
OAuth2AccessDeniedException {
/*xxx: 调用用户授权地址,该接口会报 304,401,403等才算正常*/
ResponseEntity<Void> response = getRestTemplate().execute(resource.getUserAuthorizationUri(), HttpMethod.POST,
getRequestCallback(resource, form, headers), extractor, form.toSingleValueMap());
/*xxx: 获取客户端重定向地址,从重定向地址中,取出 state,以及code值,并保存 */
URI location = response.getHeaders().getLocation();
String query = location.getQuery();
Map<String, String> map = OAuth2Utils.extractMap(query);
request.setStateKey(map.get("state"));
tring code = map.get("code");
/*xxx: 如果没获取到授权码code,则抛出 用户需要重定向异常,中断流程*/
if (code == null) {
throw new UserRedirectRequiredException(location.toString(), form.toSingleValueMap());
}
request.set("code", code);
return code;
}
}
# 授权之后,维持会话信息
todo:
# 隐式授权码模式认证提供器
public class ImplicitAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
ImplicitResourceDetails resource = (ImplicitResourceDetails) details;
/*xxx: 与授权码模式相比,省略了获取授权码的步骤*/
OAuth2AccessToken token = retrieveToken(request,
resource, getParametersForTokenRequest(resource, request), getHeadersForTokenRequest(request));
if (token==null) {
/*xxx: 抛出用户重定向异常*/
throw new UserRedirectRequiredException(resource.getUserAuthorizationUri(), request.toSingleValueMap());
}
return token;
}
private MultiValueMap<String, String> getParametersForTokenRequest(ImplicitResourceDetails resource,
AccessTokenRequest request) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.set("response_type", "token");
form.set("client_id", resource.getClientId());
form.set("scope", builder.toString());
form.set("redirect_uri", redirectUri);
return form;
}
}
# 客户端模式认证提供器
public class ClientCredentialsAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
/*xxx: 客户模式,啥都不需要干,直接去请求就行*/
ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
return retrieveToken(request, resource, getParametersForTokenRequest(resource), new HttpHeaders());
}
private MultiValueMap<String, String> getParametersForTokenRequest(ClientCredentialsResourceDetails resource) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.set("grant_type", "client_credentials");
form.set("scope", builder.toString());
return form;
}
}
# 密码模式认证提供器
public class ResourceOwnerPasswordAccessTokenProvider extends OAuth2AccessTokenSupport implements AccessTokenProvider {
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
ResourceOwnerPasswordResourceDetails resource = (ResourceOwnerPasswordResourceDetails) details;
return retrieveToken(request, resource, getParametersForTokenRequest(resource, request), new HttpHeaders());
}
private MultiValueMap<String, String> getParametersForTokenRequest(ResourceOwnerPasswordResourceDetails resource, AccessTokenRequest request) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.set("grant_type", "password");
/*xxx: 带上参数名,密码*/
form.set("username", resource.getUsername());
form.set("password", resource.getPassword());
form.putAll(request);
form.set("scope", builder.toString());
return form;
}
}
# oauth2单点登录--资源服务器令牌服务配置
# 用户信息请求工厂
- 初始化时,会将
oauth2客户端上下文
注入 - 初始化时,会将
被保护资源详细信息
进行注入
@Configuration
@ConditionalOnMissingBean(AuthorizationServerEndpointsConfiguration.class)/*xxx: 当前应用非授权服务器时,才生效*/
/*xxx: 资源服务器令牌服务配置*/
public class ResourceServerTokenServicesConfiguration {
@Bean
@ConditionalOnMissingBean
/*xxx: 用户信息请求工厂,负责获取 token,授权码等流程*/
/*xxx: 初始化时,会将oauth2客户端上下文注入*/
/*xxx: 同时注入被保护资源详细信息*/
public UserInfoRestTemplateFactory userInfoRestTemplateFactory(
ObjectProvider<List<UserInfoRestTemplateCustomizer>> customizers,
ObjectProvider<OAuth2ProtectedResourceDetails> details,
ObjectProvider<OAuth2ClientContext> oauth2ClientContext) {
return new DefaultUserInfoRestTemplateFactory(customizers, details,
oauth2ClientContext);
}
}
# 远程令牌服务配置
@Configuration
@Conditional(RemoteTokenCondition.class)
/*xxx: 远程令牌服务配置*/
/*xxx: 在没有 jwt,以及 jws环境时,才会生效配置*/
protected static class RemoteTokenServicesConfiguration {
}
# 通过授权服务器获取令牌认证信息
@Configuration
@Conditional(TokenInfoCondition.class)
/*xxx: 既没有 token-info-uri配置,也没有 user-info-uri配置时,进行激活,算是一种容错处理*/
/*xxx: 或者 配置了 token-info-uri,并且配置了 prefer-token-info 为true时,进行激活 */
protected static class TokenInfoServicesConfiguration {
/*xxx: 资源服务器配置*/
private final ResourceServerProperties resource;
@Bean
/*xxx: 通过 /oauth/token-check 接口实现的 令牌服务*/
public RemoteTokenServices remoteTokenServices() {
RemoteTokenServices services = new RemoteTokenServices();
services.setCheckTokenEndpointUrl(this.resource.getTokenInfoUri());
services.setClientId(this.resource.getClientId());
services.setClientSecret(this.resource.getClientSecret());
return services;
}
}
# 通过springSocial获取令牌认证信息
@Configuration
@ConditionalOnClass(OAuth2ConnectionFactory.class)
@Conditional(NotTokenInfoCondition.class)
/*xxx: 生效条件为,既不满足 token-info条件,又具有 social的关键类*/
/*xxx: 通过 social实现的令牌服务:根据具体的装配情况,里面兼容了对 user-info的实现*/
protected static class SocialTokenServicesConfiguration {
/*xxx: 略*/
}
# 通过userInfo组装本地访问令牌获取认证信息
@Configuration
@ConditionalOnMissingClass("org.springframework.social.connect.support.OAuth2ConnectionFactory")
@Conditional(NotTokenInfoCondition.class)
/*xxx: 当既不满足 从授权服务器获取令牌认证信息,又不满足social的条件时,生效*/
/*xxx: 通过 user-info 实现的 令牌服务 */
protected static class UserInfoTokenServicesConfiguration {
/*xxx: 资源服务器配置信息*/
private final ResourceServerProperties sso;
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public UserInfoTokenServices userInfoTokenServices() {
UserInfoTokenServices services = new UserInfoTokenServices(
this.sso.getUserInfoUri(), this.sso.getClientId());
services.setRestTemplate(this.restTemplate);
services.setTokenType(this.sso.getTokenType());
return services;
}
}
# 本地令牌服务配置(基于jwt的三种方案,先简单做了解,后文会对jwt方案进行详细说明)
# jws获取令牌认证信息(jwt的增强实现,对称密钥)
@Configuration
@Conditional(JwkCondition.class)
/*xxx: jwk令牌存储服务配置*/
protected static class JwkTokenStoreConfiguration {
/*xxx: 资源服务配置*/
private final ResourceServerProperties resource;
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public DefaultTokenServices jwkTokenServices(TokenStore jwkTokenStore) {
DefaultTokenServices services = new DefaultTokenServices();
services.setTokenStore(jwkTokenStore);
return services;
}
@Bean
@ConditionalOnMissingBean(TokenStore.class)
/*xxx: 本质上是 jwt令牌存储,经过了一次解密*/
public TokenStore jwkTokenStore() {
return new JwkTokenStore(this.resource.getJwk().getKeySetUri());
}
}
# jwt获取令牌认证信息(通过密钥值进行签名及验签)
@Configuration
@Conditional(JwtTokenCondition.class)
/*xxx: 配置了: jwt.key-value 或者 jwt.key-uri,则激活该配置 */
/*xxx: jwt令牌服务配置*/
protected static class JwtTokenServicesConfiguration {
/*xxx: 资源服务器配置*/
private final ResourceServerProperties resource;
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
/*xxx: 本地资源服务器令牌服务*/
public DefaultTokenServices jwtTokenServices(TokenStore jwtTokenStore) {
DefaultTokenServices services = new DefaultTokenServices();
services.setTokenStore(jwtTokenStore);
return services;
}
@Bean
@ConditionalOnMissingBean(TokenStore.class)
/*xxx: jwt的令牌存储,是默认的实现,可以通过复写 TokenStore 进行覆盖*/
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtTokenEnhancer());
}
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
/*xxx: 可以直接设置 keyValue*/
String keyValue = this.resource.getJwt().getKeyValue();
/*xxx: 如果没有设置 keyValue,则从服务端获取*/
if (!StringUtils.hasText(keyValue)) {
keyValue = getKeyFromServer();
}
converter.setSigningKey(keyValue);
converter.setVerifierKey(keyValue);
return converter;
}
/*xxx: 从服务端获取 jwt的 key*/
private String getKeyFromServer() {
/*xxx: 获取key时,通过客户端认证方式进行认证*/
HttpHeaders headers = new HttpHeaders();
String username = this.resource.getClientId();
String password = this.resource.getClientSecret();
if (username != null && password != null) {
byte[] token = Base64.getEncoder()
.encode((username + ":" + password).getBytes());
headers.add("Authorization", "Basic " + new String(token));
}
/*xxx: 省略其它抽象*/
String url = this.resource.getJwt().getKeyUri();
return (String) keyUriRestTemplate
.exchange(url, HttpMethod.GET, request, Map.class).getBody()
.get("value");
}
}
# jwt存储获取令牌认证信息(通过密码本进行签名及验签)
@Configuration
@Conditional(JwtKeyStoreCondition.class)
/*xxx: 生效条件为: 配置了: jwt.key-store*/
protected class JwtKeyStoreConfiguration implements ApplicationContextAware {
@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public DefaultTokenServices jwtTokenServices(TokenStore jwtTokenStore) {
DefaultTokenServices services = new DefaultTokenServices();
services.setTokenStore(jwtTokenStore);
return services;
}
@Bean
@ConditionalOnMissingBean(TokenStore.class)
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource keyStore = this.context.getResource(this.resource.getJwt().getKeyStore());
char[] keyStorePassword = this.resource.getJwt().getKeyStorePassword().toCharArray();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(keyStore, keyStorePassword);
String keyAlias = this.resource.getJwt().getKeyAlias();
char[] keyPassword = Optional.ofNullable(
this.resource.getJwt().getKeyPassword())
.map(String::toCharArray).orElse(keyStorePassword);
converter.setKeyPair(keyStoreKeyFactory.getKeyPair(keyAlias, keyPassword));
return converter;
}
}
# oauth2第三方客户端配置
# 逻辑意义
- 第三方配置客户端,是相对于oauth2流程中的授权服务器,资源服务器而言;
- 该配置的意义在于,将客户端导向授权服务器获取授权,通常情况下,用户需要在这个阶段进行认证后,方能进行授权;
# 集成配置
@Import(OAuth2ClientConfiguration.class)
public @interface EnableOAuth2Client {
}
# 客户端配置
@Configuration
/*xxx: oauth2客户端配置*/
public class OAuth2ClientConfiguration {
@Bean
/*xxx: oauth2客户端上下文过滤器*/
public OAuth2ClientContextFilter oauth2ClientContextFilter() {
OAuth2ClientContextFilter filter = new OAuth2ClientContextFilter();
return filter;
}
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
/*xxx: access令牌的请求实例,在请求时创建,请求完成后销毁,记录了当前请求的地址*/
protected AccessTokenRequest accessTokenRequest(@Value("#{request.parameterMap}")
Map<String, String[]> parameters, @Value("#{request.getAttribute('currentUri')}")
String currentUri) {
DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(parameters);
request.setCurrentUri(currentUri);
return request;
}
@Configuration
/*xxx: oauth2客户端上下文配置*/
protected static class OAuth2ClientContextConfiguration {
@Resource
@Qualifier("accessTokenRequest")
private AccessTokenRequest accessTokenRequest;
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
/*xxx: oauth2上下文,创建session域的 客户端上下文bean*/
public OAuth2ClientContext oauth2ClientContext() {
return new DefaultOAuth2ClientContext(accessTokenRequest);
}
}
}
# 客户端上下文过滤器
public class OAuth2ClientContextFilter implements Filter, InitializingBean {
/*xxx: 当前的的地址*/
public static final String CURRENT_URI = "currentUri";
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
/*xxx: 为当前的请求,记录当前请求路径信息*/
request.setAttribute(CURRENT_URI, calculateCurrentUri(request));
try {
chain.doFilter(servletRequest, servletResponse);
} catch (Exception ex) {
/*xxx: 在后续过滤器中,如果抛出了 UserRedirectRequiredException,则对其进行重定向*/
serRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
.getFirstThrowableOfType(
UserRedirectRequiredException.class, causeChain);
redirectUser(redirect, request, response);
}
}
protected void redirectUser(UserRedirectRequiredException e,
HttpServletRequest request, HttpServletResponse response)
throws IOException {
/*xxx: 获取重定向地址*/
String redirectUri = e.getRedirectUri();
/*xxx: 构建参数,设置 state*/
if (e.getStateKey() != null) {
builder.queryParam("state", e.getStateKey());
}
this.redirectStrategy.sendRedirect(request, response, builder.build()
.encode().toUriString());
}
}
# 如何确保OAuth2ClientContextFilter
位于springSecurity过滤链
之前
@Configuration
@Import(OAuth2RestOperationsConfiguration.class)
public class OAuth2AutoConfiguration {
/*xxx: 省略其它抽象...*/
}
@Configuration
@ConditionalOnClass(EnableOAuth2Client.class)
public class OAuth2RestOperationsConfiguration {
@Configuration
protected static class SessionScopedConfiguration {
@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter, SecurityProperties security) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(filter);
/*xxx: 它的位置位于springSecurity过滤链之前*/
registration.setOrder(security.getFilter().getOrder() - 10);
return registration;
}
}
/*xxx: 省略其它抽象...*/
}
# jwt令牌存储技术
# 什么是JWT
- Json Web Token,是为了在网络应用环境间传递声明,而执行的一种基于JSON的开放标准
- 该token被设计的紧凑且安全,特别适合分布式站点的单点登录场景。
# http协议维持会话的方式
- 基于Session维持会话(传统)
- 基于token维持会话
# JWT的组成
一个JWT字符串,由三部分组成,分别是头部header
,载荷playload
,签名signature
。 它们由.
连接在一起。
# JWT头部
{
"typ": "JWT",
"alg": "HS256"
}
头部声明了两部分内容:
- 类型: jwt,jws,jwk,jwe
- 加密算法
# JWT载荷
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
jwt用来存放有效信息的地方,内容可以分为三类:
- 标准中注册的声明:
- iss: jwt签发者
- sub: jwt面向的用户
- aud: 接受jwt的一方
- exp: jwt的过期时间
- iat: 签发时间
- jti: jwt的唯一身份标识
- 公共的声明: 可添加任何信息
- 私有的声明: 签发者和消费者共同定义的声明
# jwt签名
由三个签证信息组成:
- base64后的header的签证信息
- base64后的payload的签证信息
- secret盐值
通过header中声明的加密方式,加上secret盐,jwt进行签证;
secret盐值是存放在客户端的,jwt的签发也是在客户端。secret就是用来进行jwt的签发和验证。secret就是服务端的私钥,任何场景下都不能泄露出去。一旦被客户端得知secret,客户端就可以自己签发jwt了。
# 基于JWK的SSO实现说明
security.oauth2.resource.jwk.keySetUri
,该配置项用于获取RSA公钥
,对jwt进行验证;- 此种jwt头部采用的是非对称加密算法
# JWT基于secret盐的方式
- 标准的jwt模式,客户端持有了secret盐,也可以从授权服务器中获取;
- 配置项为
security.oauth2.resource.jwt.key-value
以及security.oauth2.resource.jwt.key-uri
# JWT基于本地证书的方式
- 通过RSA的方式对jwt进行签名及验签
- 配置项为
security.oauth2.resource.jwt.key-store