# SpringSecurityInfo
**Repository Path**: supvegetable/spring-security-info
## Basic Information
- **Project Name**: SpringSecurityInfo
- **Description**: 这里是本人自学SpringSecurity的资料,希望能够填满
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2023-11-09
- **Last Updated**: 2023-12-04
## Categories & Tags
**Categories**: Uncategorized
**Tags**: SpringSecurity
## README
这是我的自学笔记,当然也可以是你的参考笔记,不适合直接照着我这个学。我是已经看过视频,了解过Spring Security的过滤器链以及基本流程。
所以本自学笔记只保留了实现的具体步骤。适合初学并且喜欢先看视频在动手做的朋友看这个笔记。(因为我也是小白啊,初学也不精,写下这篇希望也能帮助其他人)
# 1.Spring Security
##1.1创建项目,添加依赖
```xml
org.springframework.boot
spring-boot-starter-parent
2.5.4
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
```
添加过以上依赖,先运行确保项目没有问题
**注意**:此时还没有添加 Spring Security依赖
我们在做Web项目时,常常会涉及到用户登录,不同用户,可以使用的功能不一样。此时想要实现这种功能,常常是**在servlet请求之前**添加各种**拦截器**/**过滤器**,从而实现对各种请求的处理。对于开发人员来说,**自己编写完整可靠的拦截器难度过大,且浪费时间。**因此Spring提供了一款安全框架Spring Security供开发人员使用。
Spring Security依靠**过滤器链**实现了更完善的安全解决方案,包括身份认证、授权管理、攻击防护等功能。
## 1.2添加Spring Security依赖
```xml
org.springframework.boot
spring-boot-starter-security
```
Spring Security本身就是Spring家族中的一员,因此添加上Spring Security依赖,你的项目就已经被安全框架所保护。
运行项目,打开浏览器,访问项目会自动跳转到由Spring Security自带的登录页面**用户名默认**为:user,**密码:**控制台打印的一长串字符。
项目已经被保护起来,登录验证也有,此时走的是**默认过滤器中的方法**,那我们只需要重写Spring Security过滤器中查询用户的方法即可。
## 1.3重写查询用户的方法
### 3.1准备数据库表
```sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_menu 菜单权限表
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称',
`parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父类ID',
`path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '路由地址',
`component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径',
`menu_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单类型( M目录 C菜单 F按钮)',
`visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '锁定状态(0正常 1停用)',
`perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '菜单图标',
`created_by` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_by` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_role 角色信息表
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称',
`role_key` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_german2_ci NOT NULL COMMENT '角色权限字符串',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`created_by` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_by` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_role_menu 角色权限关联表
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色权限关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user 用户信息表
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户昵称',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '密码',
`sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`phonenumber` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '手机号码',
`avatar` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '用户头像',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`created_by` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_by` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for sys_user_role 用户角色关联表
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户角色关联表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
```
想省事此时可以**只创建用户信息表**,其他和权限相关的表现在还用不到
### 3.2用户的实体类
```java
//1.这里用了lombok自动生成get/set方法和有参与无参的构造方法
//2.@TableName等是Mybatis-Plus的功能,如果不知道可以改动实体类名为sysUser,对照数据库表修改一下即可。
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class User {
@TableId("user_id")
private String id;
//账号
private String userName;
//昵称
private String nickName;
//密码
private String password;
//0:男 1:女 2:未知
private int sex;
//电话
@TableField("phonenumber")
private String phoneNumber;
// 存储头像的字符串类型变
private String avatar;
//0:正常 1:禁用
private int status;
//是否删除
private int delFlag;
//使用Mybatis-Plus自动填充
@TableField("create_time")
private String createTime;
@TableField("update_time")
private String updateTime;
@TableField("created_by")
private String createBy;
@TableField("updated_by")
private String updateBy;
}
```
### 3.3添加依赖
我们希望的是可以**查询自己数据库里面的用户信息**,所以肯定要**引入数据库相关的依赖**
```xml
com.baomidou
mybatis-plus-boot-starter
3.4.2
com.alibaba
druid-spring-boot-starter
1.2.16
mysql
mysql-connector-java
8.0.13
```
### 3.4yml配置文件编写
```yaml
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/vegetable_frame?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
```
url,username,password**记得改成自己的**,mysql是**低版本**的话driver-class-name: com.mysql.jdbc.Driver用这个
### 3.5Mapper
编写UserMapper,数据层,查询数据库
```java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.htu.springsecurityfast.domain.User;
@Mapper
public interface UserMapper extends BaseMapper {
}
//自己写个单元测试,一步一步来,看看数据库这步有没有调通
```
###3.6Service重写方法就是在这里
重写UserDetailsService中的loadUserByUsername方法。
**如何重写?**UserDetailsService本身是一个接口,我们只需要编写一个实现类,就能重写该接口中的loadUserByUsername方法
```java
//编写UserDetailsService的实现类
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired//userMapper数据层查询
private UserMapper userMapper;
@Override
//注意返回值是UserDetails
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//Mybatis-Plus中的语法,可以根据自己的喜好自行修改
User user = userMapper.selectOne(new LambdaQueryWrapper().eq(User::getUserName, username));
if (ObjectUtils.isEmpty(user)){
throw new UsernameNotFoundException("用户名不存在");
}
//返回需要是UserDetails类型,Ctrl+单击,发现UserDetails是Spring Security提供的一个不可更改的接口,那我们只需要自己写一个实体对象去实现UserDetails即可
//接口没办法直接new生成,老老实实写实体类
//TODO 没有权限信息,后面在这里进行封装
return new LoginUser(user);
}
}
```
自己写一个实体类实现UserDetails
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;
@Override
//权限列表,一个用户可以拥有许多权限,此时我们只做登录验证,这里先不考虑,返回null即可
public Collection extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
//用户的密码
public String getPassword() {
return user.getPassword();
}
@Override
//用户名
public String getUsername() {
return user.getUserName();
}
@Override
//账号是否正常
public boolean isAccountNonExpired() {
return true;
}
@Override
//是否被锁定
public boolean isAccountNonLocked() {
return true;
}
@Override
//验证是否没有过期
public boolean isCredentialsNonExpired() {
return true;
}
@Override
//是否启用
public boolean isEnabled() {
return true;
}
}
```
此时登录查询自己的数据库功能已经完成。
**注意**:数据库的密码此时明文存储需要在前面加上{noop}
### 3.7添加BCryptPasswordEncoder
Spring Security**自带**的密码加密,相对BCryptPasswordEncoder来说不够方便,存储在数据库中的密码需要根据**加密规则添加{}表示**,不方便操作。因此使用BCryptPasswordEncoder。
在IOC容器中注入BCryptPasswordEncoder,Spring Security会自动使用。配置大于约定思想。有了就用你的,没了就用我自己的
**新增一个SecurityConfig对Spring Security进行配置**
```java
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```
## 1.4重写登录/退出
Spring Security默认是有登录和退出页面的,不嫌弃也可以用,哈哈~
接口**三要素**:Controller,Service,Mapper。Mapper的数据层上一步已经改写过Spring Security,查询我们自己的数据库。在**重写UserDetailsService中loadUserByUsername(String username)方法时**,拥**有一个username**,这个username其实就是**过滤器链一步步传递下来**的。那接下来就只需要聚焦**如何把我们提交的数据塞给Spring Security**。
Spring Security中的**用户信息都被封装进Authentication**,然后**塞入方法中进行传递**。那么此时**目标明确**,我们只需要把**浏览器传递过来的用户信息塞进Authentication**,并**传入认证方法**中即可。
**-------------------------------------------------------------------------------------------------------------------------------------------------------**
**我的疑惑**:在认证之后用户信息会被放进SecurityContextHolder上下文中,为什么还要**Redis**缓存用户信息。
**自己查的资料**:这个上下文对象中的用户信息会在**过滤器链走完只会自动清除**,所以**每次都要验证**,因此需要Redis缓存用户信息,然后配合上JWT过滤器,如果用户有效,则**用Redis中取出的数据放入SecurityContextHolder上下文中**,这样不仅方便**后续过滤器验证**,而且是前后端分离项目认证的一中解决方案。
为尽量考虑系统的可用性,因此我们**增加Redis和JWT**。做着玩的话,可用忽略这些,只去弄Controller,Service,Mapper即可。
### 4.1准备Redis
1. 引入Redis依赖
```xml
org.springframework.boot
spring-boot-starter-data-redis
```
2. yml配置文件编写
```yaml
spring:
redis:
port: 6379
host: 127.0.0.1
#有账号密码的自己改一下
```
3. 配置Redis序列化方式
直接往Redis里面存会出现乱码,不方便操作,需要配置一下序列化方式。
```java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate