Oauth2的默认授权模式有四种:
- 授权码模式-authorization_code
- 密码模式-password
- 客户端模式-client_credentials
- 隐式授权模式-implicit
token的运行流程:
- 在发起 URL+/oauth/token 获取token的请求后,实际上是请求 TokenEndpoint 类的postAccessToken或者getAccessToken方法,就相当于一个普通的concoller请求方法,根据请求类型是get或者post,其实get请求内部也是调用post请求的方法)
- 在postAccessToken这个方法中调用TokenGranter类的grant方法来获取token,这个方法可以对请求的参数进行校验是否合法,是否给予令牌。
- TokenGranter是一个接口,它有多个实现类,CompositeTokenGranter是其中之一,在grant方法中,会循环遍历所有的授权方式,根据请求参数携带的授权方式码匹配对应的授权处理实现类,调用实现类中的grant方法。
- 4.创建自定义授权处理类,我们可以继承TokenGranter来实现自定义的身份验证以便获取token,而AbstractTokenGranter是一个继承TokenGranter的实现类,一般我们都会继承这个类进行使用。
- 5.下面是自己的实现类,重写了getOAuth2Authentication方法,对参数进行了自定义的校验,校验的方法在下图中标识出。
- 6.对于参数的校验,在源码中,AuthenticationProvider是一个接口,里面有两个方法,一个是校验参数的方法,另一个则是根据当前认证信息匹配出对应的认证提供商类,这个接口有很多实现类,其中ProviderManager类是非常关键的,在这个类中的参数校验方法中,会根据当前要认证的对象,获取符合要求的所有的认证提供商,然后循环匹配出对应的认证提供商,在调取校验方法进行参数校验。
- 我们只需要实现ProviderManager接口的两个方法,自定义自己的参数校验方法,并且把这个自定义的ProviderManager加入到认证提供商集合中,在循环匹配的时候即可匹配到我们自定义的ProviderManager,进行参数校验。
- 7.把自定义的ProviderManager放入ProviderManager集合中,我的方法如下,在配置文件中,重写configure方法,配置ProviderManager,这里除了配置我们自定义的ProviderManager之外,还需要额外配置默认的密码授权模式的ProviderManager,否则client认证将不会通过。
- 8.实现了自定义token的获取,最后要做的是把这个自定义授权模式类,放入系统默认的授权模式集合中,这样在CompositeTokenGranter的grant方法中,在AuthorizationServerEndpointsConfigurer这个类中调用了getDefaultTokenGranters()方法,并且创建了 CompositeTokenGranter的实例对象,进行初始化,但系统已经把默认的授权模式全都写死在程序里了,因此我的解决思路是如下的
- 9.把AuthorizationServerEndpointsConfigurer中,初始化默认授权方式的代码复制一下,在配置文件中额外重新配置自定义的模式,代码如下
package com.doudou.config;
import com.doudou.service.WeChatAbstractTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Configuration
public class TokenGranterConfig {
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore tokenStore;
@Autowired
TokenEnhancer tokenEnhancer;
private AuthorizationCodeServices authorizationCodeServices;
private boolean reuseRefreshToken = true;
private AuthorizationServerTokenServices tokenServices;
private TokenGranter tokenGranter;
/**
* 授权模式
*
* @return
*/
@Bean
public TokenGranter tokenGranter() {
if (tokenGranter == null) {
tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (delegate == null) {
delegate = new CompositeTokenGranter(getDefaultTokenGranters());
}
return delegate.grant(grantType, tokenRequest);
}
};
}
return tokenGranter;
}
/**
* 程序支持的授权类型
*
* @return
*/
private List<TokenGranter> getDefaultTokenGranters() {
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
// 添加授权码模式
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
// 添加刷新令牌的模式
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
// 添加隐式授权模式
tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
// 添加客户端模式
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
// 添加自定义授权模式(实际是密码模式的复制)
tokenGranters.add(new WeChatAbstractTokenGranter(tokenServices, clientDetailsService, requestFactory));
if (authenticationManager != null) {
// 添加密码模式
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
}
return tokenGranters;
}
/**
* TokenServices
*
* @return
*/
private AuthorizationServerTokenServices tokenServices() {
if (tokenServices != null) {
return tokenServices;
}
this.tokenServices = createDefaultTokenServices();
return tokenServices;
}
/**
* 授权码API
*
* @return
*/
private AuthorizationCodeServices authorizationCodeServices() {
if (authorizationCodeServices == null) {
authorizationCodeServices = new InMemoryAuthorizationCodeServices();
}
return authorizationCodeServices;
}
/**
* OAuth2RequestFactory的默认实现,它初始化参数映射中的字段,
* 验证授权类型(grant_type)和范围(scope),并使用客户端的默认值填充范围(scope)(如果缺少这些值)。
*
* @return
*/
private OAuth2RequestFactory requestFactory() {
return new DefaultOAuth2RequestFactory(clientDetailsService);
}
/**
* 默认 TokenService
*
* @return
*/
private DefaultTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore);
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(reuseRefreshToken);
tokenServices.setClientDetailsService(clientDetailsService);
tokenServices.setTokenEnhancer(tokenEnhancer);
addUserDetailsService(tokenServices, this.userDetailsService);
return tokenServices;
}
/**
* 添加预身份验证
*
* @param tokenServices
* @param userDetailsService
*/
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService));
tokenServices.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider>asList(provider)));
}
}
}
10.授权认证服务端点配置
package com.doudou.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.security.KeyPair;
@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenGranter tokenGranter;
@Autowired
UserDetailsService userDetailsService;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
TokenStore tokenStore;
@Bean("keyProp")
public KeyProperties keyProperties() {
return new KeyProperties();
}
@Resource(name = "keyProp")
private KeyProperties keyProperties;
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(this.dataSource).clients(this.clientDetails());
}
@Bean
@Autowired
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(CustomUserAuthenticationConverter customUserAuthenticationConverter) {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory
(keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
.getKeyPair(keyProperties.getKeyStore().getAlias(), keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
//配置自定义的CustomUserAuthenticationConverter
DefaultAccessTokenConverter accessTokenConverter = (DefaultAccessTokenConverter) converter.getAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(customUserAuthenticationConverter);
return converter;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.accessTokenConverter(jwtAccessTokenConverter)
.tokenGranter(tokenGranter) //四种授权模式+刷新令牌的模式+自定义授权模式
.authenticationManager(authenticationManager)//认证管理器
.tokenStore(tokenStore)//令牌存储
.userDetailsService(userDetailsService)//用户信息service
;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
11.当前clientid拥有的授权方式码是通过ClientDetails client = clientDetailsService.loadClientByClientId(clientId);获取的,在数据库中配置的,因此我们需要再oauth_client_details表中,在对应的clientid的authorized_grant_types字段中加上自定义的授权模式码。
TydnCodeGranter.java
package com.sport.uaa.server.granter;
import com.sport.uaa.server.token.TydnCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author xdd
* @since 2024/2/19
*/
public class TydnCodeGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "tydn_code";
private final AuthenticationManager authenticationManager;
public TydnCodeGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
String tydnCode = parameters.get("code");
Authentication userAuth = new TydnCodeAuthenticationToken(tydnCode);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
userAuth = authenticationManager.authenticate(userAuth);
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate code: " + tydnCode);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
TydnCodeAuthenticationToken.java
package com.sport.uaa.server.token;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* @author xdd
* @since 2024/2/19
*/
public class TydnCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
public TydnCodeAuthenticationToken(String tydnCode) {
super(null);
this.principal = tydnCode;
setAuthenticated(false);
}
public TydnCodeAuthenticationToken(Object principal,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
TydnCodeAuthenticationProvider.java
package com.sport.uaa.server.provider;
import com.sport.uaa.server.token.TydnCodeAuthenticationToken;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author xdd
* @since 2024/2/19
*/
@Setter
public class TydnCodeAuthenticationProvider implements AuthenticationProvider {
@Autowired(required = false)
@Qualifier("tydnCodeUserDetailServiceImpl")
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) {
TydnCodeAuthenticationToken authenticationToken = (TydnCodeAuthenticationToken) authentication;
String tydnCode = (String) authenticationToken.getPrincipal();
UserDetails user = userDetailsService.loadUserByUsername(tydnCode);
if (user == null) {
throw new InternalAuthenticationServiceException(tydnCode+"用户不存在");
}
TydnCodeAuthenticationToken authenticationResult = new TydnCodeAuthenticationToken(user, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
return TydnCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}
TydnCodeUserDetailServiceImpl.java
package com.sport.uaa.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.maxrocky.oidcsdk.client.OidcApiClient;
import com.maxrocky.oidcsdk.model.SportOidcApiResult;
import com.maxrocky.oidcsdk.model.response.OauthTokenResponse;
import com.maxrocky.oidcsdk.model.response.SsoUserLoginInfo;
import com.sport.common.auth.details.LoginAppUser;
import com.sport.log.annotation.LogAnnotation;
import com.sport.uaa.feign.UserFeignClient;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TydnCodeUserDetailServiceImpl implements UserDetailsService {
private final UserFeignClient userFeignClient;
private final OidcApiClient oidcApiClient;
@Override
@LogAnnotation(module = "auth-server", recordRequestParam = false)
public UserDetails loadUserByUsername(String tydnCode) throws UsernameNotFoundException {
Long accountUserId = this.tydnUserInfo(tydnCode);
//查询体育大脑用户信息
LoginAppUser loginAppUser = userFeignClient.findByTydnAccountUserId(accountUserId);
if (loginAppUser == null) {
//用户不存在
throw new AuthenticationCredentialsNotFoundException("用户不存在");
}
return loginAppUser;
}
/**
* 根据tydnCode获取体育大脑用户信息
*/
public Long tydnUserInfo(String code){
SportOidcApiResult<OauthTokenResponse> sportOidcApiResult = oidcApiClient.token(code);
if (!"200".equals(sportOidcApiResult.getCode())) {
throw new RuntimeException("通过临时code获取授权码失败");
}
SsoUserLoginInfo ssoUser;
OauthTokenResponse oauthTokenResponse = sportOidcApiResult.getData();
try {
Claims claims = parserJwt(oauthTokenResponse.getId_token());
String userStr = claims.getSubject();
if (Strings.isBlank(userStr)){
throw new RuntimeException("JWT解析失败");
}
ssoUser = JSONObject.parseObject(userStr, SsoUserLoginInfo.class);
} catch (Exception e) {
throw new RuntimeException("JWT解析失败");
}
if(ssoUser.getUserType()!=2){
throw new RuntimeException("只支持账号类用户登录!");
}
return ssoUser.getAccountUser().getId();
}
public Claims parserJwt(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(oidcApiClient.getAppSecret())
.parseClaimsJws(token)
.getBody();
}catch (ExpiredJwtException e) {
claims = e.getClaims();
}
return claims;
}
}
UAAServerConfig.java
package com.sport.uaa.server;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import com.sport.uaa.server.granter.TydnCodeGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import com.sport.common.auth.props.PermitUrlProperties;
import com.sport.common.feign.FeignInterceptorConfig;
import com.sport.common.rest.RestTemplateConfig;
import com.sport.uaa.server.service.RedisAuthorizationCodeServices;
import com.sport.uaa.server.service.RedisClientDetailsService;
import com.sport.uaa.server.token.RedisTemplateTokenStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
*
* @version 创建时间:2017年11月12日 上午22:57:51
*/
@Configuration
@Import({RestTemplateConfig.class,FeignInterceptorConfig.class})
public class UAAServerConfig {
/**
* 声明 ClientDetails实现
*/
@Bean
public RedisClientDetailsService redisClientDetailsService(DataSource dataSource , RedisTemplate<String, Object> redisTemplate ) {
RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
clientDetailsService.setRedisTemplate(redisTemplate);
return clientDetailsService;
}
@Bean
public RandomValueAuthorizationCodeServices authorizationCodeServices(RedisTemplate<String, Object> redisTemplate) {
RedisAuthorizationCodeServices redisAuthorizationCodeServices = new RedisAuthorizationCodeServices();
redisAuthorizationCodeServices.setRedisTemplate(redisTemplate);
return redisAuthorizationCodeServices;
}
/**
*
* @version 创建时间:2017年11月12日 上午22:57:51 默认token存储在内存中
* DefaultTokenServices默认处理
*/
@Component
@Configuration
@EnableAuthorizationServer
@AutoConfigureAfter(AuthorizationServerEndpointsConfigurer.class)
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 注入authenticationManager 来支持 password grant type
*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired(required = false)
private RedisTemplateTokenStore redisTokenStore;
@Autowired(required = false)
private JwtTokenStore jwtTokenStore;
@Autowired(required = false)
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private WebResponseExceptionTranslator webResponseExceptionTranslator;
@Autowired
private RedisClientDetailsService redisClientDetailsService;
@Autowired(required = false)
private RandomValueAuthorizationCodeServices authorizationCodeServices;
/**
* 配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
*/
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
if (jwtTokenStore != null) {
endpoints.tokenStore(jwtTokenStore).authenticationManager(authenticationManager)
// 支持
.userDetailsService(userDetailsService);
// password
// grant
// type;
} else if (redisTokenStore != null) {
endpoints.tokenStore(redisTokenStore).authenticationManager(authenticationManager)
// 支持
.userDetailsService(userDetailsService);
// password
// grant
// type;
}
if (jwtAccessTokenConverter != null) {
endpoints.accessTokenConverter(jwtAccessTokenConverter);
}
endpoints.authorizationCodeServices(authorizationCodeServices);
endpoints.exceptionTranslator(webResponseExceptionTranslator);
// 获取原有默认授权模式(授权码模式、密码模式、客户端模式、简化模式)的授权者
List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
// 添加自定义授权模式授权者
granterList.add(new TydnCodeGranter(authenticationManager, endpoints.getTokenServices(),
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory()
));
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
//配置存储令牌策略
endpoints
//添加自定义模式
.tokenGranter(tokenGranter(endpoints));
}
/**
* 添加自定义授权类型
*
* @return List<TokenGranter>
*/
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
// endpoints.getTokenGranter() 获取SpringSecurity OAuth2.0 现有的授权类型
List<TokenGranter> granters = new ArrayList<TokenGranter>(Collections.singletonList(endpoints.getTokenGranter()));
// 构建自定义授权类型
TydnCodeGranter smsCodeTokenGranter = new TydnCodeGranter(authenticationManager,endpoints.getTokenServices(),
endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory());
// 向集合中添加定义授权类型
granters.add(smsCodeTokenGranter);
// 返回所有类型
return new CompositeTokenGranter(granters);
}
/**
* 配置应用名称 应用id
* 配置OAuth2的客户端相关信息
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// if(clientDetailsService!=null){
// clients.withClientDetails(clientDetailsService);
// }else{
// clients.inMemory().withClient("neusoft1").secret("neusoft1")
// .authorizedGrantTypes("authorization_code", "password",
// "refresh_token").scopes("all")
// .resourceIds(SERVER_RESOURCE_ID).accessTokenValiditySeconds(1200)
// .refreshTokenValiditySeconds(50000)
// .and().withClient("neusoft2").secret("neusoft2")
// .authorizedGrantTypes("authorization_code", "password",
// "refresh_token").scopes("all")
// .resourceIds(SERVER_RESOURCE_ID).accessTokenValiditySeconds(1200)
// .refreshTokenValiditySeconds(50000)
// ;
// }
clients.withClientDetails(redisClientDetailsService);
redisClientDetailsService.loadAllClientToCache();
}
/**
* 对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// url:/oauth/token_key,exposes
security.tokenKeyAccess("permitAll()")
/// public key for token
/// verification if using
/// JWT tokens
// url:/oauth/check_token
.checkTokenAccess("isAuthenticated()")
// allow check token
.allowFormAuthenticationForClients();
// security.allowFormAuthenticationForClients();
security.tokenKeyAccess("permitAll()");
// security.tokenKeyAccess("isAuthenticated()");
}
}
@Configuration
@EnableResourceServer
@EnableConfigurationProperties(PermitUrlProperties.class)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private PermitUrlProperties permitUrlProperties;
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health");
web.ignoring().antMatchers("/oauth/user/token");
web.ignoring().antMatchers("/oauth/client/token");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(
/**
* 判断来源请求是否包含oauth2授权信息
*/
new RequestMatcher() {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public boolean matches(HttpServletRequest request) {
// 请求参数中包含access_token参数
if (request.getParameter(OAuth2AccessToken.ACCESS_TOKEN) != null) {
return true;
}
// 头部的Authorization值以Bearer开头
String auth = request.getHeader("Authorization");
if (auth != null) {
if (auth.startsWith(OAuth2AccessToken.BEARER_TYPE)) {
return true;
}
}
if (antPathMatcher.match(request.getRequestURI(), "/oauth/userinfo")) {
return true;
}
if (antPathMatcher.match(request.getRequestURI(), "/oauth/remove/token")) {
return true;
}
if (antPathMatcher.match(request.getRequestURI(), "/oauth/get/token")) {
return true;
}
if (antPathMatcher.match(request.getRequestURI(), "/oauth/refresh/token")) {
return true;
}
if (antPathMatcher.match(request.getRequestURI(), "/oauth/token/list")) {
return true;
}
if (antPathMatcher.match("/clients/**", request.getRequestURI())) {
return true;
}
if (antPathMatcher.match("/services/**", request.getRequestURI())) {
return true;
}
if (antPathMatcher.match("/redis/**", request.getRequestURI())) {
return true;
}
return false;
}
}
).authorizeRequests().antMatchers(permitUrlProperties.getIgnored()).permitAll().anyRequest()
.authenticated();
}
}
}
SecurityConfig.java
package com.sport.uaa.server.config;
import com.sport.common.auth.props.PermitUrlProperties;
import com.sport.uaa.server.handle.OauthLogoutHandler;
import com.sport.uaa.server.provider.PasswordAuthenticationProvider;
import com.sport.uaa.server.provider.SmsCodeAuthenticationProvider;
import com.sport.uaa.server.provider.TydnCodeAuthenticationProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import javax.annotation.Resource;
/**
* spring security配置
*/
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(PermitUrlProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 登出处理器
*/
@Resource
private OauthLogoutHandler oauthLogoutHandler;
@Resource
private PasswordEncoder passwordEncoder;
/**
* 登出处理器
*/
@Resource
private UserDetailsService userDetailsService;
/**
* 白名单配置
*/
@Resource
private PermitUrlProperties permitUrlProperties;
/**
* 验证码配置
*/
@Resource
private ValidateCodeConfig validateCodeConfig;
@Resource
private PasswordAuthenticationProvider passwordAuthenticationProvider;
/**
* 短信登录模式
*/
@Resource
private SmsCodeAuthenticationProvider smsCodeAuthenticationProvider;
/**
* web访问安全控制
*
* @param web WebSecurity
*/
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers( "/configuration/ui", "/swagger-resources", "/configuration/security", "/login.html");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/health");
// 忽略登录界面
web.ignoring().antMatchers("/login.html");
web.ignoring().antMatchers("/index.html");
web.ignoring().antMatchers("/oauth/user/token");
web.ignoring().antMatchers("/oauth/client/token");
web.ignoring().antMatchers("/validata/code/**");
web.ignoring().antMatchers("/sms/**");
web.ignoring().antMatchers("/authentication/**");
web.ignoring().antMatchers(permitUrlProperties.getIgnored());
}
/**
* 认证管理
*
* @return 认证管理对象
* @throws Exception 认证异常信息
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* http请求配置
*
* @param http HttpSecurity
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用CSRF(Cross-site request forgery):跨站请求伪造,
http.csrf().disable();
//所有http请求必须通过安全认证
http.authorizeRequests().anyRequest().authenticated();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
// 注册短信认证方式
http.authenticationProvider(smsCodeAuthenticationProvider);
http.apply(validateCodeConfig);
//登出配置
http.logout().logoutSuccessUrl("/login.html").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()).addLogoutHandler(oauthLogoutHandler).clearAuthentication(true);
// 解决不允许显示在iframe的问题
http.headers().frameOptions().disable();
http.headers().cacheControl();
}
/**
* 配置自定义的AuthenticationProvider
*
* @param auth the {@link AuthenticationManagerBuilder} to use
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
auth.authenticationProvider(passwordAuthenticationProvider);
auth.authenticationProvider(tydnCodeAuthenticationProvider());
}
@Bean
public TydnCodeAuthenticationProvider tydnCodeAuthenticationProvider(){
return new TydnCodeAuthenticationProvider();
}
}