Spring Cloud实现权限管理(网关+jwt版)

Spring Cloud实现权限管理(网关+jwt版)

首先要想一个问题:为什么微服务不能像普通的Spring Boot项目一样鉴权?其实并不是不能,而是不适合。 在微服务架构中使用 Spring Security + RBAC/JET 时,会面临 "重复鉴权" 的核心痛点。下面从技术原理到解决方案完整解析这个问题。

问题本质:为什么会有重复鉴权?

传统单体架构流程

flowchart LR A[客户端] --> B[单体应用] B --> C[统一鉴权] --> D[执行业务]

所有权限校验集中在一个应用内完成 • 用户登录后,Session或Token在单应用中全局有效

微服务架构下的流程

flowchart LR A[客户端] --> B[API Gateway] B --> C[服务A] --> D[鉴权] B --> E[服务B] --> F[鉴权] B --> G[服务C] --> H[鉴权]

每个微服务都需要独立完成:

  1. 解析Token
  2. 查询数据库验证权限
  3. 构建SecurityContext

假设有3个服务链式调用:

客户端 → 网关 → 服务A → 服务B → 服务C 每个服务都重复: 查询用户数据(1次DB查询) 查询权限数据(1次DB查询) ​​总查询次数 = 3服务 × 2查询 = 6次​ • 在服务交叉的时候,导致 多次数据库查询重复计算,使数据库压力倍增和网络开销叠加。

(1)创建 JWT 工具类

在util模块下创建JWT 工具类

java 复制代码
@Component
public class JwtTokenUtil {
    // 密钥,用于签名和验证 JWT,应妥善保管
    @Value("${jwt.secret}")
    private String secret;
    // JWT 的过期时间,这里设置为 10 小时
    @Value("${jwt.expiration}")
    private Long expiration;

    // 根据用户详细信息生成 JWT
    public String generateToken(SysRoleNameUserId sysRoleNameUserId) {
        //自定义的声明
        Map<String, Object> claims = new HashMap<>();
        //鉴权所需的权限角色
        claims.put("identities",sysRoleNameUserId.getRoleNames());
        return createToken(claims, String.valueOf(sysRoleNameUserId.getUserId()));
    }

    // 创建 JWT 的具体方法
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)//自定义的声明
                .setSubject(subject)//存的用户id
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    //解析jwt
    public Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secret)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

JWT 的密钥(jwt.secret)应通过配置中心(如 Nacos)或环境变量注入,避免硬编码。这里选择环境变量注入: 在util模块下的application.yml配置文件里面指定

yml 复制代码
jwt:
  secret: your-secret-key-here-must-be-at-least-256-bits-long
  expiration: 1000 * 60 * 60 * 10 # 10 hour

(2)创建JwtFilter

在gateway网关模块创建JwtFilter过滤器来验证并解析jwt,从而获取权限信息

java 复制代码
@Component
public class JwtFilter implements GlobalFilter, Ordered {

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        List<String> authHeader = headers.get("Authorization");
        if (authHeader != null && authHeader.get(0).startsWith("Bearer ")) {
            String token = authHeader.get(0).substring(7);
            // 验证 JWT 并提取角色信息
            Claims claims = null;
            try{
                claims = jwtTokenUtil.extractAllClaims(token);
            }catch (IllegalArgumentException e) {
                //解析 JWT 时发生其他错误
                System.out.println("解析 token 时发生其他错误");
            } catch (ExpiredJwtException e) {
                //JWT 已过期
                System.out.println("token 已过期");
            }
            //取出权限角色列表
            Object identitiesObj = claims.get("identities");
            List<GrantedAuthority> authorities = new ArrayList<>();
            if (identitiesObj instanceof List<?>) {
                for (Object role : (List<?>) identitiesObj) {
                    if (role instanceof String) {
                        // 将字符串转换为 SimpleGrantedAuthority
                        authorities.add(new SimpleGrantedAuthority((String) role));
                    }
                }
            }
            String userId = claims.getSubject();
            //将 Authentication 对象设置到 SecurityContextHolder 中后,Spring Security 就能在后续的授权过程中使用这些权限信息了。
            Authentication authentication = new UsernamePasswordAuthenticationToken(userId, null, authorities);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

(3)配置 Spring Security

在网关gateway模块配置所有微服务整体的权限管理规则:

java 复制代码
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        http.csrf().disable()// 关闭 CSRF(跨站请求伪造)防护。在无状态 API(如 JWT 场景)中,CSRF 防护不必要(通常依赖 Authorization 头而非 Cookie)。 避免对 POST、PUT 等请求要求携带 CSRF Token。
                .authorizeExchange()
                .pathMatchers("/api/product/**").permitAll()
                .pathMatchers("/admin/**").permitAll()
                .anyExchange().authenticated();
        return http.build();
    }
}

如果各个微服务还需要独自的更细粒度的权限控制,只需要在单个微服务模块中单独配置一个Spring Security就行了。

相关推荐
考虑考虑34 分钟前
JDK9中的dropWhile
java·后端·java ee
想躺平的咸鱼干43 分钟前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying1 小时前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·1 小时前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
martinzh2 小时前
Spring AI 项目介绍
后端
Bug退退退1232 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠2 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
前端付豪2 小时前
20、用 Python + API 打造终端天气预报工具(支持城市查询、天气图标、美化输出🧊
后端·python
爱学习的小学渣2 小时前
关系型数据库
后端
武子康2 小时前
大数据-33 HBase 整体架构 HMaster HRegion
大数据·后端·hbase