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

Java实现Steam OpenID登录遇is_valid:false问题咨询及方案寻求

Steam OpenID登录随机返回is_valid:false的问题排查与替代方案

问题描述

我用Java实现了Steam OpenID登录功能,但目前遇到随机出现is_valid:false响应的问题,即便传递的参数完全准确也会触发。我的需求是:

  1. 确保参数准确时能稳定拿到is_valid:true的响应;
  2. 如果问题无法解决,希望了解其他Steam登录的实现方式。

登录校验核心代码

@GetMapping("/login/check")
public String startSession(
    @RequestParam(value = "openid.ns") String openidNs,
    @RequestParam(value = "openid.mode") String openidMode,
    @RequestParam(value = "openid.op_endpoint") String openidOpEndpoint,
    @RequestParam(value = "openid.claimed_id") String openidClaimedId,
    @RequestParam(value = "openid.identity") String openidIdentity,
    @RequestParam(value = "openid.return_to") String openidReturnTo,
    @RequestParam(value = "openid.response_nonce") String openidResponseNonce,
    @RequestParam(value = "openid.assoc_handle") String openidAssocHandle,
    @RequestParam(value = "openid.signed") String openidSigned,
    @RequestParam(value = "openid.sig") String openidSig,
    HttpServletRequest request,
    HttpServletResponse response) throws InterruptedException {


    log.info("openidResponseNonce = {} ", openidResponseNonce);

    ResponseEntity<String> block = WebClient.create("https://steamcommunity.com")
        .get()
        .uri(uriBuilder -> uriBuilder
            .path("/openid/login")
            .queryParam("openid.ns", openidNs)
            .queryParam("openid.mode", "check_authentication")
            .queryParam("openid.op_endpoint", openidOpEndpoint)
            .queryParam("openid.claimed_id", openidClaimedId)
            .queryParam("openid.identity", openidIdentity)
            .queryParam("openid.return_to", openidReturnTo)
            .queryParam("openid.response_nonce", openidResponseNonce)
            .queryParam("openid.assoc_handle", openidAssocHandle)
            .queryParam("openid.signed", openidSigned)
            .queryParam("openid.sig", openidSig)
            .build()
        )
        .retrieve()
        .toEntity(String.class)
        .block();

   
    boolean isTrue = Objects.requireNonNull(block).getBody().contains("true");

    log.info("isTrue = {} ", isTrue);
    if (!isTrue) {
        return "redirect:https://steamcommunity.com/openid/login?openid.ns=http://specs.openid.net/auth/2.0&openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.return_to=" + baseUrl + "/login/check&openid.realm=" + baseUrl + "&openid.mode=checkid_setup";
    }

    Pattern pattern = Pattern.compile("\\d+");
    Matcher matcher = pattern.matcher(openidIdentity);
    String steamId;

    if (matcher.find()) {
        steamId = matcher.group();
    } else {
        throw new IllegalArgumentException();
    }

    if (!userService.findBySteamId(steamId).isPresent()) {
        userService.create(getUserInfo(steamId));
    }

    SecurityUser user = SecurityUser.builder()
        .id(userService.findBySteamId(steamId).get().getId())
        .username(userService.findBySteamId(steamId).get().getUsername())
        .steamId(steamId)
        .build();

    Authentication authentication = new OAuth2AuthenticationToken(user, user.getAuthorities(), "steam");
    SecurityContextHolder.getContext().setAuthentication(authentication);

    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate();
    }
    // 新建会话
    session = request.getSession(true);
    session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());

    // 将会话ID设置到Cookie中
    Cookie cookie = new Cookie("JSESSIONID", session.getId());
    cookie.setHttpOnly(true);
    cookie.setPath("/");
    response.addCookie(cookie);

    return "redirect:/user/checkFirstVisit";

}

一、解决随机is_valid:false的问题

1. 切换请求方式并校验参数一致性

  • Steam官方推荐用POST请求传递校验参数,GET请求可能因URL长度限制、编码异常导致参数篡改,尤其是openid.return_to、openid.sig这类含特殊字符的参数;
  • 确保openid.return_to与跳转Steam登录时传递的地址完全一致,包括协议(http/https)、域名、路径和参数,不能有任何修改。

2. 规范Nonce处理

  • openid.response_nonce包含时间戳,Steam会校验其是否在有效时间窗口内(通常几分钟),需优化请求延迟避免超时;
  • 记录已使用的nonce,禁止重复提交同一nonce触发校验失败。

3. 修复WebClient配置与响应解析

  • 给WebClient添加超时配置,避免超时重试导致重复提交:
WebClient webClient = WebClient.builder()
        .baseUrl("https://steamcommunity.com")
        .clientConnector(new ReactorClientHttpConnector(
                HttpClient.create().responseTimeout(Duration.ofSeconds(10))
        ))
        .build();
  • 替换当前模糊的响应判断逻辑,按Steam响应格式精准解析is_valid字段:
String responseBody = block.getBody();
boolean isTrue = responseBody.lines()
        .map(String::trim)
        .anyMatch(line -> line.equals("is_valid:true"));

二、替代Steam登录实现方式

1. Spring Security OAuth2集成Steam

利用Spring Security的OAuth2客户端能力快速集成,无需手动处理OpenID流程:

  • 注册Steam开发者账号获取API Key;
  • 配置自定义OAuth2客户端:
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .oauth2Login(oauth2 -> oauth2
                        .clientRegistrationRepository(clientRegistrationRepository())
                        .userInfoEndpoint(userInfo -> userInfo
                                .customUserType(SteamUser.class, "steam")
                        )
                );
        return http.build();
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(steamClientRegistration());
    }

    private ClientRegistration steamClientRegistration() {
        return ClientRegistration.withRegistrationId("steam")
                .clientId("YOUR_STEAM_API_KEY")
                .clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("{baseUrl}/login/oauth2/code/steam")
                .authorizationUri("https://steamcommunity.com/openid/login")
                .tokenUri("https://steamcommunity.com/openid/login")
                .userInfoUri("https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={client_id}&steamids={user_id}")
                .userNameAttributeName("steamid")
                .clientName("Steam")
                .build();
    }
}

2. 直接调用Steam Web API

跳过OpenID,用Steam官方Web API实现登录:

  1. 引导用户访问Steam授权页面获取授权凭证;
  2. 结合API Key调用接口换取用户SteamID;
  3. 调用GetPlayerSummaries接口获取用户信息完成登录流程。

内容的提问来源于stack exchange,提问作者Hyeok Geun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 15:04:50