一、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网关认证授权系统。