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

Spring Boot应用程序化获取OAuth2.0访问令牌(无需application.yaml)

程序化获取OAuth2访问令牌的实现方案

Got it, let's tackle this problem. Right now you're using @EnableOAuth2Sso which is built for browser-based Authorization Code Grant flow—perfect for end users clicking through login pages, but not ideal when you need to automate token retrieval without manual browser steps. Here are a couple of solid approaches depending on what your OAuth2 server supports:


方案一:用密码授权模式(最直接)

If your OAuth2 server supports the Password Grant Type (this is only safe for trusted internal clients, since you'll handle raw user credentials), you can skip the browser redirect entirely and fetch a token directly with a username/password.

1. 更新配置文件

先调整application.yaml,加入密码模式所需的客户端信息:

spring:
  security:
    oauth2:
      client:
        registration:
          internal-client:
            client-id: your-client-id-here
            client-secret: your-client-secret-here
            authorization-grant-type: password
            scope: openid,profile,email  # 匹配你的认证服务器要求的权限范围
        provider:
          custom-auth-provider:
            token-uri: https://your-auth-server.com/oauth2/token  # 你的实际令牌端点地址

2. 编写令牌获取工具类

创建一个简单的工具类,用RestTemplate处理HTTP请求:

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class OAuth2TokenFetcher {

    public static String getAccessToken(String tokenUri, String clientId, String clientSecret, String username, String password) throws Exception {
        RestTemplate restTemplate = new RestTemplate();
        ObjectMapper objectMapper = new ObjectMapper();

        // 设置表单编码的请求头(OAuth2令牌端点要求的格式)
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        // 构造请求参数
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", "password");
        params.add("client_id", clientId);
        params.add("client_secret", clientSecret);
        params.add("username", username);
        params.add("password", password);
        params.add("scope", "openid profile email"); // 匹配服务器要求的权限范围

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

        // 发送请求并从JSON响应中解析令牌
        String rawResponse = restTemplate.postForObject(tokenUri, request, String.class);
        JsonNode responseJson = objectMapper.readTree(rawResponse);
        
        return responseJson.get("access_token").asText();
    }
}

注意事项

  • 仅在你完全信任客户端(比如内部后端服务)时使用此方案,向不可信应用暴露用户凭证是严重的安全风险。
  • 确认你的OAuth2服务器已开启密码授权模式——很多服务器为了安全默认禁用此模式。

方案二:模拟授权码流程(无浏览器)

如果你的服务器仅支持授权码模式(且无法开启密码模式),可以程序化模拟浏览器的流程:先通过模拟用户登录获取授权码,再用授权码换取令牌。

1. 获取授权码

首先需要模拟用户登录获取会话Cookie,再请求授权码(注意:此逻辑依赖你的认证服务器登录端点的结构):

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

public class OAuth2AuthCodeSimulator {

    public static String getAuthorizationCode(String loginUri, String authUri, String clientId, String redirectUri, String username, String password) {
        RestTemplate restTemplate = new RestTemplate();

        // 第一步:模拟用户登录获取会话Cookie
        HttpHeaders loginHeaders = new HttpHeaders();
        loginHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> loginParams = new LinkedMultiValueMap<>();
        loginParams.add("username", username);
        loginParams.add("password", password);
        
        // 发送登录请求——RestTemplate会自动存储会话Cookie供后续请求使用
        restTemplate.postForObject(loginUri, new HttpEntity<>(loginParams, loginHeaders), String.class);

        // 第二步:请求授权码(使用存储的会话Cookie)
        String authRequestUrl = String.format(
            "%s?client_id=%s&redirect_uri=%s&response_type=code&scope=openid profile email",
            authUri, clientId, redirectUri
        );

        // 捕获包含授权码的重定向URL
        String redirectUrl = restTemplate.execute(authRequestUrl, HttpMethod.GET, null, response -> {
            return response.getHeaders().getLocation().toString();
        });

        // 从重定向URL中提取授权码
        return redirectUrl.split("code=")[1].split("&")[0];
    }

    // 第三步:用授权码换取访问令牌
    public static String exchangeCodeForToken(String tokenUri, String clientId, String clientSecret, String code, String redirectUri) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        
        MultiValueMap<String, String> tokenParams = new LinkedMultiValueMap<>();
        tokenParams.add("grant_type", "authorization_code");
        tokenParams.add("client_id", clientId);
        tokenParams.add("client_secret", clientSecret);
        tokenParams.add("code", code);
        tokenParams.add("redirect_uri", redirectUri);

        HttpEntity<MultiValueMap<String, String>> tokenRequest = new HttpEntity<>(tokenParams, headers);
        String rawResponse = restTemplate.postForObject(tokenUri, tokenRequest, String.class);
        
        // 从响应中解析access_token(逻辑同密码模式示例)
        return rawResponse;
    }
}

注意事项

  • 此方案更脆弱,因为它依赖认证服务器的登录流程和Cookie处理逻辑,一旦服务器修改相关机制,代码可能失效。
  • 部分服务器会在登录时要求CSRF令牌,你需要先从登录页面HTML中获取CSRF令牌,再提交登录请求。

方案三:使用Spring Security内置工具(Spring Boot项目最推荐)

如果你想贴合Spring生态,避免手动编写RestTemplate逻辑,可以用OAuth2AuthorizedClientManager来处理令牌获取:

import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.stereotype.Component;

@Component
public class SpringOAuth2TokenService {

    private final OAuth2AuthorizedClientManager authorizedClientManager;

    // 注入Spring内置的客户端注册和存储Bean
    public SpringOAuth2TokenService(ClientRegistrationRepository clientRegistrationRepository,
                                   OAuth2AuthorizedClientRepository authorizedClientRepository) {
        OAuth2AuthorizedClientProvider provider = OAuth2AuthorizedClientProviderBuilder.builder()
                .password() // 启用密码授权支持
                .refreshToken() // 令牌过期时自动刷新
                .build();

        this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        this.authorizedClientManager.setAuthorizedClientProvider(provider);
    }

    public String getAccessToken(String clientRegistrationId, String username, String password) {
        OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
                .principal(username)
                .attributes(attrs -> {
                    attrs.put(org.springframework.security.oauth2.client.OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
                    attrs.put(org.springframework.security.oauth2.client.OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
                })
                .build();

        OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(request);
        return authorizedClient.getAccessToken().getTokenValue();
    }
}

这种方式会帮你处理所有HTTP细节、令牌刷新和会话管理,比手动编写RestTemplate代码简洁可靠得多。


内容的提问来源于stack exchange,提问作者smruti ranjan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:41:17