diff --git a/fruitshorts-app-api/src/main/resources/application.yml b/fruitshorts-app-api/src/main/resources/application.yml index 908fa4a..7e088c3 100644 --- a/fruitshorts-app-api/src/main/resources/application.yml +++ b/fruitshorts-app-api/src/main/resources/application.yml @@ -65,6 +65,11 @@ fruitshorts: # 仅作 sys_config 缺失/非法时的兜底;运行时优先读取后台「系统配置」 register-max-per-ip-per-day: ${REGISTER_MAX_PER_IP_PER_DAY:5} register-max-per-device-per-day: ${REGISTER_MAX_PER_DEVICE_PER_DAY:3} + google: + # Google 登录允许的客户端 id(逗号分隔)。idToken 的 aud 随平台不同: + # iOS 为 iOS 客户端 id,Android 为 serverClientId 对应的 Web 客户端 id。 + # 客户端 id 属公开信息,不是密钥;生产可用 GOOGLE_CLIENT_IDS 覆盖。 + client-ids: ${GOOGLE_CLIENT_IDS:11115008580-51gv4sj2c78rud6n127g3shu4d2n96un.apps.googleusercontent.com,11115008580-dbqe47fhp5560nhrocrppiq7hcnlvl1s.apps.googleusercontent.com,11115008580-1v5pt5mfgbbuba74ebia6hngj618ne3a.apps.googleusercontent.com} interaction: # MACHINE_FIRST:机审通过即发布,命中 REVIEW 级敏感词才转人工 # PRE_REVIEW :全部先审后发(生产建议) diff --git a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/config/GoogleAuthProperties.java b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/config/GoogleAuthProperties.java index 9025582..756bc29 100644 --- a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/config/GoogleAuthProperties.java +++ b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/config/GoogleAuthProperties.java @@ -6,20 +6,25 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + /** * Google 登录配置。 * *

凭据只从配置/环境变量读,不入库、不提交到仓库: *

- * fruitshorts.auth.google.client-id: 1234567890-xxx.apps.googleusercontent.com
+ * fruitshorts.auth.google.client-ids: 1234-ios.apps.googleusercontent.com,1234-web.apps.googleusercontent.com
  * 
- * 未配置 clientId 时,Google 登录接口明确返回"未配置"错误,不会静默放行。 + * 客户端各平台拿到的 idToken,其 audience 并不相同:iOS 是 iOS 客户端 id, + * Android 是 serverClientId 对应的 Web 客户端 id,因此这里必须允许配置多个。 + * 未配置时,Google 登录接口明确返回"未配置"错误,不会静默放行。 */ @Data @Component @ConfigurationProperties(prefix = "fruitshorts.auth.google") public class GoogleAuthProperties { - /** Google OAuth 客户端 id,用于校验 idToken 的 audience。 */ - private String clientId = ""; + /** 允许的 Google OAuth 客户端 id 列表,用于校验 idToken 的 audience。 */ + private List clientIds = new ArrayList<>(); } diff --git a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/GoogleIdTokenVerifier.java b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/GoogleIdTokenVerifier.java index f10d6d6..a4b7673 100644 --- a/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/GoogleIdTokenVerifier.java +++ b/fruitshorts-module-user/src/main/java/com/fruitshorts/user/service/GoogleIdTokenVerifier.java @@ -19,6 +19,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.List; /** * Google idToken 校验器。 @@ -45,11 +46,14 @@ public class GoogleIdTokenVerifier { /** * 校验 idToken 并返回 Google 用户信息。 * + *

audience 按平台不同而不同(iOS 用 iOS 客户端 id,Android 用 Web 客户端 id), + * 因此只要命中配置列表里的任意一个即视为合法,同时拦截其他应用盗用令牌。 + * * @param idToken 客户端从 Google 拿到的 idToken */ public GoogleUser verify(String idToken) { - String clientId = properties.getClientId(); - if (!StringUtils.hasText(clientId)) { + List clientIds = properties.getClientIds(); + if (clientIds == null || clientIds.isEmpty()) { throw new ServiceException(ErrorCode.OPERATION_NOT_ALLOWED, "Google 登录未配置"); } if (!StringUtils.hasText(idToken)) { @@ -58,7 +62,7 @@ public class GoogleIdTokenVerifier { JsonNode body = fetchTokenInfo(idToken); String aud = body.path("aud").asText(""); String sub = body.path("sub").asText(""); - if (!clientId.equals(aud) || !StringUtils.hasText(sub)) { + if (!clientIds.contains(aud) || !StringUtils.hasText(sub)) { throw new ServiceException(ErrorCode.UNAUTHORIZED, "Google 令牌无效"); } return new GoogleUser(sub, body.path("email").asText(""),