如何通过自定义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官方示例进行改造:
- 引入最新版Spring Authorization Server依赖
- 配置基础的
RegisteredClient、用户信息(可通过InMemoryUserDetailsManager或数据库实现) - 新增上述自定义Controller
- 配置
SecurityFilterChain,允许自定义/auth/**端点的匿名访问(或根据需求添加客户端身份校验)
关键注意事项
- 授权码需设置合理有效期,且使用后标记为已失效,避免重复利用
- 建议使用JWT格式的Access Token,便于资源服务器无需调用授权服务器即可验证
- 生产环境需添加客户端身份校验,确保只有合法客户端能调用API
内容的提问来源于stack exchange,提问作者Tsokoloy
相关产品推荐
相关产品推荐

