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

Spring Boot后端如何通过用户名密码通用认证OAuth SSO?

后端OIDC令牌管理方案(适配Keycloak,无前端场景)

核心思路

采用OIDC标准的资源所有者密码凭证授权(ROPC)模式,通过.well-known/openid-configuration自动发现Keycloak的OIDC端点,彻底避免硬编码接口地址。核心流程:

  • 从发现文档自动拉取令牌端点token_endpoint
  • 使用预配置的用户名+密码请求初始access_token与refresh_token
  • 解析access_token的exp过期字段,提前触发刷新逻辑
  • 用refresh_token请求新的令牌对,循环维护有效令牌

Java 实现示例(Spring Security OAuth2)

Spring Security内置OIDC发现与令牌管理能力,无需手动处理HTTP请求:

1. 依赖配置(Maven)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

2. 配置文件(application.yml)

spring:
  security:
    oauth2:
      client:
        registration:
          keycloak-ropc:
            client-id: your-client-id
            client-secret: your-client-secret
            authorization-grant-type: password
            scope: openid, offline_access # offline_access用于获取refresh_token
        provider:
          keycloak-ropc:
            issuer-uri: https://your-keycloak-domain/auth/realms/your-realm
            # 自动从issuer-uri/.well-known/openid-configuration拉取端点

3. 令牌管理代码

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.security.oauth2.core.endpoint.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationContext;
import org.springframework.stereotype.Component;

@Component
public class TokenManager {
    private final OAuth2AuthorizedClientManager authorizedClientManager;

    public TokenManager(ClientRegistrationRepository clientRegistrationRepository,
                        OAuth2AuthorizedClientRepository authorizedClientRepository) {
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .password()
                .refreshToken()
                .build();

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

    // 获取有效令牌(自动刷新即将过期的access_token)
    public String getValidAccessToken(String username, String password) {
        OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("keycloak-ropc")
                .principal(username)
                .attributes(attrs -> {
                    attrs.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
                    attrs.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
                })
                .build();

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

Python 实现示例(轻量手动实现)

适合小型后端服务,用开源工具包快速实现:

1. 依赖安装

pip install requests python-jose[cryptography]

2. 令牌管理代码

import requests
from jose import jwt
from datetime import datetime, timedelta

class KeycloakTokenManager:
    def __init__(self, issuer_url, client_id, client_secret, username, password):
        self.issuer_url = issuer_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.username = username
        self.password = password
        self.token_endpoint = None
        self.current_tokens = None
        self._fetch_oidc_discovery()

    # 从well-known文档获取令牌端点
    def _fetch_oidc_discovery(self):
        discovery_url = f"{self.issuer_url}/.well-known/openid-configuration"
        resp = requests.get(discovery_url)
        resp.raise_for_status()
        self.token_endpoint = resp.json()["token_endpoint"]

    # 解析access_token的过期时间
    def _get_token_expiry(self, access_token):
        claims = jwt.get_unverified_claims(access_token)
        return datetime.fromtimestamp(claims["exp"])

    # 检查是否需要刷新(提前5分钟触发)
    def _needs_refresh(self):
        if not self.current_tokens:
            return True
        expiry = self._get_token_expiry(self.current_tokens["access_token"])
        return datetime.now() + timedelta(minutes=5) >= expiry

    # 获取或刷新令牌
    def _fetch_tokens(self, refresh_token=None):
        payload = {
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "scope": "openid offline_access"
        }
        if refresh_token:
            payload.update({"grant_type": "refresh_token", "refresh_token": refresh_token})
        else:
            payload.update({"grant_type": "password", "username": self.username, "password": self.password})

        resp = requests.post(self.token_endpoint, data=payload)
        resp.raise_for_status()
        self.current_tokens = resp.json()

    # 获取有效access_token
    def get_valid_access_token(self):
        if self._needs_refresh():
            if self.current_tokens and "refresh_token" in self.current_tokens:
                try:
                    self._fetch_tokens(refresh_token=self.current_tokens["refresh_token"])
                except Exception:
                    # 刷新失败,回退到用户名密码重新获取
                    self._fetch_tokens()
            else:
                self._fetch_tokens()
        return self.current_tokens["access_token"]

# 事件触发时的使用示例
if __name__ == "__main__":
    token_manager = KeycloakTokenManager(
        issuer_url="https://your-keycloak-domain/auth/realms/your-realm",
        client_id="your-client-id",
        client_secret="your-client-secret",
        username="preconfigured-user",
        password="preconfigured-pass"
    )
    access_token = token_manager.get_valid_access_token()
    # 用令牌调用遗留系统接口

关键注意事项

  • ROPC模式限制:仅用于后端可信应用(无前端交互),需在Keycloak客户端设置中开启"Direct Access Grants Enabled"
  • 令牌存储:生产环境需将refresh_token加密存储(如数据库、内存缓存),禁止明文暴露
  • 异常处理:需处理令牌刷新失败、Keycloak不可达等异常,必要时回退到用户名密码重新认证
  • 权限范围:必须请求offline_access scope才能获取refresh_token,否则无法实现令牌刷新

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:55:40