微服务整合gateway

1.概述简介

官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

     简单点说gateway就是api路由的统一管理,在请求到达服务之前网关可以替我们做很多事情,诸如:过滤,权限,熔断,降级,限流,

    SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

    (在Spring Boot应用程序中,我们可以使用网关gateway来实现请求路由和负载均衡。然而,在使用网关gateway时,可能会出现无法下载文件的问题  我就碰到这个坑 流异常关闭---multipart/form-data request failed. Stream ended unexpected,还有一个坑就是上传文件 不行,因为springboot集成gateway后协议走的netty协议了,因为内部协议的关系可能存在的问题,就把文件服务独立出来了当时用的七彩牛 也有可能是平台问题,只做了独立服务处处理不走网关了。。。。。。才解决)

直接上图简洁点:

工作原理:

上代码:

1.依赖

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--        注册发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--        配置管理-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>weiyu.saas.system</groupId>
            <artifactId>weiyu-saas-system-common</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>

2.创建一个httpLogBean用来封装响应 自己定义即可

@Data
@NoArgsConstructor
@AllArgsConstructor
public class HttpLogBean {
//    URI、Method、QueryString、POST请求的Body、响应信息和来源IP
    private String uri;
    private String method;
    private String params;
    private String headers;
    private String body;
    private String response;
    private String requestId;
}

3.添加过滤器 权限过滤--LogAndAuthFilter 

@Component
@Slf4j
public class LogAndAuthFilter implements GlobalFilter, Ordered {

    private final LogService logService;
    private final AuthService authService;

    public LogAndAuthFilter(LogService logService, AuthService authService){
        this.logService = logService;
        this.authService = authService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (exchange.getRequest().getPath().toString().contains("/export")){
            log.info("跳出导出文件时候改变流");//流中断关闭解决
            return chain.filter(exchange);
        }
        HttpLogBean httpLogBean = new HttpLogBean();
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse serverHttpResponse = exchange.getResponse();

        String requestId = UniqueIdUtil.newUniqueId("requestId");

        String method = serverHttpRequest.getMethodValue().toUpperCase();
        httpLogBean.setMethod(method);
        httpLogBean.setUri(serverHttpRequest.getURI().toString());
        httpLogBean.setRequestId(requestId);

        MultiValueMap<String, String> query = serverHttpRequest.getQueryParams();
        Map<String, String> params = new HashMap<>();
        query.forEach((k, v) -> {
            params.put(k, v.get(0));
        });

        httpLogBean.setParams(JSON.toJSONString(params));
        if("POST".equals(method)) {
            DataBuffer body = (DataBuffer) exchange.getAttributes().get("cachedRequestBody");
            httpLogBean.setBody(body.toString());
        }
        //权限校验
        if (authService.isIgnoreAuth(serverHttpRequest)){
            return rute(exchange,chain,httpLogBean);
        }else{
            ResponseResult b = authService.checkToken(serverHttpRequest);
            if(b.getCode()!= StatusCode.SUCCESS.code) {
                String resp = JSON.toJSONString(b);
                httpLogBean.setResponse(resp);
                logService.logCreate(httpLogBean);
                DataBuffer bodyDataBuffer = serverHttpResponse.bufferFactory().wrap(resp.getBytes());
                serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                return serverHttpResponse.writeWith(Mono.just(bodyDataBuffer));
            }
        }
        String account = authService.getAccount(serverHttpRequest); //获取账号 
        ServerHttpRequest newServerHttpRequest = serverHttpRequest.mutate().headers(new Consumer<HttpHeaders>() {
            @Override
            public void accept(HttpHeaders httpHeaders) {
                httpHeaders.remove("Authorization");
                if (account!=null){
                    httpHeaders.add("user", account);
                }
                httpHeaders.add("requestId", requestId);
            }
        }).build();
        ServerWebExchange build = exchange.mutate().request(newServerHttpRequest).build();
        return rute(build,chain,httpLogBean);
    }

    @Override
    public int getOrder() {
        return -1;//order顺序不能错 否者启动报错这里为-1
    }


    public Mono<Void> rute(ServerWebExchange exchange, GatewayFilterChain chain,HttpLogBean httpLogBean){
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(serverHttpResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        DataBufferUtils.release(dataBuffer);
                        String resp = new String(content, Charset.forName("UTF-8"));
                        System.out.println("===============返回");
                        httpLogBean.setResponse(resp);
                        logService.logCreate(httpLogBean);
                        byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
                        return bufferFactory.wrap(uppedContent);
                    }));
                }else {
                    System.out.println("1111111111111111");
                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }
}
Component
public class CachePostBodyFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String method = serverHttpRequest.getMethodValue();
        if("POST".equalsIgnoreCase(method)) {
                ServerRequest serverRequest = new DefaultServerRequest(exchange);
            Mono<String> bodyToMono = serverRequest.bodyToMono(String.class);
                return bodyToMono.flatMap(body -> {

                    NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                    DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(body.getBytes());
//这里要注意 put 进去的buffer 很多地方都是PooledDataBuffer ,但是我每次走到网关就报我的buffer是有问题 这个问题追溯源码好久才定位到 是应该要放DataBuffer类型而不是PooledDataBuffer 坑死我了
                    exchange.getAttributes().put("cachedRequestBody", bodyDataBuffer);
                    ServerHttpRequest newRequest = new ServerHttpRequestDecorator(serverHttpRequest) {
                        @Override
                        public HttpHeaders getHeaders() {
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                            return httpHeaders;
                        }
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return Flux.just(bodyDataBuffer);
                        }
                    };
                    return chain.filter(exchange.mutate().request(newRequest).build());
                });
            }
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return -2;//这里order是-2
    }

4.配置白名单:

package weiyu.saas.system.gateway.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
@ConfigurationProperties(prefix = "system")
public class TokenWhiteListConfig {
    // 配置文件使用list接收
    private List<String> whiteList;

    public List<String> getWhiteList() {
        return whiteList;
    }

    public void setWhiteList(List<String> whiteList) {
        this.whiteList = whiteList;
    }
}


//bootstrap.yml 配置
# 访问白名单
system:
  whiteList:
    - /aa/login/loginTest
    - /allauth/login/loginTest
 

5.回调函数

@RestController
public class FallbackController {
    @RequestMapping("/fallback")
    public ResponseResult defaultFallback() {
        return new ResponseResult(StatusCode.SERVER_ERROR.code,"服务异常","");
    }
}

最后启动类加@EnableDiscoveryClient注解  大功告成   注意:如果请求体没有参数 只能get请求

,否者网关那边是不会转发过来的

### 微服务架构下 Apache Shiro 的整合方案 #### 背景概述 微服务架构因其灵活性和可扩展性被广泛应用于现代软件开发中。然而,这种架构也带来了新的安全挑战,尤其是在身份验证和授权方面[^1]。为了应对这些挑战,可以利用 Apache Shiro 这一强大的安全框架。 #### 集成方式分析 在微服务环境中,Shiro 可以通过多种方式进行集成,其中一种常见的方式是在每个微服务中引入 Shiro 拦截器,从而实现对请求的身份验证和授权控制[^2]。这种方式能够确保各微服务之间的权限一致性,并可通过消息队列等工具完成跨服务的权限校验结果传递。 以下是具体的整合步骤和技术细节: --- #### 1. 单点登录 (SSO) 支持 由于微服务通常由多个独立的服务组成,因此需要设计一套统一的身份认证机制。可以通过网关层集中处理用户的登录状态管理,再将认证后的令牌分发至下游服务进行进一步的权限校验[^3]。 ```java // SSO 登录逻辑示例 public class SsoLoginService { public String login(String username, String password) throws AuthenticationException { Subject currentUser = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { currentUser.login(token); // 执行登录操作 return "Bearer " + generateJwtToken(currentUser.getPrincipal().toString()); } catch (AuthenticationException e) { throw e; } } private String generateJwtToken(String userId) { // 使用 JWT 工具生成 Token return Jwts.builder() .setSubject(userId) .signWith(SignatureAlgorithm.HS512, "secretKey".getBytes()) .compact(); } } ``` 上述代码展示了基于 Shiro 和 JWT 实现的简单登录流程,适用于微服务场景下的单点登录支持。 --- #### 2. 权限拦截与校验 在每个微服务内部,需配置 Shiro 的过滤器链来拦截 HTTP 请求并对用户角色或权限进行检查。这一步骤对于保护敏感资源至关重要。 ```properties # shiro-filter.properties 文件定义 [main] authc = org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter [urls] /api/admin/** = authc, roles["admin"] /api/user/** = authc, perms["user:view"] ``` 以上配置文件说明了如何设置特定 URL 下的角色和权限约束条件。当某个未满足要求的请求到达时,Shiro 将自动返回相应的错误响应。 --- #### 3. 数据库驱动的 Realm 设计 为了让 Shiro 更好地适配复杂的业务需求,建议自定义 `Realm` 类从数据库加载用户信息以及其关联的权限数据。 ```java import org.apache.shiro.realm.jdbc.JdbcRealm; public class CustomJdbcRealm extends JdbcRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String)principals.fromRealm(getName()).iterator().next(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<String> roles = getRolesByUsername(username); Set<String> permissions = getPermissionsByRoleNames(roles); info.setRoles(new HashSet<>(roles)); info.setStringPermissions(permissions); return info; } private List<String> getRolesByUsername(String username){ // 查询数据库获取该用户名对应的所有角色列表 } private Set<String> getPermissionsByRoleNames(List<String> roleNames){ // 根据角色名称查询对应的权限集合 } } ``` 此部分实现了动态读取存储于关系型数据库中的用户及其所属角色/权限映射表结构的功能。 --- #### 4. 网关层面的安全增强 考虑到实际生产环境可能涉及多套异构子系统间的交互协作情况,则有必要借助 API Gateway 统筹全局范围内的安全性策略实施工作。例如,在 Spring Cloud Gateway 中加入如下所示的相关预处理器组件即可达成目标效果: ```yaml spring: cloud: gateway: routes: - id: service-a-route uri: lb://service-a predicates: - Path=/api/service-a/** filters: - StripPrefix=1 - name: AddRequestHeader args: header_name: X-Shiro-Token header_value: ${token} security: oauth2: resource: jwt: key-value: | -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- ``` 此处片段体现了如何结合 OAuth2/JWT 方案构建起更加健壮可靠的防护体系。 --- ### 总结 综上所述,Apache Shiro 是一款非常适合用来解决微服务环境下细粒度访问控制难题的技术手段之一。它不仅提供了丰富的内置特性供开发者快速搭建基础框架原型之用;同时也允许高度定制化的二次开发能力去迎合不同企业的个性化诉求特点[^1]. ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值