
本文旨在指导如何在 spring security 6 中通过自定义 `userdetailsservice` 实现与外部数据库的用户认证。我们将介绍如何配置 `securityfilterchain`,并结合数据访问对象(dao)从外部数据库加载用户凭据,从而替代已弃用的 `websecurityconfigureradapter` 方法,提供一套现代且安全的用户登录解决方案。
在 Spring Security 6 中,传统的 WebSecurityConfigurerAdapter 类已被弃用,取而代之的是基于组件的配置方式,主要通过定义 SecurityFilterChain Bean 来实现安全配置。对于需要从外部数据库加载用户凭据进行认证的场景,核心思路是实现 UserDetailsService 接口,并通过数据访问层(DAO 或 Repository)与数据库进行交互。
Spring Security 的认证流程依赖于 UserDetailsService 接口。该接口只有一个方法:UserDetails loadUserByUsername(String username),它负责根据用户名加载用户的详细信息,包括用户名、密码以及所拥有的权限(角色)。
为了实现从外部数据库加载用户,我们需要:
首先,我们需要一个 SecurityConfiguration 类来定义 SecurityFilterChain Bean,这是 Spring Security 6 的主要配置入口。在这个配置中,我们将定义授权规则和启用表单登录。Spring Security 会自动检测并使用容器中存在的 UserDetailsService 类型的 Bean 进行用户认证。
package de.gabriel.vertretungsplan.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
// 定义不同角色的访问权限
.requestMatchers("/vertretungsplan").hasAnyRole("SCHUELER", "LEHRER", "VERWALTUNG")
.requestMatchers("/account").hasAnyRole("LEHRER", "VERWALTUNG")
.requestMatchers("/administration").hasRole("VERWALTUNG")
// 允许所有用户访问根路径
.requestMatchers("/").permitAll()
// 任何其他请求都需要认证
.anyRequest().authenticated()
)
.formLogin(form -> form
// 可以指定自定义的登录页面路径,例如 "/login"
// .loginPage("/login")
// 允许所有用户访问登录页面
.permitAll()
);
return http.build();
}
/**
* 配置密码编码器。强烈建议在生产环境中使用强密码编码器。
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}在上述配置中,我们定义了授权规则,并启用了 formLogin。重要的是,我们还定义了一个 PasswordEncoder Bean。Spring Security 在进行密码比对时会使用这个编码器。
接下来,创建一个实现 UserDetailsService 接口的类。这个类将负责从数据库中检索用户信息。
首先,定义一个简单的用户实体类 UserEntity 来映射数据库中的用户表结构。
// src/main/java/your/package/model/UserEntity.java
package your.package.model;
import java.util.List;
public class UserEntity {
private String username;
private String password; // 存储加密后的密码
private List<String> roles; // 例如 "SCHUELER", "LEHRER", "VERWALTUNG"
public UserEntity(String username, String password, List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
// Getters
public String getUsername() { return username; }
public String getPassword() { return password; }
public List<String> getRoles() { return roles; }
// Setters (根据需要添加)
}然后,实现 CustomUserDetailsService:
// src/main/java/your/package/security/CustomUserDetailsService.java
package your.package.security;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import your.package.model.UserEntity;
import your.package.repository.UserRepository; // 引入你的用户数据访问接口
import java.util.List;
import java.util.stream.Collectors;
@Service // 声明为一个Spring服务组件,使其可被Spring容器管理
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository; // 注入用户数据访问接口
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 从数据库获取用户实体
UserEntity userEntity = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户未找到: " + username));
// 2. 将用户实体中的角色转换为 Spring Security 期望的 SimpleGrantedAuthority 列表
// 注意:Spring Security 默认期望角色以 "ROLE_" 开头,例如 "ROLE_SCHUELER"
List<SimpleGrantedAuthority> authorities = userEntity.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
// 3. 构建并返回 Spring Security 的 UserDetails 对象
return new org.springframework.security.core.userdetails.User(
userEntity.getUsername(), // 用户名
userEntity.getPassword(), // 数据库中存储的加密密码
authorities // 用户权限列表
);
}
}为了让 CustomUserDetailsService 能够访问数据库,我们需要一个数据访问层。这里以 JdbcTemplate 为例,因为它轻量且易于理解。
首先,定义 UserRepository 接口:
// src/main/java/your/package/repository/UserRepository.java
package your.package.repository;
import your.package.model.UserEntity;
import java.util.Optional;
public interface UserRepository {
Optional<UserEntity> findByUsername(String username);
}然后,实现 JdbcUserRepository 使用 JdbcTemplate:
// src/main/java/your/package/repository/JdbcUserRepository.java
package your.package.repository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import your.package.model.UserEntity;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Repository // 声明为一个Spring数据访问组件
public class JdbcUserRepository implements UserRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcUserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Optional<UserEntity> findByUsername(String username) {
// 假设数据库中有一个名为 'users' 的表,包含 'username', 'password', 'roles' 列
// 'roles' 列存储逗号分隔的角色字符串,例如 "SCHUELER,LEHRER"
String sql = "SELECT username, password, roles FROM users WHERE username = ?";
try {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, new UserEntityRowMapper(), username));
} catch (org.springframework.dao.EmptyResultDataAccessException e) {
// 如果没有找到用户,JdbcTemplate 会抛出 EmptyResultDataAccessException
return Optional.empty();
}
}
// 辅助类,用于将 ResultSet 的一行映射到 UserEntity 对象
private static class UserEntityRowMapper implements RowMapper<UserEntity> {
@Override
public UserEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
String rolesString = rs.getString("roles");
List<String> roles = Arrays.asList(rolesString.split(",")); // 将逗号分隔的字符串转换为列表
return new UserEntity(
rs.getString("username"),
rs.getString("password"),
roles
);以上就是在 Spring Security 6 中集成外部数据库进行用户认证的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号