SpringBoot 学习笔记_安全管理 —— 基于数据库的认证
声明:
本次学习参考 《SpringBoot + Vue 开发实战》 · 王松(著) 一书。
本文的目的是记录我学习的过程和遇到的一些问题以及解决办法,其内容主要来源于原书。
如有侵权,请联系我删除
文章目录
SpringBoot 安全管理 —— 基于数据库的认证
实际项目中,用户的基本认证信息都是存储在数据库中,因此需要从数据中获取数据进行认证。
设计数据表
CREATE DATABASE `security` DEFAULT CHARACTER
SET utf8;
USE `security`;
# 创建用户表
CREATE TABLE USER (
`id` INT ( 11 ) NOT NULL auto_increment,
`username` VARCHAR ( 32 ) DEFAULT NULL,
`password` VARCHAR ( 255 ) DEFAULT NULL,
`enabled` TINYINT ( 1 ) NOT NULL DEFAULT 1,
`locked` TINYINT ( 1 ) NOT NULL DEFAULT 0,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT charset = utf8;
# 创建角色表
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 DEFAULT CHARSET = utf8;
# 创建关联表
CREATE TABLE user_role (
`id` INT ( 11 ) NOT NULL auto_increment,
`uid` INT ( 11 ) NOT NULL,
`rid` INT ( 11 ) NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8;
# 插入示例数据 (密文可以通过代码 Utils 类中的 EncryptBCrypt 方法获取)
INSERT INTO `USER` ( `id`, `username`, `password` )
VALUES
( 1, 'root', '$2a$10$prmKKnrA38zwSXuK35P/Wejk2OxagVOb13c4OOnl3/5w4ft61g/fG' ),
( 2, 'admin', '$2a$10$gSYELUIs8RzegyPGREIvWOUNXjuqBLk0qwmTSLRSEDDw1iwZIWhri' ),
( 3, 'sang', '$2a$10$uurxv5sKDcOPImZJWJ9hcO2auxOu8k58i/MBVQ40gzE6WWYln8AOG' );
insert into `ROLE` ( `id`, `name`, `nameZH` )
VALUES
(1, 'ROLE_dba', '数据库管理员'), # 角色名默认前缀 ROLE_
(2, 'ROLE_admin', '系统管理员'),
(3, 'ROLE_user', '用户');
INSERT INTO `USER_ROLE` ( `id`, `uid`, `rid` )
VALUES
(1, 1, 1), # root 数据库管理员
(2, 1, 2), # root 系统管理员
(3, 2, 2), # admin 系统管理员
(4, 3, 3); # sang 用户
创建 SpringBoot 项目,添加依赖
<!-- 添加 security 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 添加 mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 添加 mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<build>
...
<!-- 整合 MyMyBatis 配置 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
...
</build>
配置数据库
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql:///security?serverTimezone=GMT
创建实体类
-
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; /** * 获取当前用户对象所具有的的角色信息 * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : roles){ authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } /** * 获取当前用户对象的密码 * @return */ @Override public String getPassword() { return password; } /** * 获取当前用户对象的用户名 * @return */ @Override public String getUsername() { return username; } /** * 当前账户是否未过期 * @return */ @Override public boolean isAccountNonExpired() { return true; } /** * 当前账户是否未锁定 * @return */ @Override public boolean isAccountNonLocked() { return !locked; } /** * 当前账户密码是否未过期 * @return */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 当前账户是否可用 * @return */ @Override public boolean isEnabled() { return enabled; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } //public Boolean getEnabled() { // return enabled; //} public void setEnabled(Boolean enabled) { this.enabled = enabled; } public Boolean getLocked() { return locked; } public void setLocked(Boolean locked) { this.locked = locked; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", enabled=" + enabled + ", locked=" + locked + ", roles=" + roles + '}'; } }
-
Role
public class Role { private Integer id; private String name; private String nameZH; /* Setter & Getter */ }
创建 Mapper 和 Service
@Repository
@Mapper
public interface UserMapper {
User loadUserByUsername(String username);
List<Role> getUserRolesByUid(Integer id);
}
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace="org.sang.mapper.UserMapper">
<select id="loadUserByUsername" resultType="org.sang.bean.User">
select * from user where username=#{username}
</select>
<select id="getUserRolesByUid" resultType="org.sang.bean.Role">
select * from role r , user_role ur where r.id = ur.rid and ur.uid = #{id}
</select>
</mapper>
@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 Not Found");
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
配置 Spring Security
@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);
}
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("admin")
.antMatchers("/db/**")
.hasRole("dba")
.antMatchers("/user/**")
.hasRole("user")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
}
}
创建工具类
public class BCryptUtils {
static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
/**
* 加密 BCrypt 获取指定字符串对应的密文
* @return
*/
public static String EncryptBCrypt(String s){
System.out.println(passwordEncoder.encode(s));
return passwordEncoder.encode(s);
}
}
创建 Controller 测试
@RestController
public class LoginController {
@Autowired
UserService service;
@GetMapping("/login")
public String login(String username){
UserDetails userDetails = service.loadUserByUsername(username);
return userDetails.getUsername();
}
}
启动项目,访问登录页面测试
localhost:8080
输入用户名密码进行测试
遇到的问题 1
-
问题描述:
Invalid bound statement (not found)
-
问题原因:
pom.xml
中遗漏了 MyBatis 配置 -
解决方法:
<build> ... <!-- 整合 MyMyBatis 配置 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> ... </build>
遇到的问题 2
-
问题描述:
Illegal overloaded getter method with ambiguous type for property enabled in class class org.sang.bean.User ...
-
问题原因:
MyBatic 反射异常。 Java 内部 Boolean 类型属性的 getter 方法默认为 is 开头或者 get 开头的。
我们自定义的 User 类继承了 UserDetails 接口,实现了 isEnabled 方法。
而在使用快捷键生成 setter 和 getter 方法时,又自动生成了 getEnabled 方法。
此时,Java 会将这两个方法都认为是 bean 的属性封装,在反射的时候,就不知道使用哪个了,所以抛出了异常。
-
解决方法:
屏蔽/删除快捷键自动生成的
getEnabled
方法
遇到的问题 3
-
问题描述:
Encoded password does not look like BCrypt
-
问题原因:
用户名/密码校验失败。 admin 用户的密码在数据库中首次创建时输入了 admin 明文,而登录校验时使用了 BCrypt 加密规则校验。
-
解决方法:
将数据库中存储的明文密码修改成对应密文。
可以通过上面工具类中的 EncryptBCrypt 方法对明文密码进行 BCrypt 规则的加密,并将结果更新到数据库。
BCrypt 是强哈希函数,明文密码相同,但加密后的密文并不一定一样,需要注意