当前位置: 首页 > news >正文

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

Spring Security OAuth2实战:从零构建授权码登录系统

每次看到OAuth2授权码模式的流程图,那些密密麻麻的箭头和方框总让人望而生畏。作为开发者,我们需要的不是纸上谈兵的理论,而是能直接运行的代码和清晰的实现路径。本文将带你用Spring Security OAuth2完整实现一个授权码登录系统,从数据库表设计到自定义授权页面,再到生成Token的每个环节,我都会用可运行的代码片段展示关键实现。

1. 环境准备与基础配置

在开始编码前,我们需要搭建好基础环境。假设你已有一个Spring Boot 2.7.x项目,以下是必须的依赖:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.6.8</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

插入一个测试客户端:

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的核心,我们需要配置AuthorizationServerConfig

@Configuration @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 Security:

@Configuration @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 Map<String, 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:th="http://www.thymeleaf.org"> <head> <title>授权确认</title> </head> <body> <h2>授权请求</h2> <p>客户端 <strong th:text="${clientId}"></strong> 请求以下权限:</p> <ul> <li th:each="scope : ${scopes}" th:text="${scope}"></li> </ul> <form method="post" action="/oauth/authorize"> <input type="hidden" name="user_oauth_approval" value="true"/> <button type="submit">授权</button> </form> </body> </html>

4. 令牌生成与验证

令牌生成是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) { Map<String, 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. 常见问题与解决方案

在实际开发中,你可能会遇到以下问题:

  1. 配置优先级冲突

    • 解决方案:确保认证服务器配置的@Order值高于资源服务器
  2. 自定义页面不生效

    • 检查点:
      • 是否配置了pathMapping
      • 控制器是否添加了@SessionAttributes("authorizationRequest")
  3. 令牌存储问题

    • 如果使用Redis存储令牌:
    @Bean public TokenStore tokenStore() { return new RedisTokenStore(redisConnectionFactory); }
  4. 跨域问题

    • 在资源服务器配置中添加:
    @Override public void configure(HttpSecurity http) throws Exception { http.cors(); }

6. 完整流程测试

让我们测试整个授权码流程:

  1. 访问授权端点(替换client_idredirect_uri):

    http://localhost:8080/oauth/authorize?response_type=code&client_id=test-client&redirect_uri=http://localhost:8081/login/oauth2/code/test&scope=read
  2. 登录后跳转到自定义授权页面

  3. 同意授权后,会重定向到:

    http://localhost:8081/login/oauth2/code/test?code=生成的授权码
  4. 使用授权码获取令牌:

    curl -X POST \ http://localhost:8080/oauth/token \ -H 'Authorization: Basic dGVzdC1jbGllbnQ6c2VjcmV0' \ -d 'grant_type=authorization_code&code=生成的授权码&redirect_uri=http://localhost:8081/login/oauth2/code/test'
  5. 返回的令牌示例:

    { "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参数,否则授权操作不会被正确处理。

http://www.jsqmd.com/news/756849/

相关文章:

  • 2026 天津黄金回收优选:福正美线上线下双轨,全区域覆盖 - 福正美黄金回收
  • 厦门理工学院考研辅导班机构推荐:排行榜单与哪家好评测 - michalwang
  • Excel多文件查询终极指南:如何用1个工具解决90%的数据查找难题
  • 从Docker到Kubernetes:渐进式容器化学习路径与实战指南
  • 2026 襄阳黄金回收优选:福正美线上线下双轨,全区域覆盖 - 福正美黄金回收
  • 拥抱未来十年:Ubuntu 26.04 LTS 升级实践
  • 一个54岁的浙大教授,带着几个博士生干了17年国产CPU,最后把公司卖给了阿里,做出了玄铁910
  • 智能代理 AI 雷声大雨点小?Booking.com 分享五大经验,24 个月将有更多开创性发展!
  • 2026 UHMWPE定制服务公司权威榜单揭晓,哪家能脱颖而出?
  • 告别旁路由!用Docker在NAS或Linux主机上部署ImmortalWrt,打造家庭网络全能网关
  • 英雄联盟国服免费换肤终极指南:R3nzSkin国服特供版完整教程
  • 2026 深圳黄金回收避坑指南:选福正美,不扣点不熔金 - 福正美黄金回收
  • 突破网盘下载困局:智能直链解析工具的全方位应用指南
  • 量化交易终极指南:3步搭建QuantConnect本地学习环境
  • Windows触控板三指拖拽难题:如何让苹果MacBook手势在Windows上完美运行?
  • 河南物业工单管理系统哪个好用?要闭环报修的 - movno1
  • 歌词滚动姬:零基础快速制作专业LRC歌词的完整指南
  • 别再死记硬背SVPWM扇区表了!用STM32 CubeMX HAL库一步步推导七段式与五段式算法
  • 效率倍增:基于快马AI为stitch用户快速打造数据同步监控看板
  • C# 13拦截器深度应用案例(医疗HIS系统AOP改造全记录):响应延迟降低47%,故障定位效率提升9倍
  • 2026 西安黄金回收榜|福正美黄金回收位列榜一 - 福正美黄金回收
  • 对比直接使用原厂 API 体验 Taotoken 在模型切换便利性上的优势
  • ChatGPT插件开发实战:基于OpenAI规范构建自定义AI工具
  • 内容创作场景下如何利用Taotoken灵活切换不同大模型
  • 惠普600G2 MT加装WiFi蓝牙全记录:从NGFF转接卡到PCIE转接卡的踩坑与最终方案
  • 如何通过HTTrack网站镜像工具实现高效离线浏览与网站备份
  • AD9361 SPI no-os 文件移植 SoftConsole MPFS250T 初学(七) 初始化日志记录
  • 如何用Legacy iOS Kit轻松实现旧款iOS设备降级和性能恢复:5步完整指南
  • 如何快速掌握BooruDatasetTagManager:AI图像标注完整指南
  • 智能增强与范式演进:OpenClaw 与 Hermes Agent 自我学习机制深度研究报告