up:: SpringCloud之Zuul安全性增强

Gateway与Zuul的对比

spring-cloud-Gatewayspring-cloud的一个子项目。而zuul则是netflix公司的项目,只是spring将zuul集成在spring-cloud中使用而已。 因为zuul2.0连续跳票和zuul1的性能表现不是很理想,所以催生了spring团队开发了Gateway项目。

微服务网关Zuul和Gateway的区别 - 探歌 - 博客园

开发Gateway

开发流程:

新建项目

由于gateway与springMVC的依赖不相容,所以我们不能在此项目下新建子模块,只能重新创建Spring项目:创建SpringBoot项目

springcloud项目gateway与mvc包冲突问题解决_迫壳的博客-CSDN博客

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.12.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.imooc</groupId>
    <artifactId>cloud-mall-spring-cloud-gateway-practice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>cloud mall spring cloud gateway</description>
 
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR5</spring-cloud.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.14.0</version>
        </dependency>
    </dependencies>
 
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
 
    <repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <layout>default</layout>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
    </repositories>
 
    <pluginRepositories>
        <pluginRepository>
            <id>alimaven</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </pluginRepository>
    </pluginRepositories>
 
</project>

说明; 请先复习JWT的原理

开发网关过滤器(重要!!!)

package com.imooc.cloudmallspringcloudgatewaypractice.filter;
 
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.imooc.cloudmallspringcloudgatewaypractice.model.User;
import java.util.Objects;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
 
/**
 * 描述:     网管鉴权过滤器
 */
@Component
public class AuthorizationFilter extends AbstractGatewayFilterFactory {
 
    private User currentUser = new User();
    public static final String JWT_KEY = "imooc-mall";
    public static final String USER_ID = "user_id";
    public static final String USER_NAME = "user_name";
    public static final String USER_ROLE = "user_role";
    public static final Integer ADMIN_ROLE = 2;
 
 
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            //1 对于不想对外公开的接口,拦截住
            ServerHttpRequest request = exchange.getRequest();
            String uri = request.getURI().toString();
            if (uri.contains("/getUser")
                    || uri.contains("/checkAdminRole")
                    || uri.contains("/product/updateStock")
                    || uri.contains("/product/detailForFeign")) {
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.FORBIDDEN);
                return response.setComplete();
            }
            //2 不应该拦截的接口,要放行
            if (uri.contains("image")
                    || uri.contains("pay")
                    || uri.contains("qrcode")
                    || uri.contains("login")
                    || uri.contains("adminLogin")) {
                return chain.filter(exchange);
            }
            //3 需要鉴权的接口,要鉴权
            if (uri.contains("admin")
                    || uri.contains("cart")
                    || uri.contains("order")
                    || uri.contains("user/update")) {
                request = exchange.getRequest();
                ServerHttpResponse response = exchange.getResponse();
 
                uri = request.getURI().getPath();
                String method = request.getMethodValue();
 
                // 2.1.从AuthenticationFilter中获取token
                String key = "jwt_token";
                if (!request.getHeaders().containsKey(key)) {
                    //如果header里没有jwt_token,就直接拦住
                    response.setStatusCode(HttpStatus.FORBIDDEN);
                    return response.setComplete();
                }
 
                String token = Objects.requireNonNull(request.getHeaders().get(key)).get(0);
                Algorithm algorithm = Algorithm.HMAC256(JWT_KEY);
                JWTVerifier verifier = JWT.require(algorithm).build();
                try {
                    DecodedJWT jwt = verifier.verify(token);
                    currentUser.setId(jwt.getClaim(USER_ID).asInt());
                    Integer role = jwt.getClaim(USER_ROLE).asInt();
                    if (uri.contains("admin") && role != ADMIN_ROLE) {
                        return needAdmin(exchange);
                    }
                    currentUser.setRole(role);
                    currentUser.setUsername(jwt.getClaim(USER_NAME).asString());
                } catch (Exception e) {
                    //未通过校验
                    return needLogin(exchange);
                }
                //把用户信息传递个后端服务
                ServerHttpRequest host = exchange.getRequest().mutate().header(USER_ID, new String[]{String.valueOf(currentUser.getId())})
                        .header(USER_ROLE, new String[]{String.valueOf(currentUser.getRole())}).header(USER_NAME, new String[]{String.valueOf(currentUser.getUsername())}).build();
                ServerWebExchange build = exchange.mutate().request(host).build();
                return chain.filter(build);
            }
            return chain.filter(exchange);
        };
    }
 
 
    private Mono<Void> needLogin(ServerWebExchange exchange) {
        ServerHttpResponse response;
        response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
        String msg = "{\n"
                + "    \"status\": 10007,\n"
                + "    \"msg\": \"need right jwt_token in header\",\n"
                + "    \"data\": null\n"
                + "}";
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(msg.getBytes());
        return response.writeWith(Mono.just(bodyDataBuffer));
    }
 
    private Mono<Void> needAdmin(ServerWebExchange exchange) {
        ServerHttpResponse response;
        response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
        String msg = "{\n"
                + "    \"status\": 10007,\n"
                + "    \"msg\": \"need admin jwt_token in header\",\n"
                + "    \"data\": null\n"
                + "}";
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(msg.getBytes());
        return response.writeWith(Mono.just(bodyDataBuffer));
    }
}

说明: 学习前一定要先复习下前面所学的session升级为JWT校验

查看内部:

关于Token,前面遇到过项目实战生成JWT

这里是把信息从jwt解码存回对象,而前面是将对象里的信息存到jwt中。。。

参考下jwt生成token和验证token以及获取playload的数据,实现token拦截_心宽路阔走天下的博客-CSDN博客_jwt获取payload

配置文件

server.port=8083
 
logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}
 
eureka.client.service-url.defaultZone=http://localhost:8000/eureka/
 
spring.application.name=cloud-mall-spring-cloud-gateway-practice
 
spring.cloud.gateway.routes[0].id=user-route
spring.cloud.gateway.routes[0].uri=lb://cloud-mall-user
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[0].filters[1].name=AuthorizationFilter
 
spring.cloud.gateway.routes[1].id=category-product-route
spring.cloud.3gateway.routes[1].uri=lb://cloud-mall-category-product
spring.cloud.gateway.routes[1].predicates[0]=Path=/category-product/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[1].filters[1].name=AuthorizationFilter
 
spring.cloud.gateway.routes[2].id=cart-order-route
spring.cloud.gateway.routes[2].uri=lb://cloud-mall-cart-order
spring.cloud.gateway.routes[2].predicates[0]=Path=/cart-order/**
spring.cloud.gateway.routes[2].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[2].filters[1].name=AuthorizationFilter

说明: SpringCloud:Gateway之StripPrefix使用_yololee_的博客-CSDN博客