在这一部分先介绍Spring Security基于数据库登录,因为动态权限设置也是要基于数据库的,而且也要在基于数据库登录上实现。
在介绍实现数据库登录之前,先摆出笔者在这里踩过两个坑:
①踩坑:控制台报错There is no PasswordEncoder mapped for the id "null"并且输入登录之后不会返回任何登录相关信息,而是再次跳回登录界面;
原因:Spring Security配置类没有加@Configuration注解;
网上关于Spring Security 5.0版本之后,如果基于数据库认证需要在config方法中加载用户路径时的写法:(网上说法:保证用户登录时使用bcrypt对密码进行处理再与数据库中的密码比对)
下面展示一些 config配置类中的写法:
//注入userDetailsService的实现类
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
笔者Spring Security 5.2.2环境下试过后面.passwordEncoder(new BCryptPasswordEncoder())其实加不加都可以运行通过;
②最好是角色表role的name必须以“ROLE_”开头,例如:“ROLE_admin”可以避免后续很多坑;之前学习时,对数据库role表没有以“ROLE_”开头,是因为可以在User实体类中的getAuthorities方法中将“ROLE_”前缀写在代码中,但这次由于动态权限需要查询role表,没办法在代码中加“ROLE_”前缀,会出一些bug;
下面开始正文部分,本次项所需依赖:MyBatis、MySqlDriver、以及Spring Security和Spring Web。
设计数据库
基于数据库登录只需要user、role、user_role三张表,这里准备了五张表是为了后面动态权限设置所需。
user表
其中的password字段是密码为123的明文由BCryptPasswordEncoder加密过后的密文,BCryptPasswordEncoder加密相同的密码会得到不同的密文,这里笔者偷懒,三个密文是一样的,但并不妨碍登录。实际项目不可这样。
role表 
menu表,路径表

user_role表,根据uid(用户id)可以查到对应的rid(角色id),根据rid可以在role表中查到角色;

role_menu表,根据mid(路径id)可以查到对应的rid(角色id)根据rid可以在role表中查到路径所需的角色;

编写实体类
编写Role、User连个实体类,Role实体类的写法与普通实体类写法无异,需要注意的是实体类中的成员变量要和数据库中的字段名一一对应,并必须有get、set方法。User实体类需要实现UserDetail接口,并实现接口中的方法。这里主要介绍User实体类的写法:
public class User implements UserDetails {
private int id;
private String username;
private String password;
private boolean enabled;
private boolean locked;
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
@Override
//获取当前用户角色
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role:roles
) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
//账户是否未过期,直接返回true表示账户未过期,也可以在数据库中添加该字段
public boolean isAccountNonExpired() {
return true;
}
@Override
//账户是否为被锁,这里和数据库中的locked字段刚好反义,所以取反
public boolean isAccountNonLocked() {
return !this.locked;
}
@Override
//密码是否为过期,数据库中无该字段,直接返回true
public boolean isCredentialsNonExpired() {
return true;
}
@Override
//账户是否可用,从数据库中获取该字段
public boolean isEnabled() {
return this.enabled;
}
}
UserService实现用户登录
UserService类实现用户基于数据库登录,需实现UserDetailService接口,并实现接口中的方法,由于使用MyBatis整合数据库,所以后面需要编写Mapper类实现Service中需要的方法,供Service调用:
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("用户不存在");
}
user.setRoles(userMapper.getUserRolesById(user.getId()));
return user;
}
}
Mapper接口和XML编写
Mapper接口和XML这一套就是MyBatis里的,负责和数据库打交道,编写SQL根据需求操作数据库。这里只用实现Service中需要的方法。在UserMapper接口写入需要实现的方法并XML中实现Service中需要的loadUserByUsername和getUserRolesById两个方法,XML:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.yc.security_dynamic.mapper.UserMapper">
<select id="loadUserByUsername" resultType="org.yc.security_dynamic.bean.User">
select * from user where username=#{username}
</select>
<select id="getUserRolesById" resultType="org.yc.security_dynamic.bean.Role">
select * from role where id in (select rid from user_role where uid=#{id})
</select>
</mapper>
getUserRolesById方法获取用户角色需要先在user_role表中根据用户id查出rid,然后在role表中根据rid查出角色,上面使用了一个嵌套查询;
Spring Security配置类
编写Spring Security配置类继承自WebSecurityConfigurerAdapter,在config方法中由UserService加载用户信息,并提供密码编码类BCryptPasswordEncoder。
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
}
Controller类
最后,编写controller类,测试基于数据库登录。
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello security!";
}
}
到这里就实现了Spring Security基于数据库的登录;在浏览器端访问localhost:8080/hello接口就会自动跳转到Spring Security的默认登陆页面:

输入user表中任意用户及密码就能访问到/hello接口。这里所有用户都能访问/hello接口,因为没有配置权限。
到这里,一个简单的Spring Security基于数据库登陆就完成了,下期在这基础之上介绍动态权限设置。

本文详细介绍了使用SpringSecurity实现基于数据库的用户登录过程,包括常见问题解决、实体类设计、服务实现、MyBatis集成及SpringSecurity配置。通过具体代码示例,帮助读者理解并实践数据库登录的完整流程。
1411





