别再死记硬背流程图了!用Spring Security OAuth2手把手实现一个授权码登录(附完整代码)

📅 发布时间:2026/7/13 11:03:02 👁️ 浏览次数:
别再死记硬背流程图了!用Spring Security OAuth2手把手实现一个授权码登录(附完整代码)
Spring Security OAuth2实战从零构建授权码登录系统每次看到OAuth2授权码模式的流程图那些密密麻麻的箭头和方框总让人望而生畏。作为开发者我们需要的不是纸上谈兵的理论而是能直接运行的代码和清晰的实现路径。本文将带你用Spring Security OAuth2完整实现一个授权码登录系统从数据库表设计到自定义授权页面再到生成Token的每个环节我都会用可运行的代码片段展示关键实现。1. 环境准备与基础配置在开始编码前我们需要搭建好基础环境。假设你已有一个Spring Boot 2.7.x项目以下是必须的依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdorg.springframework.security.oauth.boot/groupId artifactIdspring-security-oauth2-autoconfigure/artifactId version2.6.8/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency /dependencies接下来创建OAuth2所需的数据库表。核心表oauth_client_details存储客户端信息CREATE TABLE oauth_client_details ( client_id varchar(256) NOT NULL, resource_ids varchar(256) DEFAULT NULL, client_secret varchar(256) DEFAULT NULL, scope varchar(256) DEFAULT NULL, authorized_grant_types varchar(256) DEFAULT NULL, web_server_redirect_uri varchar(256) DEFAULT NULL, authorities varchar(256) DEFAULT NULL, access_token_validity int(11) DEFAULT NULL, refresh_token_validity int(11) DEFAULT NULL, additional_information varchar(4096) DEFAULT NULL, autoapprove varchar(256) DEFAULT NULL, PRIMARY KEY (client_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;插入一个测试客户端INSERT INTO oauth_client_details VALUES (test-client, null, {bcrypt}$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm, read,write, authorization_code,refresh_token, http://localhost:8081/login/oauth2/code/test, null, 3600, 86400, null, false);2. 认证服务器配置认证服务器是OAuth2的核心我们需要配置AuthorizationServerConfigConfiguration EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { Autowired private AuthenticationManager authenticationManager; Autowired private DataSource dataSource; Autowired private UserDetailsService userDetailsService; Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenStore(tokenStore()) .authorizationCodeServices(authorizationCodeServices()) .pathMapping(/oauth/confirm_access, /custom/confirm); } Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } Bean public AuthorizationCodeServices authorizationCodeServices() { return new JdbcAuthorizationCodeServices(dataSource); } }关键点说明EnableAuthorizationServer启用OAuth2认证服务器功能clients.jdbc(dataSource)从数据库读取客户端配置pathMapping自定义授权确认页面的路径3. 自定义登录与授权页面默认的OAuth2登录和授权页面非常简陋我们需要替换它们。首先配置Spring SecurityConfiguration Order(1) public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .requestMatchers() .antMatchers(/login, /oauth/authorize) .and() .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage(/login) // 自定义登录页 .permitAll(); } Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser(user) .password({bcrypt}$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm) .roles(USER); } }创建自定义授权确认页面控制器Controller SessionAttributes(authorizationRequest) public class GrantController { GetMapping(/custom/confirm) public ModelAndView getAccessConfirmation( RequestParam MapString, String parameters, Model model) { AuthorizationRequest authorizationRequest (AuthorizationRequest) model.asMap().get(authorizationRequest); ModelAndView view new ModelAndView(grant); view.addObject(clientId, authorizationRequest.getClientId()); view.addObject(scopes, authorizationRequest.getScope()); return view; } }对应的Thymeleaf模板grant.html:!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head title授权确认/title /head body h2授权请求/h2 p客户端 strong th:text${clientId}/strong 请求以下权限/p ul li th:eachscope : ${scopes} th:text${scope}/li /ul form methodpost action/oauth/authorize input typehidden nameuser_oauth_approval valuetrue/ button typesubmit授权/button /form /body /html4. 令牌生成与验证令牌生成是OAuth2流程的最后一步。我们可以自定义令牌的生成规则和存储方式Configuration EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/api/**).authenticated(); } }自定义令牌增强器在令牌中添加额外信息public class CustomTokenEnhancer implements TokenEnhancer { Override public OAuth2AccessToken enhance( OAuth2AccessToken accessToken, OAuth2Authentication authentication) { MapString, Object additionalInfo new HashMap(); additionalInfo.put(organization, example-org); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }在认证服务器配置中添加令牌增强器Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { TokenEnhancerChain tokenEnhancerChain new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new CustomTokenEnhancer())); endpoints .tokenEnhancer(tokenEnhancerChain) // 其他配置... }5. 常见问题与解决方案在实际开发中你可能会遇到以下问题配置优先级冲突解决方案确保认证服务器配置的Order值高于资源服务器自定义页面不生效检查点是否配置了pathMapping控制器是否添加了SessionAttributes(authorizationRequest)令牌存储问题如果使用Redis存储令牌Bean public TokenStore tokenStore() { return new RedisTokenStore(redisConnectionFactory); }跨域问题在资源服务器配置中添加Override public void configure(HttpSecurity http) throws Exception { http.cors(); }6. 完整流程测试让我们测试整个授权码流程访问授权端点替换client_id和redirect_urihttp://localhost:8080/oauth/authorize?response_typecodeclient_idtest-clientredirect_urihttp://localhost:8081/login/oauth2/code/testscoperead登录后跳转到自定义授权页面同意授权后会重定向到http://localhost:8081/login/oauth2/code/test?code生成的授权码使用授权码获取令牌curl -X POST \ http://localhost:8080/oauth/token \ -H Authorization: Basic dGVzdC1jbGllbnQ6c2VjcmV0 \ -d grant_typeauthorization_codecode生成的授权码redirect_urihttp://localhost:8081/login/oauth2/code/test返回的令牌示例{ access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., token_type: bearer, refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..., expires_in: 3599, scope: read, organization: example-org, jti: 6d0ad5e5-3a3e-4b5a-8b8e-5e5e5e5e5e5e }在实现过程中我发现最容易被忽视的是Order注解的配置。当认证服务器和资源服务器的安全配置冲突时正确的优先级设置可以避免很多莫名其妙的错误。另外自定义授权页面时确保表单中包含user_oauth_approval参数否则授权操作不会被正确处理。