# LWH10
**Repository Path**: lin8081/LWH10
## Basic Information
- **Project Name**: LWH10
- **Description**: SpringBoot整合SpringSecurity之入门案例
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2020-03-09
- **Last Updated**: 2020-12-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## SpringBoot整合SpringSecurity
### 1.基本介绍
**SpringSecurity概念**
1. SpringSecurity是一个安全管理框架,提供了认证与授权这些基本操作
2. 认证: 用户访问系统,系统校验用户身份是否合法的过程就是认证。常见的认证: 登陆认证。
3. 授权:用户认证后,访问系统资源,校验用户是否有权限访问系统资源的过程就是授权访问校验,简称为授权。权限校验过程:1.获取用户的权限; 2. 知道访问资源需要的权限;3.拿着访问资源需要的权限去用户权限列表查找,找到则授权访问。否则拒绝访问。
### 2.创建数据库表
一般权限控制有三层,即:`用户`<–>`角色`<–>`权限`,用户与角色是多对多,角色和权限也是多对多。这里我们先暂时不考虑权限,只考虑`用户`<–>`角色`。 这里为了测试,表结构简单设计,后续可以根据业务添加先关字段。
数据库:Mysql 5.6 创建表结构:
```sql
-- 用户表
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL COMMENT '账号',
`password` varchar(255) NOT NULL COMMENT '密码',
`true_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(50) DEFAULT NULL COMMENT '手机',
`email` varchar(25) DEFAULT NULL COMMENT '邮箱',
`createtime` varchar(25) DEFAULT NULL COMMENT '创建时间',
`status` int(2) DEFAULT NULL COMMENT '状态',
`remarks` varchar(1000) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
-- 角色表
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL,
`name` varchar(50) DEFAULT NULL COMMENT '角色名',
`roleDesc` varchar(50) DEFAULT NULL COMMENT '角色说明',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- 用户-角色关系表
CREATE TABLE `sys_user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
KEY `fk_role_id` (`role_id`),
CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
```
初始化数据:
```java
insert into `sys_role` values ('1', 'ROLE_ADMIN','管理员');
insert into `sys_role` values ('2', 'ROLE_USER','普通用户');
insert into `sys_user` (id, username,password) values ('1', 'admin', '123');
insert into `sys_user` (id, username,password) values ('2', 'user', '123');
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '2');
```
**注意:**这里的权限格式为`ROLE_XXX`,是Spring Security规定的,不要乱起名字。
### 3.准备页面
用于登录的 `login.html` 片段以及用户登录成功后跳转的 `index.html`,将其放置在工程 `resources/static` 目录下:
login.html片段:
```html
```
首页index.html
```html
Title
登陆成功
检测ROLE_ADMIN角色
检测ROLE_USER角色
```
### 4.导入依赖
```xml
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
mysql
mysql-connector-java
5.1.34
com.alibaba
druid-spring-boot-starter
1.1.16
org.projectlombok
lombok
1.16.16
```
### 5.配置application.yml
```xml
server:
port: 9000
spring:
application:
name: auth01
datasource:
druid:
url: jdbc:mysql://localhost:3306/auth01?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=UTC&characterEncoding=utf8
username: root
password: root
driverClassName: com.mysql.jdbc.Driver
initialSize: 5 #初始建立连接数量
minIdle: 5 #最小连接数量
maxActive: 20 #最大连接数量
maxWait: 10000 #获取连接最大等待时间,毫秒
testOnBorrow: true #申请连接时检测连接是否有效
testOnReturn: false #归还连接时检测连接是否有效
timeBetweenEvictionRunsMillis: 60000 #配置间隔检测连接是否有效的时间(单位是毫秒)
minEvictableIdleTimeMillis: 300000 #连接在连接池的最小生存时间(毫秒)
# mybatis
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml # mybatis配置文件位置
mapper-locations:
- classpath*:mapper/**/*.xml # mapper映射文件位置
type-aliases-package: com.lwh.model # 别名包
configuration:
cache-enabled: true
```
### 6.创建实体类、DAO、Service和Controller
实体类:
- SysUser:
```java
public class SysUser implements Serializable{
private static final long serialVersionUID = -2836223054703407171L;
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
```
- SysRole:
```java
public class SysRole implements Serializable {
private static final long serialVersionUID = 7510551869226022669L;
private Integer id;
private String name;
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;
}
}
```
- SysUserRole:
```java
public class SysUserRole implements Serializable{
private static final long serialVersionUID = -3256750757278740295L;
private Integer userId;
private Integer roleId;
public SysUserRole() {
}
public SysUserRole(Integer userId, Integer roleId) {
this.userId = userId;
this.roleId = roleId;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
}
```
DAO:
- SysUserMapper:
```java
@Mapper
public interface SysUserMapper {
SysUser selectById(Integer id);
SysUser selectByName(String username);
}
```
- SysRoleMapper:
```java
@Mapper
public interface SysRoleMapper {
SysRole selectById(Integer id);
}
```
- SysUserRoleMapper:
```java
@Mapper
public interface SysUserRoleMapper {
List listByUserId(Integer userId);
}
```
Service:
- SysUserService:
接口
```java
public interface SysUserService {
SysUser selectById(Integer id);
SysUser selectByName(String name);
}
```
实现类
```java
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper userMapper;
@Override
public SysUser selectById(Integer id) {
return userMapper.selectById(id);
}
@Override
public SysUser selectByName(String name) {
return userMapper.selectByName(name);
}
}
```
- SysRoleService:
接口
```java
public interface SysRoleService {
public SysRole selectById(Integer id);
}
```
实现类
```java
@Service
public class SysRoleServiceImpl implements SysRoleService {
@Autowired
private SysRoleMapper roleMapper;
@Override
public SysRole selectById(Integer id) {
return roleMapper.selectById(id);
}
}
```
- SysUserRoleService:
接口
```java
public interface SysUserRoleService {
List listByUserId(Integer userId);
}
```
实现类
```java
@Service
public class SysUserRoleServiceImpl implements SysUserRoleService {
@Autowired
private SysUserRoleMapper userRoleMapper;
@Override
public List listByUserId(Integer userId) {
return userRoleMapper.listByUserId(userId);
}
}
```
Controller:
```java
@Controller
public class LoginController {
private Logger logger = LoggerFactory.getLogger(LoginController.class);
@GetMapping("/login")
public String showLogin() {
return "/pages/login.html";
}
@GetMapping("/")
public String showHome() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
logger.info("当前登陆用户:" + username);
return "/pages/index.html";
}
@GetMapping("/admin")
@ResponseBody
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String printAdmin() {
return "如果你看见这句话,说明你有ROLE_ADMIN角色";
}
@GetMapping("/user")
@ResponseBody
@PreAuthorize("hasRole('ROLE_USER')")
public String printUser() {
return "如果你看见这句话,说明你有ROLE_USER角色";
}
}
```
**注意**:如代码所示:
- 获取当前登录用户:`SecurityContextHolder.getContext().getAuthentication()`
- `@PreAuthorize` 用于判断用户是否有指定权限,没有就不能访问
### 7.配置mapper.xml
UserMapper.xml
```xml
id,username,password,true_name,remarks,phone
```
RoleMapper.xml
```xml
id,name
```
UserRoleMapper.xml
```xml
user_id,role_id
```
### 8.配置SpringSecurity
#### 1.UserDetailService
自定义 `UserDetailService`, 将用户信息和权限注入进来。
CustomUserDetailsService:
```java
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private SysUserService userService;
@Autowired
private SysRoleService roleService;
@Autowired
private SysUserRoleService userRoleService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Collection authorities = new ArrayList<>();
// 从数据库中取出用户信息
SysUser user = userService.selectByName(username);
// 判断用户是否存在
if(user == null) {
throw new UsernameNotFoundException("用户名不存在");
}
// 添加权限
List userRoles = userRoleService.listByUserId(user.getId());
for (SysUserRole userRole : userRoles) {
SysRole role = roleService.selectById(userRole.getRoleId());
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
// 返回UserDetails实现类
return new User(user.getUsername(), user.getPassword(), authorities);
}
}
```
#### 2.WebSecurityConfig
Spring Security默认是禁用注解的,要想开启注解, 需要在继承`WebSecurityConfigurerAdapter`的类上加`@EnableGlobalMethodSecurity`注解, 来判断用户对某个控制层的方法是否具有访问权限
例如:上面代码方法前不加@preAuthorize注解,意味着所有用户都能访问方法,如果加上注解,表示只要具备指定角色的用户才有权限访问。也就是得继承`WebSecurityConfigurerAdapter`类并加上`@EnableGlobalMethodSecurity`注解才能在控制层加@preAuthorize注解
**@EnableGlobalMethodSecurity详解**:
- `@EnableGlobalMethodSecurity(securedEnabled=true)` 开启`@Secured` 注解过滤权限
- `@EnableGlobalMethodSecurity(jsr250Enabled=true)`开启`@RolesAllowed `注解过滤权限
- `@EnableGlobalMethodSecurity(prePostEnabled=true)`使用表达式实现方法级别的安全性 ,4个注解可用:
1. `@PreAuthorize` 在方法调用之前,基于表达式的计算结果来限制对方法的访问
2. `@PostAuthorize` 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
3. `@PostFilter` 允许方法调用,但必须按照表达式来过滤方法的结果
4. `@PreFilter `允许方法调用,但必须在进入方法之前过滤输入值
首先,我们将自定义的 `userDetailsService` 注入进来,在 `configure()` 方法中使用 `auth.userDetailsService()` 方法替换掉默认的 `userDetailsService `。
这里我们还指定了密码的加密模式(5.0版本强制要求设置),我们采用SpringSecurity提供的加密模式:`BCryptPasswordEncoder`,它帮我们实现了`PasswordEncoder`,当然也可以自定义加密模式。
- WebSecurityConfig.java
```java
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
// .antMatchers().permitAll()
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功页
.defaultSuccessUrl("/").permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll();
// 关闭CSRF跨域
http.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
web.ignoring().antMatchers(
"/**/**.gif",
"/**/**.jpg",
"/**/**.css",
"/**/**.jq",
"/**/**.js",
"/**/**.ttf",
"/**/**.woff",
"/**/**.woff2",
"/**/**.png");
}
}
```
### 9.运行测试
1.启动工程之前,由于数据库用户表的密码初始化的是明文,这里我们需要使用SpringSecurity 提供的加密工具类对密码进行重新加密修改。
加密类:
```java
public class SpringSecurityUtil {
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String password = bCryptPasswordEncoder.encode("123");
System.out.println(password);
}
}
```
加密后密文:$2a$10$z/GksRmh.CxJbP3nAiFWUu0yLfJ6YvUL04OxttZXBnClvEbU3KQgy
注:随机盐加密所以两次加密后结果不一样
2.启动工程:
ROLE_ADMIN 账户:用户名: admin,密码: 123
ROLE_USER 账户:用户名: user,密码: 123