news 2026/1/16 4:22:08

探 Spring Security 之 用戶帳號資料儲存於DB

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
探 Spring Security 之 用戶帳號資料儲存於DB

前言

承襲 {初探 Spring Security 文章},使用 InMemoryUserDetailsManager,建立帳號與密碼並儲存於記憶體中。

現實中,我們不會將帳號與密碼這們做,一般情況下,都會存放在資料庫,或者LDAP。

以下我們將改寫使用 MySQL 來管理我們的使用者帳戶。

專案實作

(本次代碼有點多,請細看)

1.新增pom.xml相關Dependencies

Pom.xml <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency>

2.增修相關代碼

修改 Web 安全性, 網路安全配置類別 WebSecurityConfig(使用 HTTP Basic Authentication)

增修SecurityConfig

//SecurityConfig.java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth - > auth // Read permissions (Guest, User, Admin) .requestMatchers(HttpMethod.GET, "/api/users", "/api/user/{uid}") .hasAnyAuthority("read") // , "ROLE_GUEST") // Create permissions (User, Admin) .requestMatchers(HttpMethod.POST, "/api/user").hasAnyAuthority("create") // Requirement : Admin (CRUD) - DELETE/PUT will be handled by @PreAuthorize .requestMatchers(HttpMethod.PUT, "/api/users/{uid}").hasAnyAuthority("update") .requestMatchers(HttpMethod.DELETE, "/users/{uid}").hasAnyAuthority("delete") .anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()) // .formLogin(Customizer.withDefaults()) .sessionManagement(sess - > sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }

增修CustomUserDetailsService

//CustomUserDetailsService.java @Service @Transactional(readOnly = true) public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); Set<GrantedAuthority> authorities = new HashSet<>(); for (UserRole ur : user.getUserRoles()) { Role role = ur.getRole(); authorities.add(new SimpleGrantedAuthority( "ROLE_" + role.getName().name())); // permission-based authority for (String p : role.getPermissions()) { authorities.add(new SimpleGrantedAuthority(p)); } } return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), authorities); } }

增修Entity

// Role.java @Entity @Getter @Setter @AllArgsConstructor @Builder @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Enumerated(EnumType.STRING) @Column(nullable = false, unique = true) private RoleName name; public Role() { } public Role(RoleName role) { this.name = role; } public Role(RoleName name, Set<String> permissions) { this.name = name; this.permissions = permissions; } @Builder.Default @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "role_permissions", joinColumns = @JoinColumn(name = "role_id")) @Column(name = "permission") private Set<String> permissions = new HashSet<>(); @Builder.Default @JsonBackReference @OneToMany(mappedBy = "role", cascade = CascadeType.ALL, orphanRemoval = true) private Set<UserRole> userRoles = new HashSet<>(); } // RoleName.java public enum RoleName { ADMIN, USER, GUEST; } // 角色權限 RolePermission.java @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @Table(name = "role_permissions") public class RolePermission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "role_id") private Long role_id; @Column(name = "permission", length = 255) private String permission; } // 使用者 User.java @Entity @Getter @Setter @AllArgsConstructor @Builder @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "username", nullable = false, unique = true) private String username; @Column(name = "password", nullable = false) private String password; @Column(name = "first_name", nullable = true) private String firstName; @Column(name = "last_name", nullable = true) private String lastName; @Column(name = "email", nullable = false, unique = true) private String email; @JsonManagedReference @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private Set<UserRole> userRoles = new HashSet<>(); public User() { } public User(String username, String password, String firstName, String lastName, String email) { this.username = username; this.password = password; this.firstName = firstName; this.lastName = lastName; this.email = email; } public void addRole(UserRole role) { userRoles.add(role); role.setUser(this); } public void removeRole(UserRole role) { userRoles.remove(role); role.setUser(null); } } //UserRole.java /** * 中間實體定義(UserRole) */ @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @Table(name = "users_roles") public class UserRole implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // ManyToOne 關係到 User @JsonBackReference @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; // ManyToOne 關係到 Role @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "role_id") private Role role; @Column(name = "assigned_at") private LocalDateTime assignedAt = LocalDateTime.now(); public UserRole(User user, Role role) { this.user = user; this.role = role; } }

增修Repository

// UserRepository.java @Repository public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT u FROM User u " + "LEFT JOIN FETCH u.userRoles ur " + // 載入 UserRole 集合 "LEFT JOIN FETCH ur.role " + // 透過 ur 載入 Role 實體本身 "WHERE u.id = :id") Optional<User> findByIdWithRolesAndRoleDetails(@Param("id") Long id); Optional<User> findByUsername(String username); } @Repository public interface RoleRepository extends JpaRepository<Role, Long> { Optional<Role> findByName(RoleName name); } @Repository public interface UserRoleRepository extends JpaRepository<UserRole, Long> { }

增修Service

// UserService.java @Slf4j @Service public class UserService { @Autowired private PasswordEncoder passwordEncoder; // Used for hashing passwords @Autowired private UserMapper userMapper; @Autowired private UserRepository userRepository; @Transactional public User createUser(User newUser) {//////////////////////////// if (newUser == null) { throw new IllegalArgumentException("User must not be null"); } newUser.setPassword(passwordEncoder.encode(newUser.getPassword())); User user = userRepository.save(newUser); return user; } /** * 尋找單一使用者,並返回 DTO */ public Optional<UserDto> findByIdDto(Long id) { return userRepository.findById(id) // 使用 mapper to DTO .map(userMapper::toUserDto); } public List<User> findAll() { return userRepository.findAll(); } /** * 查找所有使用者,並返回 DTO 列表 */ public List<UserDto> findAllDto() {//////////////////////////// return userRepository.findAll().stream() // 使用 mapper to DTO .map(userMapper::toUserDto) .collect(Collectors.toList()); } public Optional<User> findById(Long id) {//////////////////////////// return userRepository.findByIdWithRolesAndRoleDetails(id); } public User getUserById(Long uid) { if (uid == null) { throw new UserNotFoundException(null); } User user = userRepository.findById(uid) .orElseThrow(() -> new UserNotFoundException(uid)); return user; } @Transactional public User updateUser(@PathVariable Long id, @RequestBody User newUser) {//////////////////////////// log.info("Updating user with id: " + id); return userRepository.findById(id) .map(user -> { user.setUsername(newUser.getUsername()); // Update password only if provided if (newUser.getPassword() != null && !newUser.getPassword().isEmpty()) { user.setPassword(passwordEncoder.encode(newUser.getPassword())); } user.setFirstName(null == newUser.getFirstName() ? user.getFirstName() : newUser.getFirstName()); user.setLastName(null == newUser.getLastName() ? user.getLastName() : newUser.getLastName()); user.setEmail(null == newUser.getEmail() ? user.getEmail() : newUser.getEmail()); return userRepository.save(user); }).orElseThrow(() -> new RuntimeException("User not found with id " + id)); } /** * 刪除使用者 */ @Transactional public void deleteUser(Long uid) {//////////////////////////// if (uid == null) { throw new UserNotFoundException(null); } userRepository.deleteById(uid); } } // UserRoleService.java @Service public class UserRoleService { @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private UserRoleRepository userRoleRepository; @Transactional public User addRole(Long userId, RoleName roleName) { // 1. 查找使用者 User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("User not found with ID: " + userId)); // 強制初始化集合,避免潛在的 LazyInitializationException Hibernate.initialize(user.getUserRoles()); // 2. 查找角色 Role role = roleRepository.findByName(roleName) .orElseThrow(() -> new RuntimeException("Role not found: " + roleName)); // 3. 檢查是否已存在角色 boolean alreadyExists = user.getUserRoles().stream() .anyMatch(userRole -> userRole.getRole().getName().equals(roleName)); if (alreadyExists) { return user; } // 4. 建立並設定 UserRole 關聯實體 UserRole userRole = new UserRole(); userRole.setUser(user); userRole.setRole(role); user.getUserRoles().add(userRole); userRoleRepository.save(userRole); return userRepository.save(user); } } // RoleService.java @Service public class RoleService { private final RoleRepository roleRepository; @Autowired public RoleService(RoleRepository roleRepository) { this.roleRepository = roleRepository; } /** * 創建一個新的角色。 * * @param role 欲儲存的角色實體 * @return 儲存後的角色實體 */ @Transactional public Role createRole(Role role) { // 可以在此處添加驗證,例如檢查角色名稱是否已存在 // RoleName roleName = RoleName.valueOf(role.getName().name().toUpperCase()); Optional<Role> existingRole = roleRepository.findByName(role.getName()); if (existingRole.isPresent()) { throw new IllegalArgumentException("Role name already exists: " + role.getName()); } return roleRepository.save(role); } }

增修Controller

// UserController.java @RestController @RequestMapping("/api") public class UserController { @Autowired private UserService userService; // Create (User/Admin) @PostMapping("/user") public ResponseEntity<?> createUser(@RequestBody User newUser) { User user = userService.createUser(newUser); return new ResponseEntity<>(user, HttpStatus.CREATED); } // Read One (Guest/User/Admin) @GetMapping("/user/{uid}") public ResponseEntity<User> getUserById(@PathVariable Long uid) { return userService.findById(uid) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } @GetMapping("/users") @PreAuthorize("hasAuthority('read')") public ResponseEntity<List<UserDto>> getAllUsers() { List<UserDto> userDtos = userService.findAllDto(); return ResponseEntity.ok(userDtos); } // Update (Admin) @PutMapping("/users/{uid}") public ResponseEntity<User> updateUser(@PathVariable Long uid, @RequestBody User userDetails) { User updatedUser = userService.updateUser(uid, userDetails); return ResponseEntity.ok(updatedUser); } @DeleteMapping("/users/{uid}") @PreAuthorize("hasAuthority('delete') or hasRole('ADMIN')") public ResponseEntity<Void> deleteUser(@PathVariable Long uid) { userService.deleteUser(uid); return ResponseEntity.noContent().build(); } }

增修DTO Mapper

// UserMapper.java @Component public class UserMapper { public RoleDto toRoleDto(Role role) { if (role == null) { return null; } RoleDto roleDto = new RoleDto(); roleDto.setId(role.getId()); roleDto.setName(role.getName()); return roleDto; } public Set<RoleDto> toRoleDtoSet(Set<UserRole> userRoles) { if (userRoles == null) { return Collections.emptySet(); } return userRoles.stream() .map(UserRole::getRole) .map(this::toRoleDto) .filter(Objects::nonNull) // .sorted(Comparator.comparing(RoleDto::getName)) .collect(Collectors.toSet()); } /** * 將 User 實體轉換為 UserDto */ public UserDto toUserDto(User user) { if (user == null) { return null; } UserDto userDto = new UserDto(); userDto.setId(user.getId()); userDto.setUsername(user.getUsername()); userDto.setFirstName(user.getFirstName()); userDto.setLastName(user.getLastName()); userDto.setEmail(user.getEmail()); userDto.setRoles(toRoleDtoSet(user.getUserRoles())); return userDto; } }

增修自定Exception

// UserNotFoundException.java public class UserNotFoundException extends RuntimeException { public UserNotFoundException(Long id) { String errString = ""; if (id == null) { errString = "User ID must not be null"; } else { errString = "User with ID " + id + " not found"; } super(errString); } }

增修初始資料,測試用

// DataInitializer.java @Slf4j @Component public class DataInitializer implements CommandLineRunner { @Autowired private UserRepository userRepository; @Autowired private RoleRepository roleRepository; @Autowired private UserRoleRepository userRoleRepository; @Autowired private PasswordEncoder passwordEncoder; @Override public void run(String... args) throws Exception { userRoleRepository.deleteAll(); userRepository.deleteAll(); roleRepository.deleteAll(); // --- 1. Create Roles --- Role adminRole = createRole(RoleName.ADMIN, Set.of("create", "read", "update", "delete")); Role userRole = createRole(RoleName.USER, Set.of("create", "read")); Role guestRole = createRole(RoleName.GUEST, Set.of("read")); // --- 2. Create Users --- User adminUser = createUser("admin", "password", "admin", "user", "admin@example.com"); User standardUser = createUser("user", "password", "standard", "user", "standard@example.com"); User guestUser = createUser("guest", "password", "guest", "user", "guest@example.com"); // --- 3. Link Users to Roles (UserRole) --- linkUserToRole(adminUser, adminRole); linkUserToRole(standardUser, userRole); linkUserToRole(guestUser, guestRole); } @Transactional private Role createRole(RoleName name, Set<String> permissions) { Role role = new Role(); role.setName(name); role.setPermissions(permissions); return roleRepository.save(role); } @Transactional private User createUser(String username, String rawPassword, String firstName, String lastName, String email) { User user = new User(); user.setUsername(username); user.setPassword(passwordEncoder.encode(rawPassword)); user.setFirstName(firstName); user.setLastName(lastName); user.setEmail(email); return userRepository.save(user); } @Transactional private void linkUserToRole(User user, Role role) { UserRole userRole = new UserRole(); userRole.setUser(user); userRole.setRole(role); userRoleRepository.save(userRole); user.getUserRoles().add(userRole); userRepository.save(user); } }

啟動App

初始寫入測試資料

. ____ _ __ _ _

/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

\\/ ___)| |_)| | | | | || (_| | ) ) ) )

' |____| .__|_| |_|_| |_\__, | / / / /

=========|_|==============|___/=/_/_/_/

:: Spring Boot :: (v3.5.8)

21:18:10.278 WARN [com.dannyyu.backend.SpringbootBackendApplication.main()][deprecation.constructDialect\(DialectFactoryImpl.java:153\21:18:10.974 WARN [com.dannyyu.backend.SpringbootBackendApplication.main()][JpaBaseConfiguration$JpaWebConfiguration.openEntityManagerInViewInterceptor\(JpaBaseConfiguration.java:258\Hibernate: select ur1_0.id,ur1_0.assigned_at,ur1_0.role_id,ur1_0.user_id from users_roles ur1_0

. . .

Hibernate: insert into roles (name) values (?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into roles (name) values (?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into roles (name) values (?)

Hibernate: insert into role_permissions (role_id,permission) values (?,?)

Hibernate: insert into users (email,first_name,last_name,password,username) values (?,?,?,?,?)

Hibernate: insert into users (email,first_name,last_name,password,username) values (?,?,?,?,?)

Hibernate: insert into users (email,first_name,last_name,password,username) values (?,?,?,?,?)

Hibernate: insert into users_roles (assigned_at,role_id,user_id) values (?,?,?)

Hibernate: select u1_0.id,u1_0.email,u1_0.first_name,u1_0.last_name,u1_0.password,ur1_0.user_id,ur1_0.id,ur1_0.assigned_at,ur1_0.role_id,u1_0.username from users u1_0 left join users_roles ur1_0 on u1_0.id=ur1_0.user_id where u1_0.id=?

Hibernate: insert into users_roles (assigned_at,role_id,user_id) values (?,?,?)

Hibernate: select u1_0.id,u1_0.email,u1_0.first_name,u1_0.last_name,u1_0.password,ur1_0.user_id,ur1_0.id,ur1_0.assigned_at,ur1_0.role_id,u1_0.username from users u1_0 left join users_roles ur1_0 on u1_0.id=ur1_0.user_id where u1_0.id=?

Hibernate: insert into users_roles (assigned_at,role_id,user_id) values (?,?,?)

Hibernate: select u1_0.id,u1_0.email,u1_0.first_name,u1_0.last_name,u1_0.password,ur1_0.user_id,ur1_0.id,ur1_0.assigned_at,ur1_0.role_id,u1_0.username from users u1_0 left join users_roles ur1_0 on u1_0.id=ur1_0.user_id where u1_0.id=?

確認測試資料已存入DB

測試案例

User 資料

{ "username": "test", "password": "123456", "firstName": "test", "lastName": "yu", "email": "test@example.com" }

GUEST:不能POST,不能新增User

回應

ADMIN:新增User

回應

確認測試數據有寫進DB

ADMIN:可刪除(hasAuthority('delete’))

USER:沒有delete權限

GUEST:可以GET

USER:可以POST(新建使用者)

回應

常見錯誤 & 解法
401 Unauthorized
原因:
.沒送 Authorization
.帳號或密碼錯
檢查:
.postman Authorization 是否設定
.密碼是否為「明碼」而不是 BCrypt

403 Forbidden
原因:
.有登入成功
.但 authority 不符合
檢查:
.hasAnyAuthority("read")
.是否真的有回傳 "read"(不是 "ROLE_READ")

本文章結束,希望各位讀者能有所得。

感謝!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/16 9:13:37

深度解析sktime软依赖管理:从混乱到有序的架构革命

深度解析sktime软依赖管理&#xff1a;从混乱到有序的架构革命 【免费下载链接】sktime sktime是一个用于机器学习中时间序列预测和分析的Python库&#xff0c;提供了丰富的数据预处理、特征提取和模型评估方法&#xff0c;适用于金融、气象等领域的数据分析。 项目地址: htt…

作者头像 李华
网站建设 2025/12/22 22:24:39

美国国家发明家科学院2025院士公布!

来源&#xff1a;新智元美国国家发明家科学院&#xff08;National Academy of Inventors&#xff0c;NAI&#xff09;公布了2025届院士名单。本届NAI院士共有185人入选&#xff0c;包括169名美国杰出的学术与机构发明家以及16位国际院士。2025届院士完整名单NAI院士是美国政府…

作者头像 李华
网站建设 2026/1/1 21:52:35

Gemini3Pro:粒子效果+手势控制(骨骼识别)

文章目录一、效果展示二、源代码三、最初代版本的prompt四、手势控制原理一、效果展示 5种粒子效果3种手势控制背景音乐调色盘全屏控制 二、源代码 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta n…

作者头像 李华
网站建设 2026/1/2 2:23:23

Nature | 丹麦环境微生物组图谱:Microflora Danica

点击蓝字 关注我们Nature | 丹麦环境微生物组图谱&#xff1a;Microflora Danica丹麦环境微生物组图谱&#xff1a;Microflora Danica研究论文● 原文: Nature (IF 48.5, 中科院双一区Top)● DOI: https://doi.org/10.1038/s41586-025-09794-2● 原文链接&#xff1a;https://…

作者头像 李华
网站建设 2026/1/12 6:41:04

半导体设备日志系统技术架构设计

核心框架&#xff1a;.NET 6 WPF通信层&#xff1a;OPC UA&#xff08;工业标准协议&#xff09;数据库&#xff1a;时序数据库InfluxDB&#xff08;专为日志数据优化&#xff09;依赖注入&#xff1a;Microsoft.Extensions.DependencyInjection分层架构&#xff1a;graph LRA…

作者头像 李华
网站建设 2025/12/16 9:11:07

雪花算法ID重复了?惨痛教训:请勿轻易造轮子!

来源&#xff1a;juejin.cn/post/7507203999102648360&#x1f449; 欢迎加入小哈的星球&#xff0c;你将获得: 专属的项目实战&#xff08;多个项目&#xff09; / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论新项目&#xff1a;《Spring AI 项目实战》正在更…

作者头像 李华