当前位置: 首页 > news >正文

物流项目第三期(统一网关、工厂模式运用)

前两期:

物流项目第一期(登录业务)-CSDN博客

物流项目第二期(用户端登录与双token三验证)-CSDN博客 

为什么要有网关?

通过前面的课程我们已经完成了四个端的登录,但是我们并没有对登录后的请求中的token做校验,假设要做的话,是不是四个端每个端都需要实现?这样类似的代码就会重复,显然这并不是一个好的设计。

所以,通过Spring Cloud Gateway就可以接管所有来自客户端的请求,在此处做校验是最为合适的选择。

实际上,在网关中除了需要对token是否有效进行校验外,还需要对用户的是否有权限进行校验,比如说:司机不能登录到快递员端,快递员不能登录到司机端等。

自定义过滤器配置 

spring:gateway: routes:- id: sl-express-ms-web-manager #路由标识,需要唯一uri: lb://sl-express-ms-web-manager #最终请求转发的微服务,指定的是微服务名,并开启负载均衡predicates: #断言- Path=/manager/**filters: #配置过滤器- ManagerToken #自定义局部过滤器- StripPrefix=1 #去掉第一个路径,例如:/manager/user/login -转发到下游微服务-> /user/login- AddRequestHeader=X-Request-From, sl-express-gateway  #转发到下游微服务携带的请求头,增强安全性

自定义过滤器

在过滤器中主要实现以下几个功能:

  • 白名单放行
  • 请求头中的token是否有效
  • 权限是否匹配(校验角色)
  • 向下游微服务传递解析token的数据以及token值
/*** 管理端token校验的过滤器*/
@Component
public class ManagerTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Resourceprivate MyConfig myConfig;@Resourceprivate TokenCheckService tokenCheckService;@Value("${role.manager}")private List<Long> managerRoleIds; //获取配置文件中的管理员角色id@Overridepublic GatewayFilter apply(Object config) {return (exchange, chain) -> {//1. 校验请求路径,如果是白名单,直接放行String path = exchange.getRequest().getPath().toString();if (StrUtil.startWithAny(path, this.myConfig.getNoAuthPaths())) {//直接放行return chain.filter(exchange);}//2. 获取请求头中的token,进行校验,如果为空或校验失效,响应401String token = exchange.getRequest().getHeaders().getFirst(Constants.GATEWAY.AUTHORIZATION);if (StrUtil.isEmpty(token)) {//设置响应状态为401exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//拦截请求return exchange.getResponse().setComplete();}//校验tokenAuthUserInfoDTO authUserInfoDTO = null;try {authUserInfoDTO = this.tokenCheckService.parserToken(token);} catch (Exception e) {//token不可用,不做处理}if (ObjectUtil.isEmpty(authUserInfoDTO)) {//token不可用,设置响应状态为401exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//拦截请求return exchange.getResponse().setComplete();}//3. 校验权限,如果是非管理员不能登录AuthTemplate authTemplate = AuthTemplateFactory.get(token);//3.1 获取用户拥有的角色id列表List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfoDTO.getUserId()).getData();//3.2 取交集,判断用户拥有的角色是否与预定的角色列表是否有交集Collection<Long> intersection = CollUtil.intersection(roleIds, this.managerRoleIds);if (CollUtil.isEmpty(intersection)) {//无交集,说明没有权限,设置响应状态码为400exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}//4. 校验通过,向下游传递用户信息和tokenexchange.getRequest().mutate().header(Constants.GATEWAY.USERINFO, JSONUtil.toJsonStr(authUserInfoDTO));exchange.getRequest().mutate().header(Constants.GATEWAY.TOKEN, token);//4.1 校验通过放行return chain.filter(exchange);};}}

代码优化

前面虽然已经实现了管理端的校验工作,同学们可以思考这样一个问题,如果实现司机端的校验,该怎么做呢?是不是意识到问题了?代码逻辑是不是很像?怎么办?没错,抽取共用代码!

优化一 

将校验逻辑抽取到独立的类中,这样在每个端的GatewayFilterFactory中就可以共用了。

package com.sl.gateway.filter;import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.itheima.auth.factory.AuthTemplateFactory;
import com.itheima.auth.sdk.AuthTemplate;
import com.itheima.auth.sdk.dto.AuthUserInfoDTO;
import com.itheima.auth.sdk.service.TokenCheckService;
import com.sl.gateway.config.MyConfig;
import com.sl.transport.common.constant.Constants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;@Component
public class TokenGatewayFilter implements GatewayFilter {@Resourceprivate MyConfig myConfig;@Resourceprivate TokenCheckService tokenCheckService;@Value("${role.manager}")private List<Long> managerRoleIds; //获取配置文件中的管理员角色id@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1. 校验请求路径,如果是白名单,直接放行String path = exchange.getRequest().getPath().toString();if (StrUtil.startWithAny(path, this.myConfig.getNoAuthPaths())) {//直接放行return chain.filter(exchange);}//2. 获取请求头中的token,进行校验,如果为空或校验失效,响应401String token = exchange.getRequest().getHeaders().getFirst(Constants.GATEWAY.AUTHORIZATION);if (StrUtil.isEmpty(token)) {//设置响应状态为401exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//拦截请求return exchange.getResponse().setComplete();}//校验tokenAuthUserInfoDTO authUserInfoDTO = null;try {authUserInfoDTO = this.tokenCheckService.parserToken(token);} catch (Exception e) {//token不可用,不做处理}if (ObjectUtil.isEmpty(authUserInfoDTO)) {//token不可用,设置响应状态为401exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//拦截请求return exchange.getResponse().setComplete();}//3. 校验权限,如果是非管理员不能登录AuthTemplate authTemplate = AuthTemplateFactory.get(token);//3.1 获取用户拥有的角色id列表List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfoDTO.getUserId()).getData();//3.2 取交集,判断用户拥有的角色是否与预定的角色列表是否有交集Collection<Long> intersection = CollUtil.intersection(roleIds, this.managerRoleIds);if (CollUtil.isEmpty(intersection)) {//无交集,说明没有权限,设置响应状态码为400exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}//4. 校验通过,向下游传递用户信息和tokenexchange.getRequest().mutate().header(Constants.GATEWAY.USERINFO, JSONUtil.toJsonStr(authUserInfoDTO));exchange.getRequest().mutate().header(Constants.GATEWAY.TOKEN, token);//4.1 校验通过放行return chain.filter(exchange);}
}

使用

package com.sl.gateway.filter;import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;import javax.annotation.Resource;/*** 管理端token校验的过滤器*/
@Component
public class ManagerTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Resourceprivate TokenGatewayFilter tokenGatewayFilter;@Overridepublic GatewayFilter apply(Object config) {return this.tokenGatewayFilter;}}

优化二

通过前面的优化一已经将过滤器抽取到一个独立的类中,这样就可以在多个过滤器工厂中通用了,但也是存在一些问题的,例如:

  • 不同的终端校验的角色id是不同的
  • 用户端是不需要校验角色的
  • 用户端校验token的逻辑与其他三端是不一样的
  • 用户端请求头中的token参数名与其他三端也不一样

基于这些问题,就能够意识到,单纯的抽取代码是不够的,需要进一步的优化。想一想,该怎么优化呢?

优化的思路就是,在TokenGatewayFilter中保留四端通用的逻辑,不同的逻辑抽取到接口中,由四端具体的实现。

package com.sl.gateway.filter;import com.itheima.auth.sdk.dto.AuthUserInfoDTO;
import com.sl.transport.common.constant.Constants;/*** 鉴权业务的回调,具体逻辑由 GatewayFilterFactory 具体完成*/
public interface AuthFilter {/*** 校验token** @param token 请求中的token* @return token中携带的数据*/AuthUserInfoDTO check(String token);/*** 鉴权** @param token        请求中的token* @param authUserInfo token中携带的数据* @param path         当前请求的路径* @return 是否通过*/Boolean auth(String token, AuthUserInfoDTO authUserInfo, String path);/*** 请求中携带token的名称** @return 头名称*/default String tokenHeaderName() {return Constants.GATEWAY.AUTHORIZATION;}}
package com.sl.gateway.filter;import cn.hutool.core.collection.CollUtil;
import com.itheima.auth.factory.AuthTemplateFactory;
import com.itheima.auth.sdk.AuthTemplate;
import com.itheima.auth.sdk.dto.AuthUserInfoDTO;
import com.itheima.auth.sdk.service.TokenCheckService;
import com.sl.gateway.config.MyConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;/*** 管理端token校验的过滤器*/
@Component
public class ManagerTokenGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> implements AuthFilter {@Resourceprivate MyConfig myConfig;@Resourceprivate TokenCheckService tokenCheckService;@Value("${role.manager}")private List<Long> managerRoleIds; //获取配置文件中的管理员角色id@Overridepublic GatewayFilter apply(Object config) {//由于要传递当前对象,所以不能采用spring注入的方式,这里采用手动构造对象的方式传入return new TokenGatewayFilter(this.myConfig, this);}@Overridepublic AuthUserInfoDTO check(String token) {try {return this.tokenCheckService.parserToken(token);} catch (Exception e) {//token不可用,不做处理}return null;}@Overridepublic Boolean auth(String token, AuthUserInfoDTO authUserInfo, String path) {//校验权限,如果是非管理员不能登录AuthTemplate authTemplate = AuthTemplateFactory.get(token);//获取用户拥有的角色id列表List<Long> roleIds = authTemplate.opsForRole().findRoleByUserId(authUserInfo.getUserId()).getData();//取交集,判断用户拥有的角色是否与预定的角色列表是否有交集Collection<Long> intersection = CollUtil.intersection(roleIds, this.managerRoleIds);return CollUtil.isNotEmpty(intersection);}
}
package com.sl.gateway.filter;import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.itheima.auth.sdk.dto.AuthUserInfoDTO;
import com.sl.gateway.config.MyConfig;
import com.sl.transport.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Slf4j
public class TokenGatewayFilter implements GatewayFilter {private AuthFilter authFilter;private MyConfig myConfig;public TokenGatewayFilter(MyConfig myConfig, AuthFilter authFilter) {this.myConfig = myConfig;this.authFilter = authFilter;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1. 校验请求路径,如果是白名单,直接放行String path = exchange.getRequest().getPath().toString();if (StrUtil.startWithAny(path, this.myConfig.getNoAuthPaths())) {//直接放行return chain.filter(exchange);}//2. 获取请求头中的token,进行校验,如果为空或校验失效,响应401String token = exchange.getRequest().getHeaders().getFirst(this.authFilter.tokenHeaderName());if (StrUtil.isEmpty(token)) {//设置响应状态为401exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//拦截请求return exchange.getResponse().setComplete();}//校验tokenAuthUserInfoDTO authUserInfoDTO = null;try {authUserInfoDTO = this.authFilter.check(token);} catch (Exception e) {//token不可用,不做处理}if (ObjectUtil.isEmpty(authUserInfoDTO)) {//token不可用,设置响应状态为401exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);//拦截请求return exchange.getResponse().setComplete();}//3. 校验权限Boolean auth = false;try {auth = this.authFilter.auth(token, authUserInfoDTO, path);} catch (Exception e) {log.error("鉴权失败, token = {}", token, e);}if (!auth) {//没有权限,设置响应状态码为400exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}//4. 校验通过,向下游传递用户信息和tokenexchange.getRequest().mutate().header(Constants.GATEWAY.USERINFO, JSONUtil.toJsonStr(authUserInfoDTO));exchange.getRequest().mutate().header(Constants.GATEWAY.TOKEN, token);//4.1 校验通过放行return chain.filter(exchange);}
}

相关文章:

  • 内网穿透:轻松实现外网访问本地服务
  • 101个α因子#8
  • 新凌印 4.2.0 | 国内短视频去水印下载~图集下载
  • 【数据结构】队列的完整实现
  • Brooks Polycold快速循环水蒸气冷冻泵客户使用手含电路图,适用于真空室应用
  • AI能源危机:人工智能发展与环境可持续性的矛盾与解决之道
  • 深入剖析Zynq AMP模式下CPU1中断响应机制:从原理到创新实践
  • addStretch 与addSpace的区别
  • base算法
  • 【YOLOs-CPP-图像分类部署】01-构建项目
  • bi报表是什么意思?如何制作一张bi报表?
  • 丝杆支撑座:机床生命周期的精度与效能
  • day 31
  • Linux(ubuntu18.04)下安装instant-ngp环境
  • 系统设计——项目设计经验总结1
  • 4.8 加密模块
  • 【动手学深度学习】1.1~1.2 机器学习及其关键组件
  • BioID技术:探索蛋白质相互作用的新方法
  • Vue3——Pinia
  • 数据分析预备篇---Pandas的DataFrame的更多操作
  • 珍岛网站建设/关键词搜索排名工具
  • wordpress哪个版本 最快/seo优化排名易下拉用法
  • 正版视频素材网站/汕头网站建设技术外包
  • wordpress实现同步登录/seo技术最新黑帽
  • 嘉兴seo/营口seo
  • 全站仪为什么要建站/百度竞价托管费用