ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java框架快速入门: Spring Security+OAuth2之UserDetails与JDBC认证实践

Java框架快速入门: Spring Security+OAuth2之UserDetails与JDBC认证实践 纲要核心接口UserDetails与UserDetailsServiceUserDetails接口的方法与扩展点UserDetailsService的职责与loadUserByUsername方法认证流程中的角色AuthenticationManager、UserDetailsService、PasswordEncoder数据库认证的默认表结构users与authorities实战从内存认证切换到 JDBC 认证添加 Spring JDBC 与 H2 依赖配置数据源与 H2 Web 控制台编写安全配置类启用jdbcAuthentication()启动应用并验证默认表与认证行为打印 SQL 日志以观察查询过程自定义UserDetails实现的基本思路小结UserDetails —— 安全上下文中的用户模型UserDetails是 Spring Security 中对用户信息的抽象其本身是一个接口而非具体类。这种设计使得框架能够适配几乎任何用户数据结构同时保留高度可扩展性。查看 Spring Security 源码可以看到该接口的定义publicinterfaceUserDetailsextendsSerializable{Collection?extendsGrantedAuthoritygetAuthorities();StringgetPassword();StringgetUsername();booleanisAccountNonExpired();booleanisAccountNonLocked();booleanisCredentialsNonExpired();booleanisEnabled();}关键方法说明getAuthorities()返回用户拥有的权限集合每个权限通常由GrantedAuthority表示。getPassword()与getUsername()安全框架要求必须提供用户名和密码。开发时可在实现时自由映射例如用邮箱代替用户名只需在getUsername()方法中返回邮箱即可。isAccountNonExpired()账户是否未过期。某些企业级项目要求账户具有有效期超过期限即不可用。isAccountNonLocked()账户是否未被锁定。锁定与禁用isEnabled()略有不同锁定的账户可能允许登录但无法执行操作而禁用的账户直接拒绝登录。isCredentialsNonExpired()凭证密码是否未过期。当密码有有效期时过期后系统可强制用户修改密码。isEnabled()账户是否激活是最常用的状态控制字段。通常情况下最简单的实现可以只关注username、password和enabled其余方法直接返回true。若需要自定义用户对象只需让实体类实现该接口并提供相应逻辑。UserDetailsService —— 数据加载的桥梁UserDetailsService负责从数据源如数据库中加载用户信息并构建UserDetails对象。它仅定义了一个方法publicinterfaceUserDetailsService{UserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException;}实现时根据用户名查询用户实体若实体类已经实现了UserDetails接口则直接返回即可否则需要构造一个UserDetails实例例如使用 Spring 提供的org.springframework.security.core.userdetails.User类。需要特别注意UserDetailsService本身并不执行认证它只是提供数据的服务。真正的认证由AuthenticationManager协调典型的实现是ProviderManager它会调用DaoAuthenticationProvider后者使用UserDetailsService获取用户信息再通过PasswordEncoder验证密码。在认证成功后Authentication对象的principal字段存储的就是UserDetails实例。由于getPrincipal()返回Object类型因此具备极强的扩展性你可以将任意对象放入安全上下文但数据库认证场景下最常见的就是UserDetails。认证流程概览以下时序图展示了基于数据库认证时的核心交互步骤DatabaseUserDetailsServiceDaoAuthenticationProviderAuthenticationManagerUsernamePasswordAuthenticationFilterClientDatabaseUserDetailsServiceDaoAuthenticationProviderAuthenticationManagerUsernamePasswordAuthenticationFilterClientalt[密码匹配][密码错误]提交用户名/密码authenticate(token)authenticate(token)loadUserByUsername(username)查询用户用户记录UserDetailspasswordEncoder.matches(raw, encoded)认证成功 Authentication已认证的 Authentication抛出 BadCredentialsException默认数据库表结构Spring Security 为 JDBC 认证提供了默认的表结构最少仅需两张表users和authorities。users 表列名描述username用户名主键password加密后的密码enabled是否启用布尔值authorities 表列名描述username用户名外键authority权限字符串在实现最基础的数据库认证时UserDetails中账户是否过期、锁定等状态均可直接返回true数据库中只需存储上述三个字段即可。实战从内存认证切换到 JDBC 认证下面通过一个完整的 Spring Boot 示例演示如何将内存用户存储切换为基于数据库的 JDBC 认证并使用 H2 内存数据库快速验证。项目结构src/ └── main/ ├── java/com/example/security/ │ ├── SecurityApplication.java │ ├── config/SecurityConfig.java │ └── controller/HomeController.java └── resources/ └── application.yml添加依赖在pom.xml中引入spring-boot-starter-jdbc和 H2 数据库依赖dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependencydependencygroupIdcom.h2database/groupIdartifactIdh2/artifactIdscoperuntime/scope/dependency/dependencies配置数据源与 H2 控制台在application.yml中设置数据源及 H2 Web 控制台spring:datasource:url:jdbc:h2:mem:testdb;MODEMySQL;DB_CLOSE_DELAY-1driver-class-name:org.h2.Driverusername:sapassword:h2:console:enabled:truepath:/h2-consolejpa:show-sql:falselogging:level:org.springframework.jdbc.core:DEBUGMODEMySQL让 H2 兼容 MySQL 语法方便将来迁移到真实 MySQL。DB_CLOSE_DELAY-1保持内存数据库在连接关闭后不被销毁。日志级别设为DEBUG以便观察 SQL 语句的执行。安全配置类创建SecurityConfig启用 JDBC 认证并使用默认表结构packagecom.example.security.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;importorg.springframework.security.crypto.password.NoOpPasswordEncoder;importorg.springframework.security.crypto.password.PasswordEncoder;importjavax.sql.DataSource;ConfigurationEnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{privatefinalDataSourcedataSource;publicSecurityConfig(DataSourcedataSource){this.dataSourcedataSource;}Overrideprotectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{auth.jdbcAuthentication().dataSource(dataSource).withDefaultSchema().passwordEncoder(passwordEncoder());}Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http.authorizeRequests().antMatchers(/h2-console/**).permitAll().anyRequest().authenticated().and().formLogin().and().httpBasic();// 允许 H2 控制台使用 frame 框架http.headers().frameOptions().sameOrigin();// 禁用 CSRF 以方便 H2 控制台访问生产环境需考虑安全http.csrf().disable();}BeanpublicPasswordEncoderpasswordEncoder(){// 演示环境使用明文编码生产务必使用 BCrypt 等returnNoOpPasswordEncoder.getInstance();}}说明withDefaultSchema()会基于 H2 数据库自动创建users和authorities两张表并默认插入两个用户用户user/ 密码password角色ROLE_USER用户admin/ 密码password角色ROLE_USER,ROLE_ADMINpasswordEncoder为了简单演示使用了NoOpPasswordEncoder实际项目必须替换为BCryptPasswordEncoder。配置放行/h2-console/**路径并允许 frame 同源请求以便通过浏览器访问数据库管理界面。创建启动类与控制器packagecom.example.security;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplicationpublicclassSecurityApplication{publicstaticvoidmain(String[]args){SpringApplication.run(SecurityApplication.class,args);}}packagecom.example.security.controller;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;RestControllerpublicclassHomeController{GetMapping(/)publicStringhome(){returnWelcome! You are authenticated.;}GetMapping(/admin)publicStringadmin(){returnAdmin page.;}}启动与验证启动应用访问http://localhost:8080会跳转至登录页面。使用默认用户user/password或admin/password登录可看到欢迎信息。访问http://localhost:8080/h2-consoleJDBC URL 使用jdbc:h2:mem:testdb用户名sa空密码登录后可查看自动生成的表及数据SELECT*FROMUSERS;SELECT*FROMAUTHORITIES;此时会发现数据库中已存在用户和对应的权限记录。为了确认 JDBC 认证确实执行了数据库查询可以在控制台日志中看到类似以下输出[DEBUG] Executing prepared SQL query [select username, password, enabled from users where username ?] [DEBUG] Executing prepared SQL query [select username, authority from authorities where username ?]这就是JdbcDaoImpl内部加载用户和权限的 SQL证明数据来自数据库。自定义 UserDetails 的方向若项目需要更复杂的用户属性如邮箱、手机号等可让实体类实现UserDetails接口然后自定义UserDetailsService实现。典型的步骤如下编写UserEntity实现UserDetails重写所有方法将数据库字段映射到对应方法。创建CustomUserDetailsService实现UserDetailsService在loadUserByUsername中通过 JPA/MyBatis 查询用户实体并返回。在安全配置中注入自定义UserDetailsService并使用BCryptPasswordEncoder替代明文编码。认证流程依然遵循前文所述的时序图只是将UserDetailsService的实现替换为自定义版本。小结本文梳理了 Spring Security 中两个核心接口UserDetails和UserDetailsService的设计意图与使用方法并演示了如何利用内置的 JDBC 认证机制快速将用户存储从内存切换到数据库。通过 H2 内存数据库和默认表结构可以零额外 SQL 编写即实现可运行的数据库认证这对于原型开发或初期验证非常高效。后续内容将深入探讨如何自定义UserDetails实体并集成生产级密码编码器以及AuthenticationManager的详细工作机理。
返回列表