我们发现security的数据源配置主要是实现UserDetailsService接口
我们可以看到其实现类有如下
可以看到,在几个能直接使用的实现类中,除了 InMemoryUserDetailsManager 之外,还有一个 JdbcUserDetailsManager,
使用 JdbcUserDetailsManager 可以让我们通过 JDBC 的方式将数据库和 Spring Security 连接起来。
我们在正式的开发中,我们可以写一个自己的实体类实现该接口
使用数据库
建表
JdbcUserDetailsManager 自己提供了一个数据库模型,这个数据库模型保存在如下位置:
org/springframework/security/core/userdetails/jdbc/users.ddl
可以看到,脚本中有一种数据类型 varchar_ignorecase,这个其实是针对 HSQLDB 数据库创建的,而我们使用的 MySQL 并不支持这种数据类型,所以这里需要大家手动调整一下数据类型,将 varchar_ignorecase 改为 varchar 即可。
create database security_db;
use security_db;
create table users
(
username varchar(50) not null primary key,
password varchar(500) not null,
enabled boolean not null
);
create table authorities
(
username varchar(50) not null,
authority varchar(50) not null,
constraint fk_authorities_users foreign key (username) references users (username)
);
create unique index ix_auth_username on authorities (username, authority);
执行完 SQL 脚本后,我们可以看到一共创建了两张表:users 和 authorities。
- users 表中保存用户的基本信息,包括用户名、用户密码以及账户是否可用。
- authorities 中保存了用户的角色。
- authorities 和 users 通过 username 关联起来。
我们现在可以使用数据库连接配置用户,而不是内存数据进行配置用户了
依赖导入
新增jdbc依赖和mysql依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
配置类
@Autowired
DataSource dataSource;
@Override
@Bean
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager detailsManager = new InMemoryUserDetailsManager();
JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
if(!manager.userExists("admin")) {
manager.createUser(User.withUsername("admin").password("123456").roles("admin").build());
}
if(!manager.userExists("user")) {
manager.createUser(User.withUsername("user").password("123456").roles("user").build());
}
return manager;
}
结果
![]() |
---|
![]() |
数据库中的enabled表示是否禁用该账户,设置为禁用之后,就无法使用该账户进行登录了
整合JPA
使用Jdbc对于一般应用来说足够使用了,但是过于复杂,
为了更简单的管理spring security,我们引入jpa进行数据管理
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置实体类
UserEntity实体
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@Entity(name = "t_user")
public class UserEntity implements UserDetails {
private static final long serialVersionUID = 5461613954799968213L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
private List<RoleEntity> roleEntities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for(RoleEntity RoleEntity : getRoleEntities()) {
authorities.add(new SimpleGrantedAuthority(RoleEntity.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public boolean equals(Object o) {
if(this == o)
return true;
if(o == null || Hibernate.getClass(this) != Hibernate.getClass(o))
return false;
UserEntity that = (UserEntity) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return 0;
}
}
用户实体类主要需要实现 UserDetails 接口,并实现接口中的方法。
这里的字段基本都好理解,几个特殊的我来稍微说一下:
- accountNonExpired、accountNonLocked、credentialsNonExpired、enabled 这四个属性 | 属性 | 描述 | 默认值 | | —- | —- | —- | | accountNonExpired | 表示账户是否没有过期 | true | | accountNonLocked | 账户是否没有被锁定 | true | | credentialsNonExpired | 密码是否没有过期 | true | | enabled | 以及账户是否可用 | true |
- roles 属性表示用户的角色,User 和 Role 是多对多关系,用一个 @ManyToMany 注解来描述。
getAuthorities 方法返回用户的角色信息,我们在这个方法中把自己的 Role 稍微转化一下即可。
RoleEntity实体
@Getter @Setter @ToString @RequiredArgsConstructor @Entity(name = "t_role") public class RoleEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String nameZh; @Override public boolean equals(Object o) { if(this == o) return true; if(o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; RoleEntity that = (RoleEntity) o; return Objects.equals(id, that.id); } @Override public int hashCode() { return 0; } }
这个实体类用来描述用户角色信息,有角色 id、角色名称(英文、中文),
@Entity 表示这是一个实体类,项目启动后,将会根据实体类的属性在数据库中自动创建一个角色表。
配置Dao
配置Jpa的dao连接层
public interface UserDao extends JpaRepository<UserEntity, Long> {
UserEntity findUserByUsername(String username);
}
配置Service
@Service
public class UserService implements UserDetailsService {
@Autowired
UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userDao.findUserByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return user;
}
}
配置用户
在配置类中进行配置用户
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
还是重写 configure 方法,只不过这次我们不是基于内存,也不是基于 JdbcUserDetailsManager,而是使用自定义的 UserService进行配置就 OK 了。
配置文件
配置一下数据库信息和jpa信息即可
server:
port: 29898
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
jpa:
database: mysql
database-platform: mysql
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
show-sql: true
测试
@SpringBootTest
class UserEntityServiceTest {
@Autowired
UserDao userDao;
@Test
void contextLoads() {
UserEntity u1 = new UserEntity();
u1.setUsername("admin");
u1.setPassword("123456");
u1.setAccountNonExpired(true);
u1.setAccountNonLocked(true);
u1.setCredentialsNonExpired(true);
u1.setEnabled(true);
List<RoleEntity> rs1 = new ArrayList<>();
RoleEntity r1 = new RoleEntity();
r1.setName("ROLE_admin");
r1.setNameZh("管理员");
rs1.add(r1);
u1.setRoleEntities(rs1);
userDao.save(u1);
UserEntity u2 = new UserEntity();
u2.setUsername("user");
u2.setPassword("123456");
u2.setAccountNonExpired(true);
u2.setAccountNonLocked(true);
u2.setCredentialsNonExpired(true);
u2.setEnabled(true);
List<RoleEntity> rs2 = new ArrayList<>();
RoleEntity r2 = new RoleEntity();
r2.setName("ROLE_user");
r2.setNameZh("普通用户");
rs2.add(r2);
u2.setRoleEntities(rs2);
userDao.save(u2);
}
}
编写测试类,执行测试类,发现数据库生成了三张表,表中会生成数据
![]() |
---|
![]() |
![]() |
执行接口测试