国家电网Java面试被问:API网关的JWT令牌验证和OAuth2.0授权码流程

一、JWT令牌在API网关中的验证机制

1.1 JWT基础结构与原理

java

复制

下载

复制代码
/**
 * JWT令牌结构解析
 * Header.Payload.Signature
 */
public class JWTStructure {
    
    /**
     * JWT三部分结构示例
     */
    public void parseJWTStructure() {
        // 原始JWT示例
        String jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
                         "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." +
                         "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
        
        // 解码各部分
        String[] parts = jwtToken.split("\\.");
        
        // 1. Header - 算法和类型
        String header = new String(Base64.getUrlDecoder().decode(parts[0]));
        // {"alg":"HS256","typ":"JWT"}
        
        // 2. Payload - 载荷(声明)
        String payload = new String(Base64.getUrlDecoder().decode(parts[1]));
        // {"sub":"1234567890","name":"John Doe","iat":1516239022}
        
        // 3. Signature - 签名
        // 验证签名的完整性
    }
    
    /**
     * JWT标准声明字段
     */
    public class StandardClaims {
        // 注册声明(建议但不强制使用)
        public static final String ISSUER = "iss";          // 签发者
        public static final String SUBJECT = "sub";         // 主题(用户ID)
        public static final String AUDIENCE = "aud";        // 接收方
        public static final String EXPIRATION = "exp";      // 过期时间
        public static final String NOT_BEFORE = "nbf";      // 生效时间
        public static final String ISSUED_AT = "iat";      // 签发时间
        public static final String JWT_ID = "jti";         // JWT ID
        
        // 公共声明
        // 可以自定义,但需要避免冲突
        
        // 私有声明
        // 自定义的业务相关声明
    }
}

1.2 API网关JWT验证实现

java

复制

下载

复制代码
/**
 * API网关JWT验证过滤器
 */
@Component
public class JwtAuthenticationFilter implements GatewayFilter {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 网关过滤器:验证JWT令牌
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        // 1. 从请求头获取Token
        String token = resolveToken(request);
        
        if (StringUtils.hasText(token)) {
            try {
                // 2. 验证Token有效性
                if (tokenProvider.validateToken(token)) {
                    // 3. 解析用户信息
                    Authentication authentication = tokenProvider.getAuthentication(token);
                    
                    // 4. 检查Token是否在黑名单(登出场景)
                    if (isTokenBlacklisted(token)) {
                        return unauthenticatedResponse(exchange, "Token已失效");
                    }
                    
                    // 5. 设置安全上下文
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    
                    // 6. 将用户信息添加到请求头,传递给下游服务
                    ServerHttpRequest mutatedRequest = addUserHeaders(request, authentication);
                    
                    return chain.filter(exchange.mutate().request(mutatedRequest).build());
                }
            } catch (JwtException e) {
                // Token验证失败
                return unauthenticatedResponse(exchange, "Token验证失败: " + e.getMessage());
            }
        }
        
        // 没有Token的情况(根据业务决定是否允许访问公开端点)
        return checkPublicEndpoint(exchange, chain);
    }
    
    /**
     * 解析Token
     */
    private String resolveToken(ServerHttpRequest request) {
        String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        
        // 尝试从查询参数获取(某些场景下)
        String tokenParam = request.getQueryParams().getFirst("access_token");
        if (StringUtils.hasText(tokenParam)) {
            return tokenParam;
        }
        
        return null;
    }
    
    /**
     * 将用户信息添加到请求头
     */
    private ServerHttpRequest addUserHeaders(ServerHttpRequest request, Authentication auth) {
        return request.mutate()
            .header("X-User-Id", auth.getName())
            .header("X-User-Roles", getRoles(auth))
            .header("X-User-Permissions", getPermissions(auth))
            .build();
    }
    
    /**
     * 检查Token是否在黑名单
     */
    private boolean isTokenBlacklisted(String token) {
        String key = "blacklist:token:" + DigestUtils.md5DigestAsHex(token.getBytes());
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
    
    /**
     * 未认证响应
     */
    private Mono<Void> unauthenticatedResponse(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        
        Map<String, Object> error = new HashMap<>();
        error.put("timestamp", System.currentTimeMillis());
        error.put("status", HttpStatus.UNAUTHORIZED.value());
        error.put("error", "Unauthorized");
        error.put("message", message);
        error.put("path", exchange.getRequest().getPath().value());
        
        byte[] bytes = JSON.toJSONBytes(error);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        
        return response.writeWith(Mono.just(buffer));
    }
}

/**
 * JWT Token提供者
 */
@Component
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private long jwtExpirationInMs;
    
    @Value("${jwt.refresh-expiration}")
    private long refreshExpirationInMs;
    
    /**
     * 生成Access Token
     */
    public String generateAccessToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
        
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .claim("userId", getUserId(userDetails))
            .claim("roles", getRoles(userDetails))
            .claim("permissions", getPermissions(userDetails))
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
    
    /**
     * 生成Refresh Token
     */
    public String generateRefreshToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + refreshExpirationInMs);
        
        return Jwts.builder()
            .setSubject(userDetails.getUsername())
            .claim("type", "refresh")
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
    
    /**
     * 验证Token
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token);
            
            // 检查Token是否过期(JWT解析会自动检查)
            return true;
        } catch (SignatureException ex) {
            log.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            log.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            log.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            log.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            log.error("JWT claims string is empty");
        }
        
        return false;
    }
    
    /**
     * 从Token中获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
        
        return claims.getSubject();
    }
    
    /**
     * 从Token中获取认证信息
     */
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser()
            .setSigningKey(jwtSecret)
            .parseClaimsJws(token)
            .getBody();
        
        String username = claims.getSubject();
        Collection<? extends GrantedAuthority> authorities = 
            extractAuthorities(claims);
        
        UserDetails userDetails = new org.springframework.security.core.userdetails.User(
            username, "", authorities);
        
        return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
    }
    
    /**
     * 从claims中提取权限信息
     */
    private Collection<? extends GrantedAuthority> extractAuthorities(Claims claims) {
        List<String> roles = claims.get("roles", List.class);
        List<String> permissions = claims.get("permissions", List.class);
        
        List<GrantedAuthority> authorities = new ArrayList<>();
        
        if (roles != null) {
            roles.forEach(role -> 
                authorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
        }
        
        if (permissions != null) {
            permissions.forEach(permission -> 
                authorities.add(new SimpleGrantedAuthority(permission)));
        }
        
        return authorities;
    }
}

1.3 JWT令牌的安全增强

java

复制

下载

复制代码
/**
 * JWT安全增强机制
 */
@Component
public class JwtSecurityEnhancer {
    
    /**
     * 增强的JWT生成(带指纹和额外安全措施)
     */
    public EnhancedJwtToken generateEnhancedToken(Authentication authentication, 
                                                  HttpServletRequest request) {
        
        // 1. 生成标准JWT
        String accessToken = jwtTokenProvider.generateAccessToken(authentication);
        String refreshToken = jwtTokenProvider.generateRefreshToken(authentication);
        
        // 2. 生成令牌指纹(防止Token泄露后的重放攻击)
        String fingerprint = generateFingerprint(request);
        
        // 3. 创建增强的Token对象
        EnhancedJwtToken enhancedToken = new EnhancedJwtToken();
        enhancedToken.setAccessToken(accessToken);
        enhancedToken.setRefreshToken(refreshToken);
        enhancedToken.setFingerprint(fingerprint);
        enhancedToken.setTokenType("Bearer");
        enhancedToken.setExpiresIn(jwtTokenProvider.getJwtExpirationInMs() / 1000);
        
        // 4. 将指纹与Token关联存储(可选)
        storeFingerprintAssociation(fingerprint, authentication.getName());
        
        return enhancedToken;
    }
    
    /**
     * 生成基于客户端特征的指纹
     */
    private String generateFingerprint(HttpServletRequest request) {
        // 使用用户代理、IP等信息生成指纹
        String userAgent = request.getHeader("User-Agent");
        String ipAddress = getClientIp(request);
        
        String rawFingerprint = userAgent + "|" + ipAddress;
        
        // 生成SHA-256哈希作为指纹
        return DigestUtils.sha256Hex(rawFingerprint);
    }
    
    /**
     * 增强的Token验证
     */
    public boolean validateEnhancedToken(String token, HttpServletRequest request) {
        // 1. 基础JWT验证
        if (!jwtTokenProvider.validateToken(token)) {
            return false;
        }
        
        // 2. 验证指纹
        String expectedFingerprint = generateFingerprint(request);
        String storedFingerprint = getStoredFingerprint(token);
        
        if (!expectedFingerprint.equals(storedFingerprint)) {
            log.warn("Token指纹验证失败,可能存在安全风险");
            // 根据安全策略决定是否拒绝访问
            // return false; // 严格模式
        }
        
        // 3. 检查Token使用频率(防滥用)
        if (isTokenOverused(token)) {
            log.warn("Token使用频率异常");
            return false;
        }
        
        return true;
    }
    
    /**
     * JWT令牌轮换机制
     */
    public TokenRotationResult rotateTokens(String refreshToken, HttpServletRequest request) {
        // 1. 验证Refresh Token
        if (!jwtTokenProvider.validateToken(refreshToken)) {
            throw new InvalidTokenException("无效的Refresh Token");
        }
        
        // 2. 检查Refresh Token类型
        Claims claims = jwtTokenProvider.parseClaims(refreshToken);
        if (!"refresh".equals(claims.get("type", String.class))) {
            throw new InvalidTokenException("Token类型错误");
        }
        
        // 3. 使旧Refresh Token失效
        revokeRefreshToken(refreshToken);
        
        // 4. 生成新的Token对
        String username = claims.getSubject();
        Authentication auth = new UsernamePasswordAuthenticationToken(
            username, null, Collections.emptyList());
        
        EnhancedJwtToken newTokens = generateEnhancedToken(auth, request);
        
        // 5. 返回结果
        return new TokenRotationResult(newTokens, true);
    }
}

/**
 * 增强的JWT Token对象
 */
@Data
public class EnhancedJwtToken {
    private String accessToken;
    private String refreshToken;
    private String tokenType;
    private Long expiresIn;
    private String fingerprint;  // 客户端指纹
    private Long refreshExpiresIn;
    private String scope;
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

二、OAuth2.0授权码流程深度实现

2.1 OAuth2.0授权码完整流程

java

复制

下载

复制代码
/**
 * OAuth2.0授权码模式完整实现
 * 涉及角色:资源拥有者(用户)、客户端(第三方应用)、授权服务器、资源服务器
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    /**
     * 配置客户端详情
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource)
            .withClient("web-app")
                .secret(passwordEncoder.encode("secret"))
                .authorizedGrantTypes("authorization_code", "refresh_token", "password")
                .scopes("read", "write")
                .autoApprove(false)
                .redirectUris("http://localhost:8080/login/oauth2/code/web-app")
                .accessTokenValiditySeconds(3600)      // 1小时
                .refreshTokenValiditySeconds(2592000)  // 30天
            .and()
            .withClient("mobile-app")
                .secret(passwordEncoder.encode("mobile-secret"))
                .authorizedGrantTypes("authorization_code", "refresh_token")
                .scopes("read")
                .autoApprove(true)
                .redirectUris("com.example.app://oauth")
                .accessTokenValiditySeconds(7200)      // 2小时
                .refreshTokenValiditySeconds(2592000);
    }
    
    /**
     * 配置授权服务器端点
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService)
            .tokenStore(tokenStore())
            .authorizationCodeServices(authorizationCodeServices())
            .pathMapping("/oauth/token", "/api/oauth/token")  // 自定义端点路径
            .pathMapping("/oauth/authorize", "/api/oauth/authorize")
            .tokenEnhancer(tokenEnhancerChain())
            .exceptionTranslator(oauth2ExceptionTranslator());
    }
    
    /**
     * 配置令牌存储
     */
    @Bean
    public TokenStore tokenStore() {
        // 使用Redis存储Token(生产环境推荐)
        return new RedisTokenStore(redisTemplate.getConnectionFactory());
        
        // 或者使用JWT(无状态)
        // return new JwtTokenStore(jwtAccessTokenConverter());
    }
    
    /**
     * 授权码服务
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        // 使用JDBC存储授权码
        return new JdbcAuthorizationCodeServices(dataSource);
    }
    
    /**
     * Token增强器链
     */
    @Bean
    public TokenEnhancerChain tokenEnhancerChain() {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancers = new ArrayList<>();
        
        enhancers.add(new CustomTokenEnhancer());  // 自定义增强
        enhancers.add(jwtAccessTokenConverter());  // JWT转换
        
        enhancerChain.setTokenEnhancers(enhancers);
        return enhancerChain;
    }
    
    /**
     * JWT Token转换器
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("my-signing-key");  // 生产环境使用非对称加密
        
        // 自定义Claims
        DefaultAccessTokenConverter accessTokenConverter = 
            new DefaultAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(new CustomUserAuthenticationConverter());
        converter.setAccessTokenConverter(accessTokenConverter);
        
        return converter;
    }
}

/**
 * 授权端点控制器
 */
@Controller
public class OAuth2AuthorizationController {
    
    @Autowired
    private ClientDetailsService clientDetailsService;
    
    /**
     * 第一步:授权请求端点
     * GET /oauth/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&scope=xxx&state=xxx
     */
    @GetMapping("/oauth/authorize")
    public String authorizePage(@RequestParam Map<String, String> parameters,
                               Model model,
                               HttpSession session) {
        
        // 1. 验证客户端
        String clientId = parameters.get("client_id");
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
        
        if (clientDetails == null) {
            throw new InvalidClientException("无效的客户端ID");
        }
        
        // 2. 验证重定向URI
        String redirectUri = parameters.get("redirect_uri");
        if (!clientDetails.getRegisteredRedirectUri().contains(redirectUri)) {
            throw new RedirectMismatchException("重定向URI不匹配");
        }
        
        // 3. 验证响应类型
        String responseType = parameters.get("response_type");
        if (!"code".equals(responseType)) {
            throw new UnsupportedResponseTypeException("不支持的响应类型");
        }
        
        // 4. 将参数存入session,供确认页面使用
        session.setAttribute("oauth2_authorization_params", parameters);
        
        // 5. 返回授权确认页面
        model.addAttribute("clientName", getClientName(clientDetails));
        model.addAttribute("scopes", parseScopes(parameters.get("scope")));
        model.addAttribute("redirectUri", redirectUri);
        
        return "oauth/authorize";
    }
    
    /**
     * 第二步:用户确认授权
     * POST /oauth/authorize
     */
    @PostMapping("/oauth/authorize")
    public void approveOrDeny(@RequestParam("user_oauth_approval") String approval,
                             HttpSession session,
                             HttpServletResponse response) throws IOException {
        
        // 1. 获取session中的授权参数
        @SuppressWarnings("unchecked")
        Map<String, String> params = (Map<String, String>) 
            session.getAttribute("oauth2_authorization_params");
        
        // 2. 生成授权码
        if ("true".equals(approval)) {
            String authorizationCode = generateAuthorizationCode(params);
            
            // 3. 重定向到客户端,附带授权码
            String redirectUri = params.get("redirect_uri");
            String state = params.get("state");
            
            String location = redirectUri + "?code=" + authorizationCode;
            if (StringUtils.hasText(state)) {
                location += "&state=" + state;
            }
            
            response.sendRedirect(location);
        } else {
            // 用户拒绝授权
            String redirectUri = params.get("redirect_uri");
            response.sendRedirect(redirectUri + "?error=access_denied");
        }
    }
    
    /**
     * 生成授权码
     */
    private String generateAuthorizationCode(Map<String, String> params) {
        String code = UUID.randomUUID().toString().replace("-", "");
        
        // 存储授权码(关联客户端、用户、范围等信息)
        OAuth2AuthorizationCode authCode = new OAuth2AuthorizationCode();
        authCode.setCode(code);
        authCode.setClientId(params.get("client_id"));
        authCode.setUsername(SecurityContextHolder.getContext()
            .getAuthentication().getName());
        authCode.setRedirectUri(params.get("redirect_uri"));
        authCode.setScopes(parseScopes(params.get("scope")));
        authCode.setCreationTime(new Date());
        authCode.setExpirationTime(new Date(System.currentTimeMillis() + 10 * 60 * 1000)); // 10分钟
        
        // 保存到存储(如Redis或数据库)
        saveAuthorizationCode(authCode);
        
        return code;
    }
}

/**
 * 令牌端点控制器
 */
@RestController
public class TokenEndpointController {
    
    @Autowired
    private TokenGranter tokenGranter;
    
    /**
     * 第三步:获取访问令牌
     * POST /oauth/token
     */
    @PostMapping("/oauth/token")
    public ResponseEntity<OAuth2AccessToken> getToken(@RequestBody MultiValueMap<String, String> parameters,
                                                      @RequestHeader(value = "Authorization", required = false) String authHeader) {
        
        // 1. 提取客户端凭证(支持Basic Auth或请求参数)
        String clientId;
        String clientSecret;
        
        if (authHeader != null && authHeader.startsWith("Basic ")) {
            // 从Basic Auth头提取
            String[] credentials = extractFromBasicAuth(authHeader);
            clientId = credentials[0];
            clientSecret = credentials[1];
        } else {
            // 从请求参数提取
            clientId = parameters.getFirst("client_id");
            clientSecret = parameters.getFirst("client_secret");
        }
        
        // 2. 验证客户端
        ClientDetails clientDetails = validateClient(clientId, clientSecret);
        
        // 3. 根据grant_type处理不同授权类型
        String grantType = parameters.getFirst("grant_type");
        
        OAuth2AccessToken accessToken;
        
        switch (grantType) {
            case "authorization_code":
                // 授权码模式
                accessToken = processAuthorizationCodeGrant(parameters, clientDetails);
                break;
                
            case "refresh_token":
                // 刷新令牌
                accessToken = processRefreshTokenGrant(parameters, clientDetails);
                break;
                
            case "password":
                // 密码模式
                accessToken = processPasswordGrant(parameters, clientDetails);
                break;
                
            case "client_credentials":
                // 客户端凭证模式
                accessToken = processClientCredentialsGrant(clientDetails);
                break;
                
            default:
                throw new UnsupportedGrantTypeException("不支持的授权类型: " + grantType);
        }
        
        return ResponseEntity.ok(accessToken);
    }
    
    /**
     * 处理授权码模式
     */
    private OAuth2AccessToken processAuthorizationCodeGrant(MultiValueMap<String, String> params,
                                                           ClientDetails clientDetails) {
        // 1. 获取授权码
        String code = params.getFirst("code");
        if (!StringUtils.hasText(code)) {
            throw new InvalidRequestException("授权码不能为空");
        }
        
        // 2. 验证授权码
        OAuth2AuthorizationCode authCode = loadAuthorizationCode(code);
        if (authCode == null) {
            throw new InvalidGrantException("无效的授权码");
        }
        
        // 3. 检查授权码是否过期
        if (authCode.getExpirationTime().before(new Date())) {
            removeAuthorizationCode(code);
            throw new InvalidGrantException("授权码已过期");
        }
        
        // 4. 验证客户端匹配
        if (!authCode.getClientId().equals(clientDetails.getClientId())) {
            throw new InvalidClientException("客户端不匹配");
        }
        
        // 5. 验证重定向URI(如果提供)
        String redirectUri = params.getFirst("redirect_uri");
        if (StringUtils.hasText(redirectUri) && 
            !redirectUri.equals(authCode.getRedirectUri())) {
            throw new RedirectMismatchException("重定向URI不匹配");
        }
        
        // 6. 移除已使用的授权码(一次性使用)
        removeAuthorizationCode(code);
        
        // 7. 生成访问令牌和刷新令牌
        OAuth2Request storedRequest = new OAuth2Request(
            null, 
            authCode.getClientId(), 
            authCode.getScopes(), 
            true, 
            authCode.getScopes(),
            null, 
            authCode.getRedirectUri(), 
            null, 
            null
        );
        
        Authentication userAuth = new UsernamePasswordAuthenticationToken(
            authCode.getUsername(), null, Collections.emptyList());
        
        OAuth2Authentication authentication = new OAuth2Authentication(
            storedRequest, userAuth);
        
        return tokenGranter.grant("authorization_code", authentication);
    }
}

2.2 OAuth2.0授权码流程安全增强

java

复制

下载

复制代码
/**
 * OAuth2.0安全增强机制
 */
@Component
public class OAuth2SecurityEnhancer {
    
    /**
     * PKCE(Proof Key for Code Exchange)增强
     * 防止授权码拦截攻击
     */
    public class PKCEEnhancer {
        
        /**
         * 生成Code Verifier和Code Challenge
         */
        public PKCEPair generatePKCEPair() {
            // Code Verifier:随机字符串,43-128字符
            String codeVerifier = generateRandomString(64);
            
            // Code Challenge:verifier的SHA-256哈希,Base64 URL编码
            String codeChallenge = generateCodeChallenge(codeVerifier);
            
            return new PKCEPair(codeVerifier, codeChallenge, "S256");
        }
        
        /**
         * 验证授权请求中的PKCE参数
         */
        public void validatePKCEAtAuthorization(Map<String, String> params) {
            String codeChallenge = params.get("code_challenge");
            String codeChallengeMethod = params.get("code_challenge_method");
            
            if (StringUtils.hasText(codeChallenge)) {
                // 验证challenge方法
                if (!"S256".equals(codeChallengeMethod) && 
                    !"plain".equals(codeChallengeMethod)) {
                    throw new InvalidRequestException("不支持的code_challenge_method");
                }
                
                // 验证challenge长度
                if (codeChallenge.length() < 43 || codeChallenge.length() > 128) {
                    throw new InvalidRequestException("code_challenge长度无效");
                }
                
                // 存储challenge,与授权码关联
                storeCodeChallenge(params.get("state"), codeChallenge, codeChallengeMethod);
            }
        }
        
        /**
         * 验证令牌请求中的PKCE参数
         */
        public void validatePKCEAtToken(String code, String codeVerifier) {
            // 1. 获取与授权码关联的challenge
            PKCEChallenge storedChallenge = loadCodeChallenge(code);
            
            if (storedChallenge == null) {
                // 客户端可能没有使用PKCE,根据策略决定
                if (requirePKCEForAllClients()) {
                    throw new InvalidGrantException("PKCE验证失败");
                }
                return;
            }
            
            // 2. 计算验证器对应的challenge
            String expectedChallenge;
            if ("S256".equals(storedChallenge.getMethod())) {
                expectedChallenge = generateCodeChallenge(codeVerifier);
            } else {
                expectedChallenge = codeVerifier;  // plain方法
            }
            
            // 3. 比较challenge
            if (!storedChallenge.getChallenge().equals(expectedChallenge)) {
                throw new InvalidGrantException("PKCE验证失败");
            }
            
            // 4. 验证成功后移除challenge
            removeCodeChallenge(code);
        }
        
        private String generateRandomString(int length) {
            SecureRandom random = new SecureRandom();
            byte[] bytes = new byte[length];
            random.nextBytes(bytes);
            return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
        }
        
        private String generateCodeChallenge(String codeVerifier) {
            try {
                MessageDigest md = MessageDigest.getInstance("SHA-256");
                byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
                return Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("SHA-256算法不可用", e);
            }
        }
    }
    
    /**
     * 令牌绑定(Token Binding)增强
     * 将令牌与TLS会话绑定,防止令牌泄露
     */
    public class TokenBindingEnhancer {
        
        /**
         * 生成令牌绑定ID
         */
        public String generateTokenBindingId(HttpServletRequest request) {
            // 从TLS会话中提取信息(如TLS唯一标识符)
            String tlsUnique = extractTlsUnique(request);
            
            if (tlsUnique != null) {
                // 生成绑定ID
                return "tls:" + DigestUtils.sha256Hex(tlsUnique);
            }
            
            return null;
        }
        
        /**
         * 验证令牌绑定
         */
        public boolean validateTokenBinding(String accessToken, HttpServletRequest request) {
            // 1. 从令牌中提取绑定ID
            String tokenBindingId = extractTokenBindingId(accessToken);
            
            if (tokenBindingId == null) {
                // 令牌没有绑定,根据策略决定
                return !requireTokenBinding();
            }
            
            // 2. 计算当前请求的绑定ID
            String currentBindingId = generateTokenBindingId(request);
            
            // 3. 比较绑定ID
            return tokenBindingId.equals(currentBindingId);
        }
    }
    
    /**
     * 增强的令牌端点安全
     */
    @Configuration
    public static class EnhancedTokenEndpointSecurity {
        
        @Bean
        public ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter() {
            ClientCredentialsTokenEndpointFilter filter = 
                new ClientCredentialsTokenEndpointFilter("/oauth/token");
            filter.setAuthenticationManager(authenticationManager);
            filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
            return filter;
        }
        
        /**
         * 速率限制:防止暴力破解
         */
        @Bean
        public FilterRegistrationBean<RateLimitFilter> rateLimitFilter() {
            FilterRegistrationBean<RateLimitFilter> registration = 
                new FilterRegistrationBean<>();
            registration.setFilter(new RateLimitFilter());
            registration.addUrlPatterns("/oauth/token");
            registration.setOrder(1);
            return registration;
        }
        
        /**
         * 请求验证:防止重放攻击
         */
        @Bean
        public FilterRegistrationBean<ReplayAttackFilter> replayAttackFilter() {
            FilterRegistrationBean<ReplayAttackFilter> registration = 
                new FilterRegistrationBean<>();
            registration.setFilter(new ReplayAttackFilter());
            registration.addUrlPatterns("/oauth/token");
            registration.setOrder(2);
            return registration;
        }
    }
}

/**
 * OAuth2.0监控和审计
 */
@Component
public class OAuth2AuditLogger {
    
    private static final Logger auditLogger = LoggerFactory.getLogger("OAUTH2_AUDIT");
    
    /**
     * 记录授权请求
     */
    public void logAuthorizationRequest(String clientId, String userId, 
                                       String redirectUri, String scope, 
                                       String ipAddress, boolean approved) {
        
        Map<String, Object> auditEntry = new LinkedHashMap<>();
        auditEntry.put("timestamp", System.currentTimeMillis());
        auditEntry.put("event", "authorization_request");
        auditEntry.put("client_id", clientId);
        auditEntry.put("user_id", userId);
        auditEntry.put("redirect_uri", redirectUri);
        auditEntry.put("scope", scope);
        auditEntry.put("ip_address", ipAddress);
        auditEntry.put("approved", approved);
        auditEntry.put("user_agent", getCurrentUserAgent());
        
        auditLogger.info(JSON.toJSONString(auditEntry));
    }
    
    /**
     * 记录令牌颁发
     */
    public void logTokenIssuance(String clientId, String userId, 
                                String grantType, String scope,
                                String tokenId, String ipAddress) {
        
        Map<String, Object> auditEntry = new LinkedHashMap<>();
        auditEntry.put("timestamp", System.currentTimeMillis());
        auditEntry.put("event", "token_issuance");
        auditEntry.put("client_id", clientId);
        auditEntry.put("user_id", userId);
        auditEntry.put("grant_type", grantType);
        auditEntry.put("scope", scope);
        auditEntry.put("token_id", tokenId);
        auditEntry.put("ip_address", ipAddress);
        
        auditLogger.info(JSON.toJSONString(auditEntry));
    }
    
    /**
     * 记录令牌验证
     */
    public void logTokenValidation(String tokenId, String clientId, 
                                  String endpoint, boolean valid,
                                  String ipAddress) {
        
        if (!valid) {
            // 重点关注无效令牌验证
            Map<String, Object> auditEntry = new LinkedHashMap<>();
            auditEntry.put("timestamp", System.currentTimeMillis());
            auditEntry.put("event", "token_validation_failed");
            auditEntry.put("token_id", tokenId);
            auditEntry.put("client_id", clientId);
            auditEntry.put("endpoint", endpoint);
            auditEntry.put("ip_address", ipAddress);
            
            auditLogger.warn(JSON.toJSONString(auditEntry));
            
            // 安全警报:检测暴力破解
            checkForBruteForce(ipAddress, clientId);
        }
    }
}

2.3 API网关的OAuth2.0集成

java

复制

下载

复制代码
/**
 * API网关的OAuth2.0资源服务器配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
            .resourceId("api-gateway")
            .tokenStore(tokenStore())
            .stateless(true)
            .accessDeniedHandler(accessDeniedHandler())
            .authenticationEntryPoint(authenticationEntryPoint());
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
                // 公开端点
                .antMatchers("/api/public/**", "/oauth/**", "/actuator/health").permitAll()
                
                // 需要认证的端点
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/api/internal/**").hasIpAddress("192.168.0.0/24")
                
                // 基于Scope的权限控制
                .antMatchers(HttpMethod.GET, "/api/products/**").access("#oauth2.hasScope('read')")
                .antMatchers(HttpMethod.POST, "/api/products/**").access("#oauth2.hasScope('write')")
                .antMatchers(HttpMethod.DELETE, "/api/products/**").access("#oauth2.hasScope('write')")
                
                // 其他所有请求需要认证
                .anyRequest().authenticated()
            .and()
            // 添加自定义过滤器
            .addFilterBefore(rateLimitFilter(), BasicAuthenticationFilter.class)
            .addFilterAfter(auditFilter(), BasicAuthenticationFilter.class)
            .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint())
                .accessDeniedHandler(accessDeniedHandler());
    }
    
    /**
     * 自定义Token存储(支持JWT和Opaque Token)
     */
    @Bean
    public TokenStore tokenStore() {
        // 复合Token存储:同时支持JWT和Opaque Token
        CompositeTokenStore tokenStore = new CompositeTokenStore();
        
        // JWT Token存储
        tokenStore.addTokenStore(new JwtTokenStore(jwtAccessTokenConverter()));
        
        // Opaque Token存储(远程验证)
        tokenStore.addTokenStore(new RemoteTokenStore(
            "http://auth-server/oauth/check_token"));
        
        return tokenStore;
    }
    
    /**
     * 自定义访问控制
     */
    @Bean
    public AccessDecisionManager accessDecisionManager() {
        List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
        
        // 基于角色的投票器
        decisionVoters.add(new RoleVoter());
        
        // 基于Scope的投票器
        decisionVoters.add(new ScopeVoter());
        
        // 自定义业务投票器
        decisionVoters.add(new BusinessAccessVoter());
        
        // 时间限制投票器(如只能在特定时间段访问)
        decisionVoters.add(new TimeRestrictionVoter());
        
        return new UnanimousBased(decisionVoters);
    }
}

/**
 * API网关的OAuth2.0代理过滤器
 */
@Component
public class OAuth2ProxyFilter implements GatewayFilter {
    
    @Autowired
    private TokenServices tokenServices;
    
    @Autowired
    private LoadBalancerClient loadBalancer;
    
    /**
     * 为服务间调用添加OAuth2 Token
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        // 1. 检查是否是内部服务调用
        if (isInternalServiceCall(request)) {
            // 2. 获取或创建服务账户Token
            String serviceToken = getOrCreateServiceToken(request);
            
            // 3. 添加Authorization头
            ServerHttpRequest mutatedRequest = request.mutate()
                .header(HttpHeaders.AUTHORIZATION, "Bearer " + serviceToken)
                .build();
            
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        }
        
        // 4. 用户请求:传递用户的Token
        String userToken = extractUserToken(request);
        if (userToken != null) {
            // 验证Token并传递给下游服务
            if (validateToken(userToken)) {
                ServerHttpRequest mutatedRequest = request.mutate()
                    .header("X-User-Token", userToken)
                    .build();
                
                return chain.filter(exchange.mutate().request(mutatedRequest).build());
            }
        }
        
        return chain.filter(exchange);
    }
    
    /**
     * 获取服务账户Token
     */
    private String getOrCreateServiceToken(ServerHttpRequest request) {
        String serviceName = extractServiceName(request);
        String cacheKey = "service_token:" + serviceName;
        
        // 检查缓存
        String cachedToken = redisTemplate.opsForValue().get(cacheKey);
        if (cachedToken != null && validateToken(cachedToken)) {
            return cachedToken;
        }
        
        // 创建新的服务Token
        OAuth2Request oAuth2Request = new OAuth2Request(
            Collections.emptyMap(),
            "gateway-service",
            Collections.singleton("internal"),
            true,
            Collections.singleton("internal"),
            Collections.singleton(serviceName),
            null,
            Collections.emptySet(),
            Collections.emptyMap()
        );
        
        Authentication authentication = new UsernamePasswordAuthenticationToken(
            "gateway-service", null, Collections.emptyList());
        
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(
            oAuth2Request, authentication);
        
        OAuth2AccessToken accessToken = tokenServices.createAccessToken(oAuth2Authentication);
        
        // 缓存Token(提前刷新)
        long ttl = accessToken.getExpiresIn() - 300; // 提前5分钟刷新
        redisTemplate.opsForValue().set(cacheKey, accessToken.getValue(), ttl, TimeUnit.SECONDS);
        
        return accessToken.getValue();
    }
}

/**
 * 令牌转换和传播
 */
@Component
public class TokenPropagationHandler {
    
    /**
     * JWT令牌转换为用户信息头
     */
    public ServerHttpRequest propagateUserInfo(ServerHttpRequest request, String jwtToken) {
        if (jwtToken == null) {
            return request;
        }
        
        // 解析JWT,提取用户信息
        Claims claims = parseJwtClaims(jwtToken);
        
        // 创建修改后的请求
        return request.mutate()
            .header("X-User-Id", claims.getSubject())
            .header("X-User-Name", claims.get("name", String.class))
            .header("X-User-Email", claims.get("email", String.class))
            .header("X-User-Roles", String.join(",", claims.get("roles", List.class)))
            .header("X-User-Permissions", String.join(",", claims.get("permissions", List.class)))
            .header("X-User-Tenant", claims.get("tenant", String.class))
            .build();
    }
    
    /**
     * Opaque Token转换:调用用户信息端点
     */
    public Mono<ServerHttpRequest> propagateOpaqueTokenInfo(ServerHttpRequest request, String token) {
        return WebClient.create()
            .get()
            .uri("http://auth-server/oauth/userinfo")
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
            .retrieve()
            .bodyToMono(Map.class)
            .map(userInfo -> {
                return request.mutate()
                    .header("X-User-Id", (String) userInfo.get("sub"))
                    .header("X-User-Name", (String) userInfo.get("name"))
                    .header("X-User-Email", (String) userInfo.get("email"))
                    .build();
            })
            .onErrorResume(e -> {
                log.error("获取用户信息失败", e);
                return Mono.just(request);
            });
    }
}

三、生产环境最佳实践

3.1 安全配置最佳实践

yaml

复制

下载

复制代码
# application-security.yml

jwt:
  security:
    # JWT签名密钥(生产环境使用RSA非对称加密)
    secret-key: ${JWT_SECRET:default-256-bit-secret-key-change-in-production}
    # 使用RSA非对称加密
    use-rsa: true
    rsa:
      private-key: classpath:private.key
      public-key: classpath:public.key
    
    # Token有效期
    access-token-expiration: 3600    # 1小时
    refresh-token-expiration: 2592000 # 30天
    
    # 刷新策略
    allow-refresh-before-expiry: 300  # 过期前5分钟允许刷新
    max-refresh-attempts: 3          # 最大刷新次数
    
    # 安全增强
    require-issuer: true
    issuer: https://auth.example.com
    require-audience: true
    audience: api-gateway
    require-jti: true                # 需要JWT ID防止重放

oauth2:
  security:
    # 授权码配置
    authorization-code:
      expiration: 600                # 10分钟
      require-pkce: true            # 强制使用PKCE
      pkce-method: S256             # PKCE方法
    
    # 客户端配置
    client:
      min-secret-length: 32         # 客户端密钥最小长度
      require-confidential: true    # 需要机密客户端
      redirect-uri-validation:
        require-exact-match: true   # 重定向URI精确匹配
        allow-wildcard-subdomains: false
    
    # 令牌配置
    token:
      include-jti: true             # 包含JWT ID
      include-issuer: true
      include-audience: true
      bind-to-tls: true             # 绑定到TLS会话
      require-token-binding: false  # 根据安全需求调整
    
    # 安全头
    security-headers:
      enable-csp: true              # 内容安全策略
      enable-hsts: true             # HSTS
      enable-xss-protection: true
      enable-frame-options: true
    
    # 速率限制
    rate-limiting:
      enabled: true
      token-endpoint:
        requests-per-minute: 60
        burst-size: 10
      authorize-endpoint:
        requests-per-minute: 30
        burst-size: 5
    
    # 审计日志
    audit:
      enabled: true
      log-successful-auth: true
      log-failed-auth: true
      log-token-issuance: true
      log-token-validation: true

# Redis配置(Token存储)
spring:
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    database: 0
    timeout: 2000ms
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
        max-wait: -1ms

# API网关路由配置
gateway:
  routes:
    - id: auth-service
      uri: lb://auth-service
      predicates:
        - Path=/oauth/**
      filters:
        - name: CircuitBreaker
          args:
            name: authService
            fallbackUri: forward:/fallback/auth
        - name: RateLimiter
          args:
            redis-rate-limiter.replenishRate: 10
            redis-rate-limiter.burstCapacity: 20
        - name: RequestRateLimiter
          args:
            key-resolver: "#{@remoteAddrKeyResolver}"
            redis-rate-limiter.replenishRate: 50
            redis-rate-limiter.burstCapacity: 100
    
    - id: api-service
      uri: lb://api-service
      predicates:
        - Path=/api/**
      filters:
        - name: JwtAuthentication
        - name: OAuth2TokenRelay
        - StripPrefix=1

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

3.2 监控和运维配置

java

复制

下载

复制代码
/**
 * 安全监控和指标收集
 */
@Component
public class SecurityMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    
    // 计数器指标
    private final Counter tokenValidationSuccess;
    private final Counter tokenValidationFailure;
    private final Counter authorizationSuccess;
    private final Counter authorizationFailure;
    
    // 计时器指标
    private final Timer tokenValidationTimer;
    private final Timer tokenGenerationTimer;
    
    // 仪表盘指标
    private final Gauge activeTokens;
    private final Gauge tokenCacheHitRate;
    
    public SecurityMetricsCollector(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // 初始化指标
        tokenValidationSuccess = Counter.builder("security.token.validation")
            .tag("result", "success")
            .description("Token验证成功次数")
            .register(meterRegistry);
        
        tokenValidationFailure = Counter.builder("security.token.validation")
            .tag("result", "failure")
            .description("Token验证失败次数")
            .register(meterRegistry);
        
        tokenValidationTimer = Timer.builder("security.token.validation.time")
            .description("Token验证耗时")
            .register(meterRegistry);
        
        // 更多指标初始化...
    }
    
    /**
     * 记录Token验证指标
     */
    public void recordTokenValidation(boolean success, long durationMs) {
        if (success) {
            tokenValidationSuccess.increment();
        } else {
            tokenValidationFailure.increment();
        }
        
        tokenValidationTimer.record(durationMs, TimeUnit.MILLISECONDS);
        
        // 记录到日志
        log.info("Token验证: success={}, duration={}ms", success, durationMs);
    }
    
    /**
     * 获取安全仪表盘数据
     */
    public Map<String, Object> getSecurityDashboard() {
        Map<String, Object> dashboard = new HashMap<>();
        
        // 令牌统计
        dashboard.put("activeTokens", getActiveTokenCount());
        dashboard.put("tokensIssuedToday", getTokensIssuedToday());
        dashboard.put("tokenValidationRate", getTokenValidationRate());
        
        // 安全事件
        dashboard.put("failedAuthAttempts", getFailedAuthAttempts());
        dashboard.put("suspiciousActivities", getSuspiciousActivities());
        
        // 性能指标
        dashboard.put("avgTokenValidationTime", getAvgValidationTime());
        dashboard.put("p95TokenValidationTime", getP95ValidationTime());
        
        // 客户端统计
        dashboard.put("topClientsByToken", getTopClientsByTokenIssuance());
        dashboard.put("clientsWithMostFailures", getClientsWithMostFailures());
        
        return dashboard;
    }
}

/**
 * 安全事件处理器
 */
@Component
public class SecurityEventHandler {
    
    private final ApplicationEventPublisher eventPublisher;
    
    /**
     * 发布安全事件
     */
    public void publishSecurityEvent(SecurityEvent event) {
        eventPublisher.publishEvent(event);
        
        // 根据事件类型采取相应措施
        switch (event.getType()) {
            case TOKEN_COMPROMISED:
                handleTokenCompromised(event);
                break;
                
            case BRUTE_FORCE_ATTEMPT:
                handleBruteForceAttempt(event);
                break;
                
            case SUSPICIOUS_LOCATION:
                handleSuspiciousLocation(event);
                break;
                
            case RATE_LIMIT_EXCEEDED:
                handleRateLimitExceeded(event);
                break;
        }
    }
    
    /**
     * 处理令牌泄露事件
     */
    private void handleTokenCompromised(SecurityEvent event) {
        String token = event.getDetails().get("token");
        
        // 1. 将令牌加入黑名单
        blacklistToken(token);
        
        // 2. 通知用户
        notifyUser(event.getUserId(), "检测到异常登录,已保护您的账户");
        
        // 3. 记录安全日志
        logSecurityAlert("TOKEN_COMPROMISED", event);
        
        // 4. 触发令牌撤销
        revokeRelatedTokens(event.getUserId());
    }
    
    /**
     * 处理暴力破解尝试
     */
    private void handleBruteForceAttempt(SecurityEvent event) {
        String ipAddress = event.getDetails().get("ip_address");
        String clientId = event.getDetails().get("client_id");
        
        // 1. 临时封禁IP
        temporaryBlockIp(ipAddress, Duration.ofHours(1));
        
        // 2. 增加验证码要求
        enableCaptchaForClient(clientId);
        
        // 3. 发送安全警报
        sendSecurityAlert("暴力破解尝试检测", event);
    }
}

/**
 * 令牌生命周期管理
 */
@Service
public class TokenLifecycleManager {
    
    /**
     * 定期清理过期令牌
     */
    @Scheduled(cron = "0 0 2 * * ?")  // 每天凌晨2点执行
    public void cleanupExpiredTokens() {
        log.info("开始清理过期令牌");
        
        int cleaned = 0;
        
        // 清理Redis中的过期令牌
        cleaned += cleanupRedisTokens();
        
        // 清理数据库中的过期令牌
        cleaned += cleanupDatabaseTokens();
        
        // 清理黑名单中的过期条目
        cleaned += cleanupBlacklist();
        
        log.info("令牌清理完成,共清理{}个过期条目", cleaned);
    }
    
    /**
     * 令牌自动刷新
     */
    @Scheduled(fixedDelay = 300000)  // 每5分钟执行
    public void autoRefreshTokens() {
        // 查找即将过期的活跃令牌
        List<ExpiringToken> expiringTokens = findTokensExpiringSoon(300); // 5分钟内过期
        
        for (ExpiringToken token : expiringTokens) {
            try {
                // 自动刷新令牌
                refreshTokenIfNeeded(token);
            } catch (Exception e) {
                log.error("自动刷新令牌失败: {}", token.getTokenId(), e);
            }
        }
    }
    
    /**
     * 令牌使用分析
     */
    public TokenUsageAnalysis analyzeTokenUsage(String tokenId) {
        TokenUsageAnalysis analysis = new TokenUsageAnalysis();
        
        // 获取令牌使用记录
        List<TokenUsageRecord> records = getTokenUsageRecords(tokenId);
        
        // 分析使用模式
        analysis.setTotalUses(records.size());
        analysis.setFirstUseTime(records.get(0).getTimestamp());
        analysis.setLastUseTime(records.get(records.size() - 1).getTimestamp());
        
        // 检测异常模式
        analysis.setSuspiciousPatterns(detectSuspiciousPatterns(records));
        
        // 计算使用频率
        analysis.setUsageFrequency(calculateUsageFrequency(records));
        
        // 地理位置分析
        analysis.setLocationPatterns(analyzeLocations(records));
        
        return analysis;
    }
}

四、常见问题与解决方案

4.1 JWT常见安全问题及解决方案

java

复制

下载

复制代码
/**
 * JWT安全问题和解决方案
 */
public class JWTSecurityIssuesAndSolutions {
    
    /**
     * 问题1:令牌泄露后的撤销问题
     * 解决方案:使用短有效期 + 刷新令牌 + 黑名单
     */
    public class TokenRevocationSolution {
        
        public void handleTokenLeakage(String leakedToken) {
            // 1. 立即加入黑名单
            addToBlacklist(leakedToken);
            
            // 2. 撤销相关刷新令牌
            revokeRelatedRefreshTokens(leakedToken);
            
            // 3. 通知用户
            notifyUserOfSuspiciousActivity(leakedToken);
            
            // 4. 强制重新认证
            forceReauthenticationForUser(leakedToken);
        }
        
        /**
         * 增强的黑名单实现
         */
        public class EnhancedBlacklist {
            // 使用Bloom Filter进行高效存在性检查
            private BloomFilter<String> bloomFilter;
            
            // Redis存储详细信息
            private RedisTemplate<String, Object> redisTemplate;
            
            public boolean isBlacklisted(String token) {
                // 1. Bloom Filter快速检查
                if (!bloomFilter.mightContain(token)) {
                    return false;
                }
                
                // 2. Redis精确验证
                return redisTemplate.hasKey("blacklist:token:" + hashToken(token));
            }
            
            public void addToBlacklist(String token, String reason) {
                String hash = hashToken(token);
                String key = "blacklist:token:" + hash;
                
                // 存储详细信息
                Map<String, Object> info = new HashMap<>();
                info.put("token_hash", hash);
                info.put("blacklisted_at", System.currentTimeMillis());
                info.put("reason", reason);
                info.put("expires_at", System.currentTimeMillis() + 24 * 60 * 60 * 1000); // 24小时
                
                redisTemplate.opsForHash().putAll(key, info);
                redisTemplate.expire(key, 25, TimeUnit.HOURS); // 稍微延长
                
                // 添加到Bloom Filter
                bloomFilter.put(token);
            }
        }
    }
    
    /**
     * 问题2:JWT载荷篡改
     * 解决方案:强签名算法 + 加密
     */
    public class JWTTamperingSolution {
        
        /**
         * 使用非对称加密签名
         */
        public String signTokenWithRSA(JwtClaims claims) {
            return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.RS256, loadPrivateKey())
                .compact();
        }
        
        /**
         * JWE(JSON Web Encryption)加密
         */
        public String encryptToken(JwtClaims claims) {
            // 使用JWE标准加密整个JWT
            return Jwts.builder()
                .setClaims(claims)
                .encryptWith(
                    KeyAlgorithm.DIR,  // 直接加密
                    EncryptionAlgorithm.A256GCM) // AES-GCM加密
                .compact();
        }
        
        /**
         * 嵌套JWT:先签名再加密
         */
        public String createNestedJWT(JwtClaims claims) {
            // 1. 创建签名的JWT
            String signedJWT = signTokenWithRSA(claims);
            
            // 2. 加密签名的JWT
            return encryptPayload(signedJWT);
        }
    }
    
    /**
     * 问题3:重放攻击
     * 解决方案:JTI + 时间戳 + 使用次数限制
     */
    public class ReplayAttackSolution {
        
        public boolean preventReplayAttack(String token, HttpServletRequest request) {
            Claims claims = parseToken(token);
            
            // 1. 检查JTI(JWT ID)
            String jti = claims.getId();
            if (isJtiUsed(jti)) {
                return false; // JTI已使用
            }
            
            // 2. 检查时间窗口
            long iat = claims.getIssuedAt().getTime();
            long now = System.currentTimeMillis();
            if (now - iat > 5 * 60 * 1000) { // 5分钟窗口
                // 允许稍长时间,但需要额外验证
                return validateWithAdditionalChecks(token, request);
            }
            
            // 3. 记录JTI使用
            recordJtiUsage(jti, now);
            
            return true;
        }
        
        /**
         * 使用滑动窗口限制令牌使用频率
         */
        public class TokenUsageLimiter {
            
            public boolean canUseToken(String tokenId, int maxUsesPerMinute) {
                String key = "token_usage:" + tokenId;
                
                // 使用Redis sorted set记录使用时间戳
                long currentTime = System.currentTimeMillis();
                long oneMinuteAgo = currentTime - 60000;
                
                // 移除一分钟前的记录
                redisTemplate.opsForZSet().removeRangeByScore(key, 0, oneMinuteAgo);
                
                // 获取一分钟内的使用次数
                Long usageCount = redisTemplate.opsForZSet().zCard(key);
                
                if (usageCount >= maxUsesPerMinute) {
                    return false;
                }
                
                // 添加当前使用记录
                redisTemplate.opsForZSet().add(key, String.valueOf(currentTime), currentTime);
                redisTemplate.expire(key, 2, TimeUnit.MINUTES);
                
                return true;
            }
        }
    }
}

4.2 OAuth2.0常见攻击及防护

java

复制

下载

复制代码
/**
 * OAuth2.0安全威胁和防护措施
 */
public class OAuth2SecurityThreatsAndProtections {
    
    /**
     * 威胁1:授权码拦截攻击
     * 防护:PKCE + HTTPS + State参数
     */
    public class AuthorizationCodeInterceptionProtection {
        
        public void validateAuthorizationRequest(OAuth2Request request) {
            // 1. 强制使用PKCE(移动端和SPA)
            if (isPublicClient(request.getClientId())) {
                if (!request.getParameters().containsKey("code_challenge")) {
                    throw new InvalidRequestException("PKCE required for public clients");
                }
            }
            
            // 2. 验证State参数
            String state = request.getParameters().get("state");
            if (!isValidState(state)) {
                throw new InvalidRequestException("Invalid state parameter");
            }
            
            // 3. 验证重定向URI
            validateRedirectUri(request);
            
            // 4. 记录请求用于后续验证
            storeAuthorizationRequest(request);
        }
    }
    
    /**
     * 威胁2:令牌泄露
     * 防护:令牌绑定 + 短有效期 + 范围限制
     */
    public class TokenLeakageProtection {
        
        public OAuth2AccessToken createSecureToken(OAuth2Authentication authentication, 
                                                   HttpServletRequest request) {
            DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
                generateTokenValue());
            
            // 1. 短有效期
            token.setExpiration(new Date(System.currentTimeMillis() + 3600000)); // 1小时
            
            // 2. 最小权限原则
            token.setScope(restrictScopes(authentication.getOAuth2Request().getScope()));
            
            // 3. 令牌绑定
            String bindingId = createTokenBindingId(request);
            token.getAdditionalInformation().put("binding_id", bindingId);
            
            // 4. 使用声明
            token.getAdditionalInformation().put("usage_limit", 1000);
            
            return token;
        }
        
        /**
         * 验证令牌绑定的中间件
         */
        @Component
        public class TokenBindingMiddleware implements Filter {
            
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, 
                               FilterChain chain) throws IOException, ServletException {
                
                HttpServletRequest httpRequest = (HttpServletRequest) request;
                String authHeader = httpRequest.getHeader("Authorization");
                
                if (authHeader != null && authHeader.startsWith("Bearer ")) {
                    String token = authHeader.substring(7);
                    
                    // 验证令牌绑定
                    if (!validateTokenBinding(token, httpRequest)) {
                        ((HttpServletResponse) response).sendError(
                            HttpServletResponse.SC_UNAUTHORIZED, 
                            "Token binding validation failed");
                        return;
                    }
                }
                
                chain.doFilter(request, response);
            }
        }
    }
    
    /**
     * 威胁3:CSRF攻击
     * 防护:State参数 + 同源策略
     */
    public class CSRFProtection {
        
        /**
         * 生成和验证State参数
         */
        public class StateParameterManager {
            
            public String generateStateParameter(HttpServletRequest request) {
                // 使用加密的随机值 + 时间戳 + 用户会话
                String random = generateCryptographicallyRandomString();
                long timestamp = System.currentTimeMillis();
                String sessionId = request.getSession().getId();
                
                String rawState = random + "|" + timestamp + "|" + sessionId;
                
                // 加密State
                return encryptState(rawState);
            }
            
            public boolean validateStateParameter(String state, HttpServletRequest request) {
                try {
                    // 解密State
                    String decrypted = decryptState(state);
                    String[] parts = decrypted.split("\\|");
                    
                    if (parts.length != 3) {
                        return false;
                    }
                    
                    // 验证时间戳(防止重放)
                    long timestamp = Long.parseLong(parts[1]);
                    long currentTime = System.currentTimeMillis();
                    
                    if (currentTime - timestamp > 10 * 60 * 1000) { // 10分钟有效期
                        return false;
                    }
                    
                    // 验证会话
                    String sessionId = parts[2];
                    if (!sessionId.equals(request.getSession().getId())) {
                        return false;
                    }
                    
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }
        }
        
        /**
         * SPA应用的特殊防护
         */
        public class SPACSFRFProtection {
            
            // 使用双提交Cookie模式
            public void setupCSRFToken(HttpServletResponse response) {
                String csrfToken = generateCSRFToken();
                
                // 1. 设置HttpOnly Cookie(不可被JS读取)
                response.setHeader("Set-Cookie", 
                    "__Host-csrf_token=" + csrfToken + 
                    "; Path=/; Secure; HttpOnly; SameSite=Strict");
                
                // 2. 在响应头中返回CSRF Token(可被JS读取)
                response.setHeader("X-CSRF-Token", csrfToken);
            }
            
            public boolean validateCSRFToken(HttpServletRequest request) {
                // 从Cookie读取
                String cookieToken = getCookieValue(request, "__Host-csrf_token");
                
                // 从请求头读取
                String headerToken = request.getHeader("X-CSRF-Token");
                
                return cookieToken != null && cookieToken.equals(headerToken);
            }
        }
    }
    
    /**
     * 威胁4:开放重定向
     * 防护:严格的重定向URI验证
     */
    public class OpenRedirectProtection {
        
        public boolean validateRedirectUri(String redirectUri, ClientDetails client) {
            // 1. 检查是否在注册的重定向URI中
            if (!client.getRegisteredRedirectUri().contains(redirectUri)) {
                return false;
            }
            
            // 2. 验证协议(强制HTTPS)
            if (!redirectUri.startsWith("https://")) {
                // 允许localhost用于开发
                if (!redirectUri.startsWith("http://localhost")) {
                    return false;
                }
            }
            
            // 3. 防止开放重定向攻击
            if (isOpenRedirectVulnerable(redirectUri)) {
                return false;
            }
            
            // 4. 验证域名匹配
            return validateDomain(redirectUri, client);
        }
        
        private boolean isOpenRedirectVulnerable(String uri) {
            try {
                URL url = new URL(uri);
                
                // 检查是否有潜在危险的参数
                String query = url.getQuery();
                if (query != null) {
                    // 防止像 redirect_uri=https://evil.com 这样的参数
                    if (query.toLowerCase().contains("redirect_uri=") ||
                        query.toLowerCase().contains("url=") ||
                        query.toLowerCase().contains("return=")) {
                        return true;
                    }
                }
                
                // 检查是否有JavaScript协议
                if (uri.toLowerCase().startsWith("javascript:")) {
                    return true;
                }
                
                // 检查是否有数据协议
                if (uri.toLowerCase().startsWith("data:")) {
                    return true;
                }
                
            } catch (MalformedURLException e) {
                return true;
            }
            
            return false;
        }
    }
}

五、面试要点总结

5.1 核心概念记忆点

text

复制

下载

复制代码
JWT令牌验证:
1. 结构:Header.Payload.Signature三部分
2. 优点:无状态、自包含、易于跨域
3. 安全要点:强签名、短有效期、黑名单机制
4. API网关作用:集中验证、传递用户信息、限流防护

OAuth2.0授权码流程:
1. 授权请求:client_id, redirect_uri, response_type=code, scope, state
2. 用户授权:登录并同意授权
3. 获取授权码:重定向到redirect_uri?code=xxx&state=xxx
4. 交换令牌:client_id, client_secret, code, grant_type=authorization_code
5. 获取访问令牌:返回access_token, refresh_token, expires_in

安全增强机制:
1. PKCE:防止授权码拦截攻击
2. 令牌绑定:绑定到TLS会话
3. State参数:防止CSRF攻击
4. 范围限制:最小权限原则
5. 审计日志:完整的安全审计

5.2 常见面试问题

Q1:JWT和Session的区别是什么?

text

复制

下载

复制代码
答:主要区别如下:

1. 存储位置:
   - Session:服务器端存储(Redis/数据库)
   - JWT:客户端存储(LocalStorage/Cookie)

2. 状态管理:
   - Session:有状态,服务器需要维护会话状态
   - JWT:无状态,令牌自包含所有信息

3. 扩展性:
   - Session:在集群环境下需要会话共享
   - JWT:天然支持水平扩展

4. 安全性:
   - Session:容易受到CSRF攻击
   - JWT:需要防范XSS攻击

5. 性能:
   - Session:每次请求需要查询会话存储
   - JWT:只需要验证签名,但令牌体积较大

选择建议:
- 需要完全控制会话:Session
- 需要无状态扩展:JWT
- 混合方案:JWT + 短有效期 + 黑名单

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

Q2:OAuth2.0授权码模式为什么最安全?

text

复制

下载

复制代码
答:授权码模式最安全的原因:

1. 双重验证:
   - 第一步:用户身份验证(浏览器)
   - 第二步:客户端凭证验证(后端)

2. 令牌不经过浏览器:
   - 授权码通过浏览器传递
   - 访问令牌通过后端通道交换
   - 避免令牌泄露给恶意JavaScript

3. 支持PKCE:
   - 为公共客户端(SPA、移动应用)提供额外保护
   - 防止授权码拦截攻击

4. 灵活性:
   - 支持多种客户端类型
   - 支持范围控制和同意页面
   - 支持撤销和刷新机制

安全增强:
1. 强制使用HTTPS
2. 验证重定向URI
3. 使用State参数防CSRF
4. 实施PKCE
5. 短授权码有效期

Q3:API网关在OAuth2.0架构中的角色是什么?

text

复制

下载

复制代码
答:API网关在OAuth2.0架构中扮演多重角色:

1. 安全边界:
   - 统一身份验证入口
   - 集中令牌验证
   - 防止直接访问后端服务

2. 令牌中继:
   - 接收Bearer令牌
   - 验证后传递给下游服务
   - 添加用户上下文信息

3. 协议转换:
   - 支持多种认证协议(JWT、OAuth2、SAML)
   - 统一转换为内部格式
   - 简化服务集成

4. 安全增强:
   - 速率限制
   - IP白名单/黑名单
   - 请求审计
   - 防止DDoS攻击

5. 用户体验:
   - 单点登录(SSO)
   - 统一的错误处理
   - 会话管理

实现模式:
1. 透明网关:只传递令牌
2. 转换网关:令牌转换和增强
3. 代理网关:完全处理认证授权

Q4:如何实现JWT令牌的主动撤销?

text

复制

下载

复制代码
答:实现JWT令牌主动撤销的几种方案:

方案1:短有效期 + 刷新令牌
- Access Token:短有效期(15分钟)
- Refresh Token:长有效期(7天),可撤销
- 泄露时:撤销Refresh Token

方案2:令牌黑名单
- 将撤销的令牌加入黑名单
- 验证时检查黑名单
- 实现方式:Redis Bloom Filter

方案3:令牌版本控制
- 用户令牌版本号
- 版本变更时使旧令牌失效
- 在JWT Claims中存储版本号

方案4:状态化JWT
- 维护令牌状态服务
- 每次验证检查状态
- 失去无状态优势但更可控

方案5:滑动会话
- 每次请求更新令牌
- 活跃会话持续有效
- 闲置会话自动过期

最佳实践组合:
1. 短有效期Access Token(15-30分钟)
2. 可撤销Refresh Token
3. 关键操作需要重新认证
4. 重要变更时全局登出
5. 实时监控异常使用

这个全面的API网关JWT和OAuth2.0实现方案,涵盖了从基础原理到高级安全实践的所有关键点。掌握这些知识,无论是面试还是实际工作,都能游刃有余地设计和实现安全的API网关认证授权系统。

相关推荐
tqs_123451 小时前
单例模式代码
java·开发语言·单例模式
C系语言2 小时前
安装Python版本opencv命令
开发语言·python·opencv
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 多语言国际化实现
android·java·开发语言·javascript·flutter·游戏
CHU7290352 小时前
淘宝扭蛋机小程序前端功能详解:以交互设计赋能趣味体验
java·前端·小程序·php
SunnyDays10112 小时前
Java 实战:高效合并多个 Word 文档
java·合并word文档
靠沿2 小时前
Java数据结构初阶——七大排序算法及“非比较”排序
java·数据结构·排序算法
源代码•宸2 小时前
Leetcode—146. LRU 缓存【中等】(哈希表+双向链表)
后端·算法·leetcode·缓存·面试·golang·lru
少控科技2 小时前
QT新手日记 029 - QT所有模块
开发语言·qt
wjs20242 小时前
解释器模式
开发语言