一、auth服务
使用keytool
生成RSA证书jwt.jks
,复制到resource
目录下,在JDK的bin
目录下使用命令:
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
1、创建UserServiceImpl
类实现Spring Security的UserDetailsService
接口,用于加载用户信息;
2、添加认证服务相关配置Oauth2ServerConfig
,需要配置加载用户信息的服务UserServiceImpl
及RSA的钥匙对KeyPair
;
3、如果你想往JWT中添加自定义信息,比如说登录用户的ID
,可以自己实现TokenEnhancer
接口;
/***
* JWT内容增强器
* @author albert
*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer{
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser)authentication.getPrincipal();
Map<String, Object> info = new HashMap<>(4);
//把用户ID设置到JWT中
info.put("id",securityUser.getId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
4、由于网关服务需要RSA公钥来验证签名是否合法,所以认证服务需要有个接口把公钥暴露出来
/**
* 获取RSA公钥接口
*/
@RestController
public class KeyPairController {
@Autowired
private KeyPair keyPair;
@GetMapping("/rsa/publicKey")
public Map<String, Object> getKey(){
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}
5、不要忘了还需要配置Spring Security,允许获取公钥接口的访问;
/***
* SpringSecurity配置
* @author albert
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 允许获取公钥接口的访问
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers("/rsa/publicKey").permitAll()
.anyRequest()
.authenticated()
;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
二、gataway服务
接下来我们就可以搭建网关服务了,它将作为Oauth2的资源服务、客户端服务使用,对访问微服务的请求进行统一的校验认证和鉴权操作。
在pom.xml
中添加相关依赖,主要是Gateway、Oauth2和JWT相关依赖;
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>8.16</version>
</dependency>
</dependencies>
在application.yml
添加配置,主要是路由规则配置、Oauth2中RSA公钥配置及路由白名单配置;
server:
port: 8201
spring:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能
lower-case-service-id: true #使用小写service-id,默认是大写
routes: #配置路由路径
- id: ops-demo
uri: lb://ops-demo
predicates:
- Path=/ops-demo/**
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: 'http://localhost:8201/ops-auth/rsa/publicKey' #配置RSA的公钥访问地址
redis:
database: 5
port: 6379
host: 172.16.3.71
password: 123456
secure:
ignore:
urls: #配置白名单路径
- "/ops-auth/oauth/token"
- "/ops-auth/rsa/publicKey"
logging:
level:
com.alibaba.nacos.client.naming: warn
对网关服务进行配置安全配置,由于Gateway使用的是WebFlux
,所以需要使用@EnableWebFluxSecurity
注解开启;
/**
* 资源服务器配置
* Created by fwone on 2020/6/19.
* @author albert
*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
// 自定义的认证管理器
private final AuthorizationManager authorizationManager;
// 自定义白名单配置
private final IgnoreUrlsConfig ignoreUrlsConfig;
// 自定义未授权的处理器
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
// 自定义未认证的处理器
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
// 白名单路径,直接移除JWT请求头
private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter());
//自定义处理JWT请求头过期或签名错误的结果
http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
//对白名单路径,直接移除JWT请求头
http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
http.authorizeExchange()
//预检请求都放行
.pathMatchers(HttpMethod.OPTIONS).permitAll()
//白名单配置
.pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()
//鉴权管理器配置
.anyExchange().access(authorizationManager)
.and().exceptionHandling()
//处理未授权
.accessDeniedHandler(restfulAccessDeniedHandler)
//处理未认证
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and().csrf().disable();
return http.build();
}
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}