SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权 (7)

12、新建AuthGlobalFilter.java全局过滤器,解析用户请求信息,将用户信息及租户信息放在请求的Header中,这样后续服务就不需要解析JWT令牌了,可以直接从请求的Header中获取到用户和租户信息。

package com.gitegg.gateway.filter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.ParseException; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import com.gitegg.platform.base.constant.AuthConstant; import com.nimbusds.jose.JWSObject; import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Mono; /** * 将登录用户的JWT转化成用户信息的全局过滤器 */ @Slf4j @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { /** * 是否开启租户模式 */ @Value(("${tenant.enable}")) private Boolean enable; @Override public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String tenantId = exchange.getRequest().getHeaders().getFirst(AuthConstant.TENANT_ID); String token = exchange.getRequest().getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER); if (StrUtil.isEmpty(tenantId) && StrUtil.isEmpty(token)) { return chain.filter(exchange); } Map<string, string=""> addHeaders = new HashMap<>(); // 如果系统配置已开启租户模式,设置tenantId if (enable && StrUtil.isEmpty(tenantId)) { addHeaders.put(AuthConstant.TENANT_ID, tenantId); } if (!StrUtil.isEmpty(token)) { try { //从token中解析用户信息并设置到Header中去 String realToken = token.replace("Bearer ", ""); JWSObject jwsObject = JWSObject.parse(realToken); String userStr = jwsObject.getPayload().toString(); log.info("AuthGlobalFilter.filter() User:{}", userStr); addHeaders.put(AuthConstant.HEADER_USER, URLEncoder.encode(userStr, "UTF-8")); } catch (ParseException | UnsupportedEncodingException e) { e.printStackTrace(); } } Consumer<httpheaders> httpHeaders = httpHeader -> { addHeaders.forEach((k, v) -> { httpHeader.set(k, v); }); }; ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders).build(); exchange = exchange.mutate().request(request).build(); return chain.filter(exchange); } @Override public int getOrder() { return 0; } }

13、在Nacos中添加权限相关配置信息:

spring: jackson: time-zone: Asia/Shanghai date-format: yyyy-MM-dd HH:mm:ss security: oauth2: resourceserver: jwt: jwk-set-uri: 'http://127.0.0.1/gitegg-oauth/oauth/public_key' # 多租户配置 tenant: # 是否开启租户模式 enable: true # 需要排除的多租户的表 exclusionTable: - "t_sys_district" - "t_sys_tenant" - "t_sys_role" - "t_sys_resource" - "t_sys_role_resource" # 租户字段名称 column: tenant_id # 网关放行白名单,配置白名单路径 white-list: urls: - "/gitegg-oauth/oauth/public_key" 二、注销登录使JWT失效

因为JWT是无状态的且不在服务端储存,所以,当系统在执行退出登录时就无法使JWT失效,我们有两种方式拒绝注销登录后的JWT:

JWT白名单:每次登录成功就将JWT存到缓存中,缓存有效期和JWT有效期保持一致,注销登录就将JWT从缓存中移出。Gateway每次认证授权先从缓存JWT白名单中获取是否存在该JWT,存在则继续校验,不存在则拒绝访问。

JWT黑名单:每当注销登录时,将JWT存到缓存中,解析JWT的到期时间,将缓存过期时间设置为和JWT一致。Gateway每次认证授权先从缓存中获取JWT是否存在于黑名单中,存在则拒绝访问,不存在则继续校验。

不管是白名单还是黑名单,实现方式的原理都基本一致,就是将JWT先存放到缓存,再根据不同的状态进行判断JWT是否有效,下面是两种方式的优缺点分析:

黑名单功能分析:优点是存放到缓存的数据量将小于白名单方式存放的数据量,缺点是无法获知当前签发了多少JWT,当前在线多少登录用户。

白名单功能分析:优点是当我们需要统计在线用户的时候,白名单方式可以近似的获取到当前系统登录用户,可以扩展踢出登录用户的功能。缺点是数据存储量大,且大量token存在缓存中需要进行校验,万一被攻击会导致大量信息泄露。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzjgjz.html