SpringBoot整合Spring Security (一,基于数据库的登录认证)
一、基本环境准备
1、数据库表设计
登录认证一般涉及到三张表:用户表、角色表、用户角色中间表。
/*
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;
2.pom管理用到的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<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.14</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.配置文件
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/permission?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:com/permission/mapper/*Mapper.xml
type-aliases-package: com.permission.domain
4.启动类
@SpringBootApplication
@MapperScan(value = "com.permission.mapper")
public class PermissionApplication {
public static void main(String[] args) {
SpringApplication.run(PermissionApplication.class,args);
}
}
5.实体类
- User
/**
* @Author:lvyuanjie
* @Date:2020/5/2 15:10
*/
public class User {
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean locked;
private List<Role> roles;
//省略getter 、setter 方法
- Role
/**
* @Author:lvyuanjie
* @Date:2020/5/2 15:11
*/
public class Role {
private Integer id;
private String name;
private String nameZh;
//省略getter 、setter 方法
6.Mapper 和 Mapper.xml
1.UserMapper
/**
* @Author:lvyuanjie
* @Date:2020/5/2 15:24
*/
public interface UserMapper {
/**
* 通过用户名获取用户信息
*
* @param username 用户名
* @return User 用户信息
*/
User loadUserByUsername(String username);
/**
* 通过用户id获取用户角色集合
*
* @param userId 用户id
* @return List<Role> 角色集合
*/
List<Role> getUserRolesByUserId(Integer userId);
}
2.UserMapper.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="com.permission.mapper.UserMapper">
<select id="loadUserByUsername" resultType="com.permission.domain.User">
select * from user where username=#{username}
</select>
<select id="getUserRolesByUserId" resultType="com.permission.domain.Role">
select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{userId}
</select>
</mapper>
二、使用Spring Security的三步(实现三个Spring Security的接口)
1.实现 UserDetails 接口
这个接口是用来将用户的信息给spring security框架进行验证用的,实现方式就是用户表的实体类实现这个接口就可以
/**
* @Author:lvyuanjie
* @Date:2020/5/2 15:10
*/
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 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 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 password;
}
@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;
}
}
UserDetails 接口的7个方法
方法名 | 解释 |
---|---|
getAuthorities() | 获取当前用户的所有角色信息 |
getPassword() | 获取当前用户的密码 |
getUsername() | 获取当前用户的用户名 |
isAccountNonExpired() | 当前账户是否未过期 |
isAccountNonLocked() | 当前账户是否未锁定 |
isCredentialsNonExpired() | 当前账户密码是否未过期 |
isEnabled() | 当前账户是否可用 |
2.实现 UserDetailsService 接口
这个接口只有一个方法:loadUserByUsername(String s) ,s是用户登录时输入的账户名,这个方法就是通过用户输入的账户名判断该账户是否存在,存在才进行用户角色信息的查找,然后才有DaoAuthenticationProvider校验密码
/**
* @Author:lvyuanjie
* @Date:2020/5/2 15:22
*/
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(s);
if (user == null) {
throw new UsernameNotFoundException("该用户不存在!");
}
user.setRoles(userMapper.getUserRolesByUserId(user.getId()));
return user;
}
}
3.继承 WebSecurityConfigurerAdapter
1.将刚创建的UserService 配置到AuthenticationManagerBuilder 中
2. AuthenticationSuccessHandler() :自定义登录成功或失败的返回信息(这里采用的匿名内部类配的,也可以单独写一个类来配置)
3.successHandler():将自定义的登录成功信息配置进去
4. failureHandler():将自定义登录失败的信息配置进去
/**
* @Author:lvyuanjie
* @Date:2020/5/2 11:15
*/
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login").permitAll()
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
Object principal = auth.getPrincipal();
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(200);
Map<String, Object> map = new HashMap<>(16);
map.put("status",200);
map.put("msg",principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(401);
Map<String, Object> map = new HashMap<>(16);
map.put("status", 401);
if (e instanceof LockedException) {
map.put("msg", "账户被锁定,登录失败!");
} else if (e instanceof BadCredentialsException) {
map.put("msg", "账户名或密码输入错误,登录失败!");
} else if (e instanceof DisabledException) {
map.put("msg", "账户被禁用,登录失败!");
} else if (e instanceof AccountExpiredException) {
map.put("msg", "账户已过期,登录失败!");
} else if (e instanceof CredentialsExpiredException) {
map.put("msg", "密码已过期,登录失败!");
} else {
map.put("msg", "登录失败!");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.and()
.csrf().disable();
}
}
4.工程结构
三、测试
账户名:admin 密码:123
使用错误密码登录