依赖搭建
认证中心原则上也是一个独立的服务,需要引入Web依赖。
<dependencies>
<dependency>
<groupId>com.book</groupId>
<artifactId>cbook_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
</dependencies>
Spring Security 配置
这里和普通的Spring Security配置一样
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 这个配置类是创建我们自定义授权模式的认证处理器
private SmsCodeSecurityConfig smsCodeSecurityConfig;
@Autowired
public void setSmsCodeSecurityConfig(SmsCodeSecurityConfig smsCodeSecurityConfig) {
this.smsCodeSecurityConfig = smsCodeSecurityConfig;
}
@Resource
private GlobalAuthenticationEntryPoint authenticationEntryPoint;
@Resource
private GlobalAccessDeniedHandler accessDeniedHandler;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/code/*")
.and();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.apply(smsCodeSecurityConfig)
.and()
.exceptionHandling() // 自定义的认证和授权异常处理器
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.and()
.authorizeRequests()
.antMatchers("/code/*").permitAll() // 放行获取验证码的请求
.anyRequest().authenticated();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
资源配置中心配置
@EnableAuthorizationServer
@Configuration
@Slf4j
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
public DataSource dataSource;
@Autowired
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Resource
private ClientDetailsService clientDetailsService;
@Resource
private TokenStore tokenStore;
@Resource
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Resource
private AuthenticationManager authenticationManager;
@Resource
private GlobalAuthenticationEntryPoint authenticationEntryPoint;
@Resource
private GlobalAccessDeniedHandler accessDeniedHandler;
// 客户端配置
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 配置客户端认证模块,oauth2.0授权模式默认在对用户进行认证之前会对客户端进行认证,并且是客户端认证在前面,这里需要实现ClientDetailsService中的loadClientByClientId方法,从数据库中加载客户端信息。
clients.jdbc(dataSource).clients(clientDetailsService);
}
/**
* 配置令牌访问的安全约束
*/
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.tokenKeyAccess("permitAll()");
}
/**
* 配置令牌访问的端点
*/
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// 获取oauth的授权模式,将我们自定义的授权模式加进去
List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
granters.add(new MobilePassWordGranter(tokenServices(), clientDetailsService, endpoints.getOAuth2RequestFactory(), authenticationManager));
endpoints
//令牌管理服务,无论哪种模式都需要
.tokenServices(tokenServices())
.tokenGranter(new CompositeTokenGranter(granters))
.exceptionTranslator(new GlobalOauth2ExceptionHandler())
//只允许POST提交访问令牌,uri:/oauth/token
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
/**
* 配置令牌的相关信息
*/
public AuthorizationServerTokenServices tokenServices(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
//客户端端配置策略
tokenServices.setClientDetailsService(clientDetailsService);
//支持令牌的刷新
tokenServices.setSupportRefreshToken(true);
//令牌服务
tokenServices.setTokenStore(tokenStore);
//access_token的过期时间
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 3);
//refresh_token的过期时间
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
//一定要配置这一步,否则不会使用JWT方式生成token,默认生成的access_token过短
tokenServices.setTokenEnhancer(jwtAccessTokenConverter);
return tokenServices;
}
}
令牌增强配置
@Configuration
@Slf4j
public class JwtConfig {
@Resource(name = "keyProp")
private KeyProperties keyProperties;
@Bean("keyProp")
public KeyProperties keyProperties()
{
// 这是使用java生成的证书来生成公钥私钥的配置类
return new KeyProperties();
}
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter)
{
return new JwtTokenStore(jwtAccessTokenConverter);
}
/**
* JWT令牌转换器
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter()
{
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenEnhancer();
// 获取公钥私钥信息
KeyPair keyPair = new KeyStoreKeyFactory(
keyProperties.getKeyStore().getLocation(),
keyProperties.getKeyStore().getSecret().toCharArray()
).getKeyPair(keyProperties.getKeyStore().getAlias(),
keyProperties.getKeyStore().getPassword().toCharArray());
// RSA 私钥加密
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
tokenConverter.setSigner(new RsaSigner(privateKey));
return tokenConverter;
}
/**
* JWT令牌增强,继承JwtAccessTokenConverter
* 将业务所需的额外信息放入令牌中,这样下游微服务就能解析令牌获取
*/
public static class JwtAccessTokenEnhancer extends JwtAccessTokenConverter {
/**
* 重写enhance方法,在其中扩展
*/
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
log.info("accessToken=={}", accessToken);
Object principal = authentication.getUserAuthentication().getPrincipal();
if (principal instanceof LoginUser)
{
LoginUser loginUser = (LoginUser) principal;
System.out.println(loginUser.toString());
// 设置用户信息
LinkedHashMap<String, Object> info = new LinkedHashMap<>();
// info.put(SysConstant.USER_ID, loginUser.getUserId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
}
return super.enhance(accessToken, authentication);
}
}
}
授权处理器配置SmsCodeSecurityConfig
@Component
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private RedisCache redisCache;
@Autowired
public void setRedisCache(RedisCache redisCache) {
this.redisCache = redisCache;
}
private RSAUtils rsaUtils;
@Autowired
public void setRsaUtils(RSAUtils rsaUtils) {
this.rsaUtils = rsaUtils;
}
private PasswordEncoder passwordEncoder;
@Autowired
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
private SmsCodeUserDetailService smsCodeUserDetailService;
@Autowired
public void setSmsCodeUserDetailService(SmsCodeUserDetailService smsCodeUserDetailService) {
this.smsCodeUserDetailService = smsCodeUserDetailService;
}
@Override
public void configure(HttpSecurity builder) {
MobilePasswordAuthenticationProvider mobilePasswordAuthenticationProvider = new MobilePasswordAuthenticationProvider(smsCodeUserDetailService, passwordEncoder, redisCache, rsaUtils);
// 构造授权处理器并配置到Spring Security中
builder.authenticationProvider(mobilePasswordAuthenticationProvider);
}
}