Spring Cloud Gateway(四)
涉及相关角色
- Client: 客户
- gateway-sertvice: 网关服务,转发,验证,鉴权
- oauth2-service: 授权服务,颁发令牌
- product-service: 资源服务
流程方案
- 客户向gateway-service 请求访问令牌;
- gateway-service 将请求转发到 授权服务 oauth2-service;
- 授权服务验证成功,颁发令牌;
- 客户携带令牌向gateway-service 请求访问的特定资源;
- gateway-service 验证令牌及访问权限,成功则转发到相关资源服务获取资源。
oauth-service
新增授权服务
<artifactId>oauth2-service</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>pr.iceworld.fernando</groupId>
<artifactId>common-entity</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--
引入 caffeine 解决 不能启用 cache功能
caffeine, spring-context-support
当客户的调用服务 lb://ServiceName 时 会导致 LoadBalancerCacheManager not available, returning delegate without caching.
-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
</dependencies>
server:
port: 9011
spring:
application:
name: oauth2-service
zipkin:
base-url: http://192.168.79.8:9411
sender:
type: rabbit
sleuth:
sampler:
# 全部采样
probability: 1.0
rabbitmq:
host: 192.168.79.8
port: 5672
username: admin
password: admin
cloud:
nacos:
discovery:
server-addr: 192.168.79.8:9999
service: ${spring.application.name}
group: provider-consumer
jackson:
date-format: yyyy-MM-dd HH:mm:ss
redis:
port: 6379
host: 192.168.79.8
management:
endpoints:
web:
exposure:
include: "*"
$ keytool -genkey -keystore jwt_test.keystore -keypass tester -storepass tester -alias jwt_test -keyalg RSA -validity 365 -dname "CN=fernando, OU=iceworld, O=iceworld, L=GZ, ST=GD, C=CN"
Keytool 是一个Java 数据证书的管理工具 ,Keytool 将密钥(key)和证书(certificates)存在一个称为keystore的文件中。
在keystore里,包含两种数据:
- 密钥实体(Key entity)——密钥(secret key)又或者是私钥和配对公钥(采用非对称加密)
- 可信任的证书实体(trusted certificate entries)——只包含公钥
keytool -genkey -keystore jwt_test.keystore -keypass tester -storepass tester -alias jwt_test -keyalg RSA -validity 365 "CN=(名字与姓氏), OU=(组织单位名称), O=(组织名称), L=(城市或区域名称), ST=(州或省份名称), C=(单位的两字母国家代码)"
将生成的密钥证书文件放入oauth2-service/src/main/resources/jwt_test.keystore
@EnableWebSecurity
// 开启授权服务
@EnableAuthorizationServer
@EnableDiscoveryClient
@SpringBootApplication
public class Oauth2MainApplication {
// ...
}
redis 配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
@Component
@AllArgsConstructor
@Slf4j
public class RedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
log.error("{}", e);
return false;
}
}
}
/**
* jwt 内容增强器
*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Map<String, Object> additionalInformation = new HashMap();
additionalInformation.put("id", userPrincipal.getId());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
return accessToken;
}
}
Jwt 配置
JWT 令牌值和 OAuth 身份验证信息(双向)之间转换的助手。授予令牌时还充当TokenEnhancer
@Configuration
public class JwtConfig {
@Bean
public JwtAccessTokenConverter accessTokenConverter(@Qualifier("keyPair") KeyPair keyPair) {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair);
return jwtAccessTokenConverter;
}
@Bean
public KeyPair keyPair() {
// 从classpath下的证书中获取秘钥对
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt_test.keystore"), "tester".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt_test", "tester".toCharArray());
}
}
使用单个密钥创建新的 JSON Web Key (JWK) 集合,暴露给外部服务器
@RestController
public class KeyPairController {
@Resource
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();
}
}
/**
* 允许获取公钥接口的访问
*/
@Configuration
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()
.and().formLogin().permitAll();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
public class UserDto {
private Long id;
private String username;
private String password;
private Integer status;
private List<String> roles;
}
重要用户数据,适配 Spring security UserDetailsService
@Data
public class UserPrincipal implements UserDetails {
/**
* ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 用户状态
*/
private Boolean enabled;
/**
* 权限数据
*/
private Collection<SimpleGrantedAuthority> authorities;
public UserPrincipal(UserDto userDto) {
this.setId(userDto.getId());
this.setUsername(userDto.getUsername());
this.setPassword(userDto.getPassword());
this.setEnabled(userDto.getStatus() == 1);
if (userDto.getRoles() != null) {
authorities = new ArrayList<>();
userDto.getRoles().forEach(item -> authorities.add(new SimpleGrantedAuthority(item)));
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
通过用户名获取数据,实现 UserDetailsService
@Service
public class UserService implements UserDetailsService {
private List<UserDto> userList;
@Resource
private PasswordEncoder passwordEncoder;
@PostConstruct
public void initData() {
String password = passwordEncoder.encode("123456");
userList = new ArrayList<>();
userList.add(new UserDto(1L,"admin", password,1, Arrays.asList("ADMIN")));
userList.add(new UserDto(2L,"fernando", password,1, Arrays.asList("NORMAL")));
userList.add(new UserDto(3L,"normal", password,1, Arrays.asList("NORMAL")));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<UserDto> findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(findUserList)) {
throw new UsernameNotFoundException(MessageConst.USERNAME_PASSWORD_ERROR);
}
UserPrincipal securityUser = new UserPrincipal(findUserList.get(0));
if (!securityUser.isEnabled()) {
throw new DisabledException(MessageConst.ACCOUNT_DISABLED);
} else if (!securityUser.isAccountNonLocked()) {
throw new LockedException(MessageConst.ACCOUNT_LOCKED);
} else if (!securityUser.isAccountNonExpired()) {
throw new AccountExpiredException(MessageConst.ACCOUNT_EXPIRED);
} else if (!securityUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(MessageConst.CREDENTIALS_EXPIRED);
}
return securityUser;
}
}
/**
* 认证服务器配置
* 加载用户信息的服务UserService及RSA的钥匙对KeyPair
*/
@Configuration
@AllArgsConstructor
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final UserService userDetailsService;
private final AuthenticationManager authenticationManager;
private final JwtTokenEnhancer jwtTokenEnhancer;
private final JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* 客户端服务配置
* @param clients the client details configurer
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("397b0b6e-4e20-4fa4-99b7-513d7618290c")
.secret(passwordEncoder.encode("123456"))
.scopes("all")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(7200)
.refreshTokenValiditySeconds(86400);
}
/**
* 授权服务端配置
* @param endpoints the endpoints configurer
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList();
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
// 配置JWT的内容增强器
enhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
// 配置加载用户信息的服务
.userDetailsService(userDetailsService)
.accessTokenConverter(jwtAccessTokenConverter)
.tokenEnhancer(enhancerChain);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients();
}
授权可访问的服务
@Service
public class ResourceService {
private Map<String, List<String>> resourceRoles;
@Resource
private RedisUtils redisUtils;
@PostConstruct
public void initData() {
resourceRoles = new TreeMap<>();
resourceRoles.put("/api/productServ/products", Arrays.asList("ADMIN"));
resourceRoles.put("/api/productServ/products/*", Arrays.asList("ADMIN"));
resourceRoles.put("/api/productServ/productCategories", Arrays.asList("ADMIN", "NORMAL"));
resourceRoles.put("/api/productServ/productCategories/*", Arrays.asList("ADMIN", "NORMAL"));
redisUtils.set(RedisConst.RESOURCE_ROLES, resourceRoles);
}
}
gateway-service
引入 jwt, oauth 等依赖包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>pr.iceworld.fernando</groupId>
<artifactId>common-entity</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
spring:
application:
name: gateway-service
zipkin:
base-url: http://192.168.79.8:9411
sender:
type: rabbit
sleuth:
sampler:
# 全部采样
probability: 1.0
rabbitmq:
host: 192.168.79.8
port: 5672
username: admin
password: admin
cloud:
nacos:
discovery:
server-addr: 192.168.79.8:9999
service: ${spring.application.name}
group: provider-consumer
gateway:
routes:
- id: user-route
uri: lb://user-service
predicates:
- Path=/api/userServ/**
filters:
- StripPrefix=2
- id: product-route
uri: lb://product-service
predicates:
- Path=/api/productServ/**
filters:
- StripPrefix=2
- id: order-route
uri: lb://order-service
predicates:
- Path=/api/orderServ/**
filters:
- StripPrefix=2
- id: oauth2-route
uri: lb://oauth2-service
predicates:
- Path=/api/authServ/**
filters:
- StripPrefix=2
sentinel:
eager: true
transport:
port: 8719
dashboard: 127.0.0.1:18182
client-ip: localhost
enabled: true
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:9011/rsa/publicKey
redis:
host: 192.168.79.8
port: 6379
ignore:
urls:
- "/actuator/**"
- "/api/authServ/oauth/token"
- "/api/orderServ/**"
- "/zipkin/**"
- "/nacos/**"
management:
endpoints:
gateway:
enabled: true
web:
exposure:
include: "*"
配置白名单
@Data
@EqualsAndHashCode(callSuper = false)
@Component
@ConfigurationProperties(prefix="ignore")
public class IgnoreUrlsConfig {
private List<String> urls;
}
Redis 设置
@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
/**
* 令牌过期,失效异常处理
*/
@Component
public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
String body= JSONObject.toJSONString(ResultData.unauthorized(e.getMessage()));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
}
/**
* 无权访问异常处理
*/
@Component
public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
String body= JSONObject.toJSONString(ResultData.forbidden(denied.getMessage()));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}
}
/**
* 鉴权管理器,用于判断是否有资源的访问权限
*/
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
@Resource
private RedisUtils redisUtils;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
//从Redis中获取当前路径可访问角色列表
URI uri = authorizationContext.getExchange().getRequest().getURI();
Object obj = redisUtils.get(RedisConst.RESOURCE_ROLES);
Map<String, List<String>> values = (Map<String, List<String>>) obj;
PathMatcher pathMatcher = new AntPathMatcher();
List<String> authorities = null;
for (Map.Entry<String, List<String>> me: values.entrySet()) {
if (pathMatcher.match(me.getKey(), uri.getPath())) {
authorities = me.getValue();
}
}
authorities = authorities.stream().map(e -> AuthConst.AUTHORITY_PREFIX + e).collect(Collectors.toList());
// 认证通过且角色匹配的用户可访问当前路径
return mono
.filter(Authentication::isAuthenticated)
// 获取认证后的全部权限
.flatMapIterable(Authentication::getAuthorities)
.map(GrantedAuthority::getAuthority)
.any(authorities::contains)
.map(AuthorizationDecision::new)
.defaultIfEmpty(new AuthorizationDecision(false));
}
}
/**
* 资源服务器配置
*/
@AllArgsConstructor
@Configuration
public class ResourceServerConfig {
private final AuthorizationManager authorizationManager;
private final IgnoreUrlsConfig ignoreUrlsConfig;
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
private final AuthorizationWhiteList2RemoveJwtFilter authorizationWhiteList2RemoveJwtFilter;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(
ServerHttpSecurity http,
@Qualifier("jwtAuthenticationConverter") Converter jwtAuthenticationConverter) {
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter);
// 对白名单路径,直接移除JWT请求头
http.addFilterBefore(authorizationWhiteList2RemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
// url白名单
.pathMatchers(ignoreUrlsConfig.getUrls().toArray(new String[0])).permitAll()
.anyExchange()
// 鉴权管理
.access(authorizationManager)
.and()
.exceptionHandling()
// 未被授权
.accessDeniedHandler(restfulAccessDeniedHandler)
// 配置应用程序请求身份验证时要执行的操作
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
// 禁用 CSRF
.csrf().disable();
return http.build();
}
@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConst.AUTHORITY_PREFIX);
jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConst.AUTHORITY_CLAIM_NAME);
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
设置全局过滤器,方便后续服务可以定位到当前是哪个用户
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isEmpty(token)) {
return chain.filter(exchange);
}
try {
//从token中解析用户信息并设置到Header中去
String realToken = token.replace("Bearer ", "");
JWSObject jwsObject = JWSObject.parse(realToken);
String userStr = jwsObject.getPayload().toString();
log.info("AuthGlobalFilter#filter() user:{}", userStr);
ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
exchange = exchange.mutate().request(request).build();
} catch (ParseException e) {
log.error(e.getMessage());
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
@Component
public class AuthorizationWhiteList2RemoveJwtFilter implements WebFilter {
@Resource
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
PathMatcher pathMatcher = new AntPathMatcher();
//白名单路径移除JWT请求头
List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, uri.getPath())) {
request = exchange.getRequest().mutate().header("Authorization", "").build();
exchange = exchange.mutate().request(request).build();
return chain.filter(exchange);
}
}
return chain.filter(exchange);
}
}
验证,获取access_token
POST /api/authServ/oauth/token HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 118
grant_type=password&client_id=397b0b6e-4e20-4fa4-99b7-513d7618290c&client_secret=123456&username=admin&password=123456
返回
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVC...",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hb...",
"expires_in": 59,
"scope": "all",
"id": 1,
"jti": "10396d06-f30f-437b-903c-716486e5f9ec"
}
access_token 失效后,使用 refresh_token 获取新的token
POST /api/authServ/oauth/token HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 789
grant_type=refresh_token&client_id=397b0b6e-4e20-4fa4-99b7-513d7618290c&client_secret=123456&refresh_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVC...
返回
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp...",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV...",
"expires_in": 59,
"scope": "all",
"id": 1,
"jti": "7c3af89c-9fdb-42b2-b50b-3bbba2bcc10c"
}