You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:07:42