Spring Cloud Gateway(四)

Spring Cloud Gateway(四)

什么是OAuth

在这里插入图片描述

涉及相关角色

  • Client: 客户
  • gateway-sertvice: 网关服务,转发,验证,鉴权
  • oauth2-service: 授权服务,颁发令牌
  • product-service: 资源服务

流程方案

  1. 客户向gateway-service 请求访问令牌;
  2. gateway-service 将请求转发到 授权服务 oauth2-service;
  3. 授权服务验证成功,颁发令牌;
  4. 客户携带令牌向gateway-service 请求访问的特定资源;
  5. 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"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值