gateway网关细粒度至 指定接口 免鉴权实现

前言

在现在微服务架构中,我们常常将鉴权的工作交由 网关 来处理。那么类似于 登录、注册 的免鉴权接口,我们要如何实现呢?

鉴权方案

JWT 介绍

首先,我们先简单了解一下 JWT 的原理。

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名

  • 头部(Header)

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个 JSON 对象。

{"typ":"JWT","alg":"HS256"}

在头部指明了签名算法是 HS256 算法。 我们进行 BASE64编码 能得到如下结果:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
  • 载荷(playload)

载荷就是存放有效信息的地方,这里会在 生成 JWT时将 信息进行编码,可以将我们可能需要用到的东西存在这里面,如 userIdusername 等,将来可以在解析后使用

定义一个 payload:

{"sub":"1234567890","name":"lucy","admin":true,"age":18}

然后将其进行 base64 编码,得到Jwt的第二部分。

base64 是双向的,所以前两部分是可以进行解密的

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iml0bGlscyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==
  • 签证(signature)

jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)

payload (base64后的)

secret

这个部分需要 base64加密 后的 header base64 加密后的payload使用 . 连接组成的字符串,然后通过 header 中声明的 加密方式 进行加盐 secret 组合加密,然后就构成了 jwt 的第三部分。

hs256("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iml0bGlscyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==",secret)

将这三部分用   ' . '  (点) 连接成一个完整的字符串,构成了最终的 JWT

JTdCJTIydHlwJTIyJTNBJTIySldUJTIyJTJDJTIyYWxnJTIyJTNBJTIySFMyNTYlMjIlN0Q=.JTdCJTIyc3ViJTIyJTNBJTIyMTIzNDU2Nzg5MCUyMiUyQyUyMm5hbWUlMjIlM0ElMjJqYWNrJTIyJTJDJTIyYWRtaW4lMjIlM0F0cnVlJTdE.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

一般鉴权流程

微服务鉴权流程

其实微服务的鉴权流程没有太大差别,只是 鉴权的位置 不一样了。

一般我们会在 网关 gateway 里完成 验证、解析、处理等操作

所以用户的请求也都会向这里发送

免鉴权方案

网关代码示例

我们一般使用网关的 全局过滤器(Filter) 来完成这个工作,上代码

/**
 * jwt 认证过滤器
 *
 * @author durance
 */
@Component
@Slf4j
public class AuthorizeFilter implements GlobalFilter, Ordered {

	public static final String AUTHORIZE_TOKEN = "token";

	private Set<String> matchersCheck;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();
		ServerHttpResponse response = exchange.getResponse();
		// 进行请求路径判度,放行不需要认证的接口
		String path = request.getURI().getPath();
		// 拿到jwt的值
		String jwt = request.getHeaders().getFirst(AUTHORIZE_TOKEN);
		// token不为空时,优先解析token
		if (!StringUtils.isEmpty(jwt)) {
			// 如果不为空则进行效验
			try {
				// 效验jwt正确性,如果错误会抛出异常
				JwtUtil.parseJwt(jwt);
				// 解析jwt拿到jwt的载荷跟其余信息
				Claims claims = JwtUtil.parseJwt(jwt);
				Integer userId = (Integer) claims.get("userId");
				ServerHttpRequest build = exchange.getRequest().mutate().header("userId", userId.toString()).build();
				exchange = exchange.mutate().request(build).build();
				// 只要token解析正确就进行返回
				return chain.filter(exchange);
			} catch (Exception e) {
				// 出现异常可能是token过期或恶意攻击
				log.warn("jwt解析错误:{}", e.getMessage());
				// 如果解析错误(过期了也会解析错误),判断是否为免鉴权接口
				if (verifyNoAuthentication(path)){
					return chain.filter(exchange);
				}
				response.setStatusCode(HttpStatus.UNAUTHORIZED);
				return response.setComplete();
			}
		}
		// 没有token,则判断是否为免鉴权接口
		if(verifyNoAuthentication(path)){
			return chain.filter(exchange);
		}
		response.setStatusCode(HttpStatus.UNAUTHORIZED);
		return response.setComplete();
	}

	/**
	 * 判断接口是否为免鉴权接口
	 *
	 * @param path 请求接口
	 * @return 判断结果
	 */
	private boolean verifyNoAuthentication(String path){
		//将不需要认证的接口存储在 Set中,减少判断是否为非鉴权接口的时间复杂度
		if(CollectionUtils.isEmpty(matchersCheck)){
			matchersCheck = new HashSet<>();
			String[] matchers = JwtProperties.matchers;
			matchersCheck.addAll(Arrays.asList(matchers));
		}
		// 为非鉴权接口直接跳过, 否则返回未认证
		return matchersCheck.contains(path);
	}

	@Override
	public int getOrder() {
		// 过滤器优先级,越小越先
		return -1;
	}

}

接口免鉴权操作流程

可以根据上诉代码来阅读逻辑:

  • 这里我们使用一个配置类 来记录要免鉴权的接口
  • 使用字符串数组 matchers 来定义多个不需要鉴权的接口
  • 这里我们使用系统提供的方法 request.getURI().getPath() 统就能得到用户请求的接口
  • 使用 Set 来记录免鉴权的接口,减低判断的 时间复杂度 O(1) 
  • 如果传入 token 不为空,则进行效验,返回对应的结果
  • 如果传入 token 为空,则判断是否为 免鉴权接口,是则通过

由此完成鉴权的操作

鉴权后处理

因为 JWT 不光光能够用来验证,我们也需要拿到验证之后的一些结果。

也就是当时在生成 JWT时存入的载荷。我们也需要将它解析后 再次存入请求头,其它的服务才能够正确地拿到对应的信息。如 userId 等,可以见上诉代码示例

优化

除去运行效率的优化,那么代码是否能够更优雅的去获取免鉴权的接口呢?

既然是 配置类,如果我们是将免鉴权的接口直接 硬编码 在上面那也太 low 了。

所以,我们可以使用 SpringBoot 提供的   @ConfigurationProperties(建议) 或者 @Value 来把免鉴权的接口获取进来。减低耦合程度。

当然,这些配置我们都可以在 nacos配置中心 或其他配置中心 里进行配置,方便管理。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

durancer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值