如何在Spring Boot中验证Google OAuth2.0访问令牌,保障资源仅合法用户可访问
验证Google Access Token的Spring Boot实现方案
核心思路
Google颁发的Access Token是JWT格式,Spring Boot验证令牌主要有两种方式:一是本地验证JWT签名(通过Google公开的公钥),二是调用Google官方的令牌验证接口。前者无需额外网络请求,效率更高;后者依赖Google服务,但验证逻辑更直观。
具体实现步骤
1. 添加依赖
在pom.xml中引入Spring Security OAuth2资源服务器相关依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency>
2. 配置文件配置
在application.yml中指定Google的JWT验证参数,Spring会自动处理公钥获取和签名验证:
spring: security: oauth2: resourceserver: jwt: issuer-uri: https://accounts.google.com jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
issuer-uri:指定令牌的签发者,必须和Google JWT中的iss字段一致jwk-set-uri:Google提供的公钥集合地址,Spring会定期拉取公钥用于验证JWT签名
3. 安全规则配置
创建Spring Security配置类,强制所有API请求都需要通过JWT验证:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(new GoogleJwtConverter()) // 可选:自定义用户信息转换 ) ); return http.build(); } }
4. 可选:自定义JWT用户信息转换
如果需要从Google的JWT中提取邮箱、姓名等用户信息并封装到认证对象中,可以自定义转换器:
import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import java.util.Collections; public class GoogleJwtConverter implements Converter<Jwt, AbstractAuthenticationToken> { private final JwtGrantedAuthoritiesConverter defaultConverter = new JwtGrantedAuthoritiesConverter(); @Override public AbstractAuthenticationToken convert(Jwt jwt) { var authorities = defaultConverter.convert(jwt); // 从JWT Claim中提取用户邮箱作为认证主体标识 String userEmail = jwt.getClaimAsString("email"); return new JwtAuthenticationToken(jwt, authorities, userEmail); } }
5. 备选方案:调用Google验证接口
如果不想用本地JWT验证,可以直接调用Google的令牌验证接口,在自定义过滤器中实现验证逻辑:
import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class GoogleTokenValidator { private final RestTemplate restTemplate = new RestTemplate(); private static final String VALIDATE_URL = "https://oauth2.googleapis.com/tokeninfo?access_token="; // 替换为你的Google应用客户端ID private static final String YOUR_CLIENT_ID = "your-google-client-id"; public boolean isValidToken(String accessToken) { try { String requestUrl = VALIDATE_URL + accessToken; GoogleTokenInfo tokenInfo = restTemplate.getForObject(requestUrl, GoogleTokenInfo.class); // 验证令牌的受众是否匹配你的应用ID,防止跨应用滥用 return tokenInfo != null && YOUR_CLIENT_ID.equals(tokenInfo.getAud()); } catch (Exception e) { return false; } } // 对应Google验证接口的响应字段 public static class GoogleTokenInfo { private String aud; private String email; private long exp; // getter和setter方法 public String getAud() { return aud; } public void setAud(String aud) { this.aud = aud; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public long getExp() { return exp; } public void setExp(long exp) { this.exp = exp; } } }
然后添加自定义过滤器到安全链中:
// 在SecurityConfig的securityFilterChain方法中添加 http.addFilterBefore(new TokenValidationFilter(tokenValidator), UsernamePasswordAuthenticationFilter.class);
自定义过滤器示例:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; public class TokenValidationFilter extends OncePerRequestFilter { private final GoogleTokenValidator tokenValidator; public TokenValidationFilter(GoogleTokenValidator tokenValidator) { this.tokenValidator = tokenValidator; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing or invalid Authorization header"); return; } String accessToken = authHeader.substring(7); if (!tokenValidator.isValidToken(accessToken)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid access token"); return; } // 验证通过,将用户信息存入安全上下文 String userEmail = tokenValidator.getTokenInfo(accessToken).getEmail(); var authentication = new UsernamePasswordAuthenticationToken(userEmail, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); } }
注意事项
- 前端请求API时,必须在请求头中携带
Authorization: Bearer <Google Access Token> - 本地JWT验证方式更适合生产环境,无需依赖Google接口的可用性,但要确保服务器能正常访问Google的公钥地址
- 无论哪种方式,都建议额外验证令牌的
aud字段(受众),确保令牌是颁发给你的应用的
内容的提问来源于stack exchange,提问作者Amit Bhatiwal
相关产品推荐
相关产品推荐

