阅读完需:约 8 分钟
在前面的 SpringBoot—整合SpringSecurity(安全框架) 里讲了对登录的用户名/密码进行配置,有三种不同的方式:
- 在 application.properties 中进行配置
- 通过 Java 代码配置在内存中
- 通过 Java 从数据库中加载
这次来讲讲第三种方式基于数据库的认证:
下图是项目的总目录:

1.首先创建一个项目:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
2.连接数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/security
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50717
Source Host : localhost:3306
Source Database : security
Target Server Type : MYSQL
Target Server Version : 50717
File Encoding : 65001
Date: 2018-07-28 15:26:51
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`nameZh` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'dba', '数据库管理员');
INSERT INTO `role` VALUES ('2', 'admin', '系统管理员');
INSERT INTO `role` VALUES ('3', 'user', '用户');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`enabled` tinyint(1) DEFAULT NULL,
`locked` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'root', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('2', 'admin', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
INSERT INTO `user` VALUES ('3', 'sang', '$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq', '1', '0');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uid` int(11) DEFAULT NULL,
`rid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `user_role` VALUES ('3', '2', '2');
INSERT INTO `user_role` VALUES ('4', '3', '3');
SET FOREIGN_KEY_CHECKS=1;
3.创建两个实体类
第一个User
public class User implements UserDetails {
private Integer 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 Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setUsername(String username) {
this.username = username;
}
@Override //返回用户的所有角色 一定一定不要忘记加上 ROLE_ 这个前缀
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> list=new ArrayList<>();
for(Role role:roles){
list.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
}
return list;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
}
UserDetails
该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象。
也就是说—— UserDetailsService 获取的信息是存放在 UserDetails 里面的
UserDetailsService
可以知道最终交给Spring Security的是UserDetails
UserDetails 中几个字段的解释:
//返回验证用户密码,无法返回则NULL
String getPassword();
String getUsername();
//账户是否过期,过期无法验证
boolean isAccountNonExpired();
//指定用户是否被锁定或者解锁,锁定的用户无法进行身份验证
boolean isAccountNonLocked();
//指示是否已过期的用户的凭据(密码),过期的凭据防止认证
boolean isCredentialsNonExpired();
//是否被禁用,禁用的用户不能身份验证
boolean isEnabled();
//返回用户的所有角色 一定一定不要忘记加上 ROLE_ 这个前缀
public Collection getAuthorities();
SimpleGrantedAuthority
:
GrantedAuthority接口的默认实现SimpleGrantedAuthority
我们知道UserDeitails接口里面有一个getAuthorities()方法。这个方法将返回此用户的所拥有的权限。这个集合将用于用户的访问控制,也就是Authorization。
在Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀”ROLE_”, 在Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。
所以,在Security提供的UserDetailsService
默认实现JdbcDaoImpl
中,角色和权限都存储在auhtorities表中。
在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示”角色”的权限,在数据库中就带有”ROLE_”前缀了。

角色和权限能否分开存储?角色能不能不带”ROLE_”前缀
当然可以分开存储,你可以定义两张表,一张存角色,一张存权限。但是你自定义UserDetailsService的时候,需要保证把这两张表的数据都取出来,放到UserDails的权限集合中。当然你数据库中存储的角色也可以不带”ROLE_”前缀,就像这样。

但是前面说到了,Security才不管你是角色,还是权限。它只比对字符串。
比如它有个表达式hasRole(“ADMIN”)。那它实际上查询的是用户权限集合中是否存在字符串”ROLE_ADMIN”。如果你从角色表中取出用户所拥有的角色时不加上”ROLE_”前缀,那验证的时候就匹配不上了。
所以角色信息存储的时候可以没有”ROLE_”前缀,但是包装成GrantedAuthority对象的时候必须要有。
第二个Role
public class Role {
private Integer id;
private String name;
private String nameZh;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameZh() {
return nameZh;
}
public void setNameZh(String nameZh) {
this.nameZh = nameZh;
}
}
4.创建一个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()));
try {
System.out.println(new ObjectMapper().writeValueAsString(user));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return user;
}
}
该类主要是用来返回查询到的用户的信息,然后拿这些信息去验证
5.创建Mapper类
先创建Mapper的接口
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
List<Role> getUserRolesById(Integer id);
}
Mpper映射
<?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="cn.securitydb.mapper.UserMapper">
<select id="loadUserByUsername" resultType="cn.securitydb.bean.User">
select * from user where username=#{username}
</select>
<select id="getUserRolesById" resultType="cn.securitydb.bean.Role">
select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}
</select>
</mapper>
别忘记在pom里面加上这个防止xml被忽略
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
6.创建config配置文件
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
//配置认证管理器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService); //自定义的方法
}
//这个是密码加密
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//用户安全过滤器链配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/dba/**").hasRole("dba")
.antMatchers("/user/**").hasRole("user")
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
//角色继承问题 使另一个角色有其他角色的权限
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_dba > ROLE_admin \n ROLE_admin > ROLE_user";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
}
在这个文件中 auth.userDetailsService(userService);
是自定义的验证,通过 userService
里面返回的User来进行。
关于角色继承:
7.创建Controller 控制器
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello security";
}
@GetMapping("/dba/hello")
public String dba(){
return "hello dba";
}
@GetMapping("/admin/hello")
public String admin(){
return "hello admin";
}
@GetMapping("/user/hello")
public String user(){
return "hello user";
}
}
官方 中文 文档:
https://www.springcloud.cc/spring-security-zhcn.html#core-components