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

Spring Boot结合Google OAuth2实现Android客户端有状态会话身份认证的问题

Spring Boot结合Google OAuth2实现Android客户端有状态会话身份认证的问题

老哥,你的场景我太熟了——Android端自己搞定Google登录拿到token,后端要接这个token做有状态会话,不用密码,还纠结oauth2login的问题是吧?别慌,我给你捋得明明白白:

先搞懂:为啥oauth2login不适合你

那个http.oauth2login()是给Web应用设计的授权码流,后端会自己跳转到Google的授权页引导用户登录,完全和你现在Android端先拿token的场景不搭,所以直接把这个配置从你的filterChain里删掉就行,不用在这上面浪费时间。

Android端该怎么传token?

标准且最省心的做法是:把Google返回的ID Token(划重点,是ID Token不是Access Token!Access Token是用来调Google API的,身份认证用ID Token才对)放在请求头里,格式如下:
Authorization: Bearer <你的Google ID Token>
别搞什么把token放请求体里的操作,用请求头是行业惯例,后端解析也方便。

后端具体实现步骤

1. 添依赖&配置Google JWT验证

先在你的pom.xml(或者Gradle配置)里加上spring-boot-starter-security和spring-boot-starter-oauth2-resource-server这俩依赖,它们会帮你搞定JWT的签名验证,不用自己写复杂的加密逻辑。

然后在application.yml里配置Google的JWT解析规则:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com
          jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs

这个配置会自动拉取Google的公钥来验证ID Token的合法性,还会自动校验token的发行方是不是Google的账号服务器,省了你好多事。

2. 写自定义的认证接口

不用搞复杂的过滤器,先写个简单的POST接口来接收Android端的token,验证通过后直接创建有状态会话:

@RestController
@RequestMapping("/api/auth")
public class GoogleAuthController {

    private final JwtDecoder jwtDecoder;

    // 构造注入JwtDecoder,Spring会自动用我们刚才的配置实例化它
    public GoogleAuthController(JwtDecoder jwtDecoder) {
        this.jwtDecoder = jwtDecoder;
    }

    @PostMapping("/google")
    public ResponseEntity<?> authenticateWithGoogleToken(@RequestHeader("Authorization") String authHeader,
                                                        HttpServletRequest request) {
        // 先校验请求头格式对不对
        if (!authHeader.startsWith("Bearer ")) {
            return ResponseEntity.badRequest().body("授权头格式错误,必须以Bearer开头");
        }
        // 提取出真正的ID Token
        String idToken = authHeader.substring(7);

        try {
            // 解析并验证ID Token,不合法的话会直接抛JwtException
            Jwt jwt = jwtDecoder.decode(idToken);
            
            // 额外加个安全校验:确保这个token是发给你的Android应用的
            String yourAndroidClientId = "这里替换成你在Google Cloud控制台给Android应用创建的Client ID";
            if (!jwt.getAudience().contains(yourAndroidClientId)) {
                throw new JwtException("该Token不属于你的应用,禁止使用");
            }

            // 从token里提取用户核心信息,sub是Google给每个用户分配的唯一不变ID
            String userId = jwt.getSubject();
            String userEmail = jwt.getClaimAsString("email");
            String userName = jwt.getClaimAsString("name");

            // 创建已认证的Authentication对象,把它塞进SecurityContext
            List<GrantedAuthority> userAuthorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
            Authentication authenticatedUser = new UsernamePasswordAuthenticationToken(
                    userId, null, userAuthorities
            );
            SecurityContextHolder.getContext().setAuthentication(authenticatedUser);

            // 触发Spring创建有状态会话,默认就是有状态的,这里手动拿到会话可以存点用户信息方便后续使用
            HttpSession userSession = request.getSession(true);
            userSession.setAttribute("userDetails", Map.of(
                    "userId", userId,
                    "email", userEmail,
                    "nickname", userName
            ));

            return ResponseEntity.ok("登录成功,会话已创建");
        } catch (JwtException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token无效或已过期:" + e.getMessage());
        }
    }
}

3. 配置SecurityFilterChain,搞定权限和会话

把没用的oauth2login去掉,配置好权限规则和会话策略:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 如果Android端用Cookie管理会话,生产环境建议开启CSRF防护,开发阶段可以先禁用省麻烦
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/google").permitAll() // 认证接口必须放开,让Android能调用
                        .anyRequest().authenticated() // 其他所有接口都需要认证才能访问
                )
                // 明确配置有状态会话,默认就是IF_REQUIRED(需要时创建),写出来更清晰
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                        .maximumSessions(1) // 可选配置:限制同一用户只能同时登录一个设备
                );
        return http.build();
    }

    // 注入符合Google配置的JwtDecoder
    @Bean
    public JwtDecoder jwtDecoder() {
        return JwtDecoders.fromIssuerLocation("https://accounts.google.com");
    }
}

最后,Android端的后续请求处理

当Android端调用完上面的认证接口后,后端会在响应头里返回Set-Cookie,里面包含JSESSIONID(Spring默认的会话ID)。你用OkHttp或者Retrofit的话,只要配置自动管理Cookie,比如给OkHttp加个CookieJar实现,后续所有请求都会自动带上这个Cookie,后端就能通过JSESSIONID找到对应的会话,自动识别用户身份,根本不用每次再传Google的token了。

哦对了,再补个小提醒:别用Google的Access Token来做后端认证!Access Token是用来访问Google自身API的,有效期短,而且结构不适合做身份校验,ID Token才是专门用来做跨应用身份认证的标准凭证。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 13:08:05