安装
本文使用最新版本的依赖。
父pom,省略其他不相关依赖,根据实际指定和添加
<dependencyManagement>
<dependencies>
<!-- spring-cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud-alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
uaa工程,即认证服务
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
发现,依赖没被导入,查阅资料发现2020版本不再提供版本,需要自己添加
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!-- security -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
授权服务器
授权服务器3个重点
- 令牌管理服务
- 令牌安全约束
- 令牌端点服务
完整代码如下,MyUserDetailsService需要自己定义
/**
* 认证服务
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
final AuthenticationManager authenticationManager;
final JwtAccessTokenConverter jwtAccessTokenConverter;
final TokenStore tokenStore;
final MyUserDetailsService myUserDetailsService;
final DataSource dataSource;
final PasswordEncoder passwordEncoder;
@Resource
AuthorizationCodeServices authorizationCodeServices;
@Resource
ClientDetailsService clientDetails;
public AuthorizationServerConfig(AuthenticationManager authenticationManager,
JwtAccessTokenConverter jwtAccessTokenConverter,
TokenStore tokenStore,
MyUserDetailsService myUserDetailsService,
DataSource dataSource,
PasswordEncoder passwordEncoder) {
this.authenticationManager = authenticationManager;
this.jwtAccessTokenConverter = jwtAccessTokenConverter;
this.tokenStore = tokenStore;
this.myUserDetailsService = myUserDetailsService;
this.dataSource = dataSource;
this.passwordEncoder = passwordEncoder;
}
/**
* 将客户端信息存储到数据库
*
* @return
*/
@Bean
public ClientDetailsService clientDetails() {
JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
clientDetailsService.setPasswordEncoder(passwordEncoder);
return clientDetailsService;
}
/**
* 令牌管理服务
*
* @return
*/
@Bean
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setClientDetailsService(clientDetails);//客户端详情服务
defaultTokenServices.setSupportRefreshToken(true);//不支持刷新令牌
defaultTokenServices.setTokenStore(tokenStore);//令牌存储策略
defaultTokenServices.setAccessTokenValiditySeconds(7200);
defaultTokenServices.setRefreshTokenValiditySeconds(259200);
//令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(jwtAccessTokenConverter));
defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
return defaultTokenServices;
}
/**
* 设置授权码模式
*
* @return
*/
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 令牌安全约束
*
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
/**
* 客户端详情服务
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails);
}
/**
* 令牌端点服务
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)//密码模式
.authorizationCodeServices(authorizationCodeServices)//授权码服务
.tokenServices(tokenService())//令牌管理服务
.reuseRefreshTokens(false).userDetailsService(myUserDetailsService)//刷新令牌
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
}
CustomAccessDeniedHandler
/**
* 自定义 Access Denied
*/
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setCharacterEncoding(String.valueOf(StandardCharsets.UTF_8));
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.print(new ObjectMapper().writeValueAsString(HttpResult.failed(ConstVal.ACCESS_DENIED_MSG)));
printWriter.flush();
printWriter.close();
}
}
CustomAuthenticationEntryPoint
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpServletResponse.setCharacterEncoding(String.valueOf(StandardCharsets.UTF_8));
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
PrintWriter printWriter = httpServletResponse.getWriter();
Throwable cause = e.getCause();
if (cause instanceof InvalidTokenException) {
printWriter.print(new ObjectMapper().writeValueAsString(HttpResult.failed(ConstVal.INVALID_TOKEN_MSG)));
} else {
printWriter.print(new ObjectMapper().writeValueAsString(HttpResult.failed(ConstVal.UNAUTHORIZED_MSG)));
}
printWriter.flush();
printWriter.close();
}
}
TokenConfig
@Configuration
public class TokenConfig {
@Resource(name = "keyProp")
private KeyProperties keyProperties;
@Bean("keyProp")
public KeyProperties keyProperties() {
return new KeyProperties();
}
/**
* 使用redis存储认证token
*
* @return
*/
@Bean
public TokenStore tokenStore() {
//无状态方式的存储
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 使用非对称加密算法来对Token进行签名
*
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = (new KeyStoreKeyFactory(this.keyProperties.getKeyStore().getLocation(), this.keyProperties.getKeyStore().getSecret().toCharArray()))
.getKeyPair(this.keyProperties.getKeyStore().getAlias(), this.keyProperties.getKeyStore().getPassword().toCharArray());
converter.setKeyPair(keyPair);
return converter;
}
}
WebSecurityConfig
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
final MyUserDetailsService userDetailsService;
public WebSecurityConfig(MyUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
/**
* 认证管理器
*
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 密码编码器
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 用户信息服务
*
* @return
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setHideUserNotFoundExceptions(false);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
/**
* 用户验证
*
* @param auth
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/oauth/token").permitAll().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler())//自定义异常处理
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and().formLogin();
}
}
encrypt:
key-store:
location: classpath:keystore.jks
secret: ice
alias: ice
password: ice
keytool -genkeypair -alias ice -keyalg RSA -keypass ice -keystore keystore.jks -storepass ice
资源服务器
资源服务器比较简单,添加依赖和配置文件即可
WebSecurityConfig配置文件
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
@Configuration
@EnableResourceServer
@Slf4j
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@SneakyThrows
@Bean
public ResourceServerTokenServices tokenService() {
log.info(">>> 资源服务器认证...");
DefaultTokenServices service = new DefaultTokenServices();
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(new String(FileCopyUtils.copyToByteArray(
new ClassPathResource("public.txt").getInputStream())));
converter.afterPropertiesSet();
service.setTokenStore(new JwtTokenStore(converter));
return service;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("ice_cloud_life").tokenServices(tokenService());
}
}
以上需要使用公钥文件
keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.jks -deststoretype pkcs12
一开始,没有生效,是因为没写converter.afterPropertiesSet();
调试代码JwtAccessTokenConverter.java
protected Map<String, Object> decode(String token) {
try {
Jwt jwt = JwtHelper.decodeAndVerify(token, verifier);
String claimsStr = jwt.getClaims();
Map<String, Object> claims = objectMapper.parseMap(claimsStr);
if (claims.containsKey(EXP) && claims.get(EXP) instanceof Integer) {
Integer intValue = (Integer) claims.get(EXP);
claims.put(EXP, new Long(intValue));
}
this.getJwtClaimsSetVerifier().verify(claims);
return claims;
}
catch (Exception e) {
throw new InvalidTokenException("Cannot convert access token to JSON", e);
}
}
第一句就报错了,因为verifier未null。通过在类中寻找verifier的初始化后发现。
public void afterPropertiesSet() throws Exception {
if (verifier != null) {
// Assume signer also set independently if needed
return;
}
//....
}
于是在配置文件中添加调用。