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

如何通过自定义API实现OAuth2授权码与Access Token获取(2022版)

技术实现方案

一、自定义双端点实现(/auth/code + /auth/token)

1. 实现/auth/code端点

核心逻辑是校验用户身份后生成并返回授权码:

  • 身份校验:借助Spring Security的AuthenticationManager验证用户名密码,生成合法的Authentication对象。
  • 生成授权码:调用Spring Authorization Server的AuthorizationCodeGenerator生成授权码,并将授权码与用户信息绑定后存入存储(如内存或数据库)。
  • 返回结果:直接返回授权码字符串。

核心代码片段:

@RestController
@RequestMapping("/auth")
public class CustomAuthController {
    private final AuthenticationManager authenticationManager;
    private final AuthorizationCodeGenerator authorizationCodeGenerator;
    private final AuthorizationCodeRepository authorizationCodeRepo;
    private final RegisteredClientRepository clientRepo;

    // 构造注入所有依赖

    @PostMapping("/code")
    public ResponseEntity<String> generateAuthCode(@RequestBody UserLoginDto loginDto) {
        // 验证用户身份
        UsernamePasswordAuthenticationToken authRequest = 
            new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
        Authentication auth = authenticationManager.authenticate(authRequest);
        SecurityContextHolder.getContext().setAuthentication(auth);

        // 构建授权码请求上下文
        RegisteredClient client = clientRepo.findByClientId("your-client-id");
        OAuth2AuthorizationCodeRequest codeRequest = OAuth2AuthorizationCodeRequest.with(
                ClientAuthenticationMethod.CLIENT_SECRET_BASIC, client.getId())
            .principal(auth)
            .redirectUri("http://localhost:8080/ignore") // 自定义场景可设为占位值
            .build();

        // 生成并存储授权码
        OAuth2AuthorizationCode authCode = authorizationCodeGenerator.generate(codeRequest);
        authorizationCodeRepo.save(authCode);

        return ResponseEntity.ok(authCode.getTokenValue());
    }
}

2. 实现/auth/token端点

核心逻辑是验证授权码有效性后生成Access Token:

  • 校验授权码:从请求参数中获取授权码,通过存储查询对应的用户信息,验证其有效性(未过期、未被使用)。
  • 生成Token:使用OAuth2TokenGenerator生成JWT格式的Access Token,关联用户权限。
  • 返回标准Token响应:包含Token值、过期时间、Token类型等。

核心代码片段:

@PostMapping("/token")
public ResponseEntity<OAuth2AccessTokenResponse> generateToken(@RequestParam String code) {
    // 查询并验证授权码
    OAuth2AuthorizationCode authCode = authorizationCodeRepo.findByTokenValue(code);
    if (authCode == null || authCode.getExpiresAt().isBefore(Instant.now())) {
        return ResponseEntity.badRequest().body(null);
    }

    // 构建Token生成上下文
    RegisteredClient client = clientRepo.findByClientId("your-client-id");
    OAuth2TokenContext tokenContext = OAuth2TokenContext.builder()
        .principal(authCode.getPrincipal())
        .registeredClient(client)
        .tokenType(OAuth2TokenType.ACCESS_TOKEN)
        .build();

    // 生成Access Token
    OAuth2Token token = oAuth2TokenGenerator.generate(tokenContext);
    OAuth2AccessToken accessToken = (OAuth2AccessToken) token;

    // 构建响应
    OAuth2AccessTokenResponse response = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
        .tokenType(OAuth2TokenType.BEARER)
        .expiresIn(ChronoUnit.SECONDS.between(Instant.now(), accessToken.getExpiresAt()))
        .build();

    // 标记授权码已使用(可选,防止重复使用)
    authorizationCodeRepo.remove(authCode);
    return ResponseEntity.ok(response);
}

二、合并为单端点/auth/token实现

如果不需要授权码环节,直接在/auth/token中完成身份校验+Token生成:

@PostMapping("/token")
public ResponseEntity<OAuth2AccessTokenResponse> getTokenDirectly(@RequestBody UserLoginDto loginDto) {
    // 验证用户身份
    UsernamePasswordAuthenticationToken authRequest = 
        new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
    Authentication auth = authenticationManager.authenticate(authRequest);

    // 生成Token
    RegisteredClient client = clientRepo.findByClientId("your-client-id");
    OAuth2TokenContext tokenContext = OAuth2TokenContext.builder()
        .principal(auth)
        .registeredClient(client)
        .tokenType(OAuth2TokenType.ACCESS_TOKEN)
        .build();
    OAuth2AccessToken accessToken = (OAuth2AccessToken) oAuth2TokenGenerator.generate(tokenContext);

    // 返回响应
    OAuth2AccessTokenResponse response = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
        .tokenType(OAuth2TokenType.BEARER)
        .expiresIn(ChronoUnit.SECONDS.between(Instant.now(), accessToken.getExpiresAt()))
        .build();
    return ResponseEntity.ok(response);
}

三、可运行项目改造参考

基于Spring Authorization Server官方示例进行改造:

  1. 引入最新版Spring Authorization Server依赖
  2. 配置基础的RegisteredClient、用户信息(可通过InMemoryUserDetailsManager或数据库实现)
  3. 新增上述自定义Controller
  4. 配置SecurityFilterChain,允许自定义/auth/**端点的匿名访问(或根据需求添加客户端身份校验)

关键注意事项

  • 授权码需设置合理有效期,且使用后标记为已失效,避免重复利用
  • 建议使用JWT格式的Access Token,便于资源服务器无需调用授权服务器即可验证
  • 生产环境需添加客户端身份校验,确保只有合法客户端能调用API

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 05:25:19