其他分享
首页 > 其他分享> > Spring Gateway 开发实例

Spring Gateway 开发实例

作者:互联网

简介

基于spring cloud gateway,微服务网关,注册中心使用nacos, 具备微服务动态路由,jwt token鉴权功能CheckTokenFilter, 路径白名单配置。

架构图

image

网关作为所有服务的请求入口,鉴权token 用的jwt , 好处是jwt本身携带用户身份信息,网关校验通过后,可以直接把用户信息发在请求头传递给上游服务,不要额外存储。

交互时序图

image

实现

设计介绍完了就,开始实现

  1. 创建工程pom文件添加依赖
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>

		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>
      <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.version}</version>
        </dependency>
  1. 实现GlobalFilter接口, jwt 鉴权的主要逻辑
@Component
@RequiredArgsConstructor
public class CheckTokenFilter implements GlobalFilter, Ordered {

    public static final String AUTHHEADER = "authorization";
    public static final String USER_ID_KEY = "userId";
    public static final String USER_NAME_KEY = "userName";
    public static final String TRACE_ID = "traceId";

    static final String BODY_401 = " {\n" +
            "  \"code\": 401,\n" +
            "  \"message\": \"Unauthorized\"\n" +
            "}";

    static final String BODY_403 = " {\n" +
            "  \"code\": 403,\n" +
            "  \"message\": \"token expired\"\n" +
            "}";

    private final CheckTokenUtil checkTokenUtil;
    private final TokenParse tokenParse;
    //布隆过滤器,对校验过的token过滤避免重复校验,提高性能
    private final CircleBloomFilter passedCircleBloomFilter;
    private final CircleBloomFilter stopedCircleBloomFilter;

    private final MyFilterConfiguration myFilterConfiguration;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        if (request.getMethod() == HttpMethod.OPTIONS){
            return chain.filter(exchange);
        }
        //请求路径白名单 判断
        if (checkWhitePath(request.getPath().value())){
            return chain.filter(exchange);
        }
        //token
        String token = request.getHeaders().getFirst(AUTHHEADER);
        if(token == null){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return getVoidMono(response, request, BODY_401);
        }

        if (stopedCircleBloomFilter.exists(token)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return getVoidMono(response, request, BODY_401);
        }
        Claims claims = null;
        if (passedCircleBloomFilter.exists(token)){
            claims = tokenParse.parseToken(token);
           setHeaders(claims, request.mutate());
        }else {
            try {
                //jwt token 校验
                claims = checkTokenUtil.check(token);
                passedCircleBloomFilter.put(token);
                //重写请求头,带上 userId
                setHeaders(claims, request.mutate());
            } catch (ExpiredJwtException e) {
                stopedCircleBloomFilter.put(token);
                response.setStatusCode(HttpStatus.FORBIDDEN);
                return getVoidMono(response, request, BODY_403);
            } catch (Exception e){
                stopedCircleBloomFilter.put(token);
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return getVoidMono(response, request, BODY_401);
            }
        }
        return chain.filter(exchange);
    }

    private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, ServerHttpRequest httpRequest, String body) {
        HttpHeaders headers = serverHttpResponse.getHeaders();
        headers.add("Content-Type", "application/json;charset=UTF-8");
        
        DataBuffer dataBuffer = serverHttpResponse.bufferFactory().wrap(body.getBytes());
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return -100;
    }

    private boolean checkWhitePath(String reqPath){
        AntPathMatcher pathMatcher = new AntPathMatcher();
        for (String white : myFilterConfiguration.getWhiteList()) {
            if (pathMatcher.match(white, reqPath)) {
                return true;
            }
        }

        return false;
    }

    private void setHeaders(Claims claims, ServerHttpRequest.Builder builder){
        String traceId = UUID.randomUUID().toString();
        builder.header(TRACE_ID, traceId);
        builder.header(USER_ID_KEY, claims.get("uid").toString());
        try {
            builder.header(USER_NAME_KEY, URLEncoder.encode(claims.getSubject(), "utf-8"));
        } catch (UnsupportedEncodingException e) {
            builder.header(USER_NAME_KEY, claims.getSubject());
        }
        builder.header(Claims.AUDIENCE, claims.getAudience());
        Object eid = claims.get("eid");
        builder.header("employeeId", eid != null ? String.valueOf(eid) : null);
        Object admin = claims.get("admin");
        builder.header("admin", admin != null ? String.valueOf(admin) : null);
    }

  
}

3.网关配置

spring:
  cloud:
    nacos:
      discovery:
        #nacos 地址
        server-addr: 127.0.0.1:8848
        namespace:
        ip:

    gateway:
      default-filters:
        - DedupeResponseHeader=Vary Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
        - DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_FIRST
        - RemoveRequestHeader=Authorization
      discovery:
        locator:
          enabled: true
      globalcors:
        # 跨域配置
        cors-configurations:
          '[/**]':
            maxAge: 3600
            allowedOrigins: "*"
            allowedHeaders: "Origin, X-Requested-With, Content-Type, Accept, Authorization, *"
            allowCredentials: true
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS

      httpclient:
        pool:
          max-connections: 2000
        connect-timeout: 3000
        response-timeout: 15000

      routes:
        - id: test
          uri: https://httpbin.org
          predicates:
            - Path=/test/{segment}
          filters:
            - SetPath=/{segment}



  application:
    name: gateway

server:
  port: 9000


management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

工程源码: springGateway

标签:return,String,Spring,final,token,实例,claims,response,Gateway
来源: https://www.cnblogs.com/wenshun/p/16469569.html