目录
[零、Spring Security 简介](#零、Spring Security 简介)
[1.2 编写配置文件](#1.2 编写配置文件)
[1.2.1 创建配置文件](#1.2.1 创建配置文件)
[1.2.2 编写配置文件](#1.2.2 编写配置文件)
[1.3 准备控制器](#1.3 准备控制器)
[1.4 编写启动类](#1.4 编写启动类)
[1.5 准备展示页面](#1.5 准备展示页面)
[1.6 运行程序( SpringBootMain )](#1.6 运行程序( SpringBootMain ))
[二、了解 Spring Security 认证的逻辑:](#二、了解 Spring Security 认证的逻辑:)
[2.3.0 建表,编写实体类,数据访问层(mapper)](#2.3.0 建表,编写实体类,数据访问层(mapper))
[2.3. 了解UserDetailsService接口逻辑:](#2.3. 了解UserDetailsService接口逻辑:)
[2.3.1 创建一个业务层实现类 EmpServiceImpl,实现 UserDetailsService 接口](#2.3.1 创建一个业务层实现类 EmpServiceImpl,实现 UserDetailsService 接口)
[2.3.2 在 EmpServiceImpl 重写 loadUserByUsername 方法](#2.3.2 在 EmpServiceImpl 重写 loadUserByUsername 方法)
[2.3.2-1 分析UserDetailsService接口中方法的返回值:](#2.3.2-1 分析UserDetailsService接口中方法的返回值:)
[2.3.3 编写测试类 DemoTest](#2.3.3 编写测试类 DemoTest)
[2.3.3-1 报错:](#2.3.3-1 报错:)
[2.3.3-2 PasswordEncoder 接口](#2.3.3-2 PasswordEncoder 接口)
[2.3.3-3 了解SpringSecurity 的加密算法 ==> 密码解析器:](#2.3.3-3 了解SpringSecurity 的加密算法 ==> 密码解析器:)
[2.3.4 运行 test1](#2.3.4 运行 test1)
[3.1 编写登录页面( empLogin.html )](#3.1 编写登录页面( empLogin.html ))
[3.2 修改控制类( EmpController )](#3.2 修改控制类( EmpController ))
[3.3 编写自定义配置类 MyConfig](#3.3 编写自定义配置类 MyConfig)
[3.4 自定义登录成功和失败页面](#3.4 自定义登录成功和失败页面)
[3.5 编写登录成功后跳转的页面( success.html )](#3.5 编写登录成功后跳转的页面( success.html ))
[3.6 运行 SpringBootMain](#3.6 运行 SpringBootMain)
[四、记住我 功能( Remember Me )](#四、记住我 功能( Remember Me ))
[4.1. 创建 RememberMeConfig 配置类( + 建表)](#4.1. 创建 RememberMeConfig 配置类( + 建表))
[4.2 修改登录页面( empLogin.html )](#4.2 修改登录页面( empLogin.html ))
[4.3 修改配置文件( MyConfig )](#4.3 修改配置文件( MyConfig ))
[4.3.1 remember Me 功能设置](#4.3.1 remember Me 功能设置)
[4.3.2 依赖注入](#4.3.2 依赖注入)
[4.3.3 还可以设置有效时间](#4.3.3 还可以设置有效时间)
[4.3.4 运行](#4.3.4 运行)
[4.3.4-1 如果不能自动创建保存我的信息表](#4.3.4-1 如果不能自动创建保存我的信息表)
[4.4 代码没错,但就是运行不了 / 报错:](#4.4 代码没错,但就是运行不了 / 报错:)
[5.1 新建数据库表](#5.1 新建数据库表)
[5.2 修改EmpMapper接口](#5.2 修改EmpMapper接口)
[5.3 添加 EmpMapper.xml 映射文件](#5.3 添加 EmpMapper.xml 映射文件)
[5.3.1 如果 mapper 接口上没出现小鸟图标:](#5.3.1 如果 mapper 接口上没出现小鸟图标:)
[5.4 修改 EmpServiceImpl,为用户动态添加权限](#5.4 修改 EmpServiceImpl,为用户动态添加权限)
[5.5 修改配置类:为指定路径 设置权限](#5.5 修改配置类:为指定路径 设置权限)
[5.6 运行](#5.6 运行)
[5.7 添加错误页面](#5.7 添加错误页面)
[六、 整合 Thymeleaf](#六、 整合 Thymeleaf)
[6.1 在 pom.xml 添加启动器](#6.1 在 pom.xml 添加启动器)
[6.2 修改配置类 MyConfig](#6.2 修改配置类 MyConfig)
[6.3 修改 success 页面](#6.3 修改 success 页面)
[6.4 测试结果:](#6.4 测试结果:)
[如果:ls 登录后也是四个功能:](#如果:ls 登录后也是四个功能:)
[七、退出 功能](#七、退出 功能)
[7.1 修改 success 页面:添加退出链接](#7.1 修改 success 页面:添加退出链接)
[八、 CSRF](#八、 CSRF)
[8.1 什么是 CSRF](#8.1 什么是 CSRF)
[8.2 Spring Security 中 CSRF](#8.2 Spring Security 中 CSRF)
[8.3 Spring Security 中 CSRF原理](#8.3 Spring Security 中 CSRF原理)
[8.4 实现步骤](#8.4 实现步骤)
[8.4.2 修改配置类 MyConfig](#8.4.2 修改配置类 MyConfig)
[9.1 在配置类 MyConfig 添加完整的注销功能配置](#9.1 在配置类 MyConfig 添加完整的注销功能配置)
[9.2 修改成功页面( success.html )的注销功能](#9.2 修改成功页面( success.html )的注销功能)
[9.3 创建新的注销成功页面 logoutSuccess.html](#9.3 创建新的注销成功页面 logoutSuccess.html)
[9.4 运行](#9.4 运行)
零、Spring Security 简介

Spring Security 是一个高度自定义的安全框架。利用 Spring IoC/DI 和 AOP 功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。
"认证(authentication)"和"授权(authorization)" (或者访问控制)是 Spring Security 重要核心功能。"认证" ,是建立一个他声明的主体的过程(一个"主体"一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。**"授权"**指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否有权限去做某些事情。
需要有权限控制的项目都可以使用Spring Security。
一、创建一个Maven项目

1.1.添加启动器
在 pom.xml 的<project>里添加:
XML
<!--1.继承方式添加SpringBoot启动器 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
</parent>
XML
<!-- 2.添加Spring MVC的启动器依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<!-- 3.添加Mybatis 的启动器依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- 4.添加mysql数据库的驱动包依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version> <!--<version>5.1.8</version> -->
</dependency>
<!--5. 添加thymeleaf 的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<!--6.添加lombok 注解依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<!--7.添加Spring Security启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
XML
<build>
<!--添加tomcat插件 -->
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
<!--资源拷贝的插件 -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.xml</include>
<include>**/*.html</include> // 很重要!!
<include>**/*.js</include> // 很重要!!
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
在 Maven 项目中,pom.xml文件中的标签是有特定顺序要求的,常见的标签顺序:
- 基础信息相关
- modelVersion(必须,且通常为 4.0.0)
- groupId、artifactId、version(GAV 三要素,必须)
- packaging(可选,默认 jar)
- name、description、url等描述信息
- 依赖管理相关
- parent(父项目配置)
- modules(聚合模块配置)
- properties(属性定义)
- dependencies(项目依赖)
- dependencyManagement(依赖版本管理)
- 构建配置相关
- build(构建配置,包括sourceDirectory、plugins等)
- profiles(环境配置文件)
具体添加位置如下:
添加完刷新一下Maven(大部分不需要,我的需要这一步)
方法一:

方法二:

1.2 编写配置文件
1.2.1 创建配置文件
先在 src 的 main 包下创建 一个 resources目录

有这个 resources就直接双击,没有就自己写然后按回车,创建完长这样 ↓↓↓
如果你的 resources 长这样,没有橙色的横杠:
进入项目结构:项目--项目结构 / file(左上角的四条杠)--Project Strucrure

在 resources 下创建配置文件 application.yml (不要打错字了)

,回车
application.yml 长这样 ↓↓↓

1.2.2 编写配置文件
注意格式,注意最近,冒号后面必须有一个空格
按自己的具体情况修改端口号(8866),url中的数据库名(security),用户名,密码(root),包名(com.jr,pojo,com/jr/mapper/*.xml)等
XML
server:
port: 8866
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/security?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
mybatis:
type-aliases-package: com.jr.pojo
mapper-locations: classpath:com/jr/mapper/*.xml
configuration: ##控制台输出sql语句
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
1.3 准备控制器

java
@Controller
public class EmpController {
@RequestMapping("/show")
public String show(){
return "show";
}
}
报红就点 import class

1.4 编写启动类
在 controller 包的上级包下面创建启动类 SpringBootMain并编写内容,这里的注解 @MapperScan("com.jr.mapper"),要么在这写,要么在每个mapper接口上写 @Mapper
java
@SpringBootApplication
//@MapperScan("com.jr.mapper")
public class SpringBootMain {
public static void main(String[] args) {
SpringApplication.run(SpringBootMain.class,args);
}
}

1.5 准备展示页面
在 resources下创建 templates,在 templates 里创建show.html 页面,在body里写内容
1.6 运行程序( SpringBootMain )
在浏览器输入:127.0.0.8866/show,会自动跳到 login 页面( Spring Security 自带的 )

用户名是 user,密码是控制台里输出的 ↓↓↓ 每次都不一样

就能进到show页面了

因为密码每次都不一样,退出刷新再用这个密码就登不了了

另外,如果是直接输入 127.0.0.1:8866**/login** ,登录成功也会出现404错误
因为本身跳到 login 页面只是拦截 ,登录成功 后Spring Security默认 会重定向 到用户最初请求的页面 (即 /login),但由于我们并没有 实际开发**/login 这个页面** (Spring Security 自带的登录页面是框架内部的,并非我们的 show.html),所以会出现 404 错误。
二、了解 Spring Security 认证的逻辑:
2.1.了解页面逻辑
在登录页右键点击查看网页源码

所以重新写登录页面时也要按这个逻辑写,表单的 method 叫 "post" ,表单的action属性指向后端处理登录请求的接口地址,用户名叫 "username",密码叫 " password" ,提交按钮的 type 要设为 "submit"
2.2.了解认证逻辑

2.3.0 建表,编写实体类,数据访问层(mapper)
emp表 Emp类
Mapper接口 :在 java.com.jr.mapper 下创建
要么每个mapper接口上都写 @Mapper注解,
要么在启动类(SpringBootMain)上面加一个注解:@MapperScan("com.jr.mapper")
java
//@Mapper
public interface EmpMapper {
@Select("select * from emp where username=#{username}")
Emp selectEmpByEname(String username);
}
2.3. 了解UserDetailsService接口逻辑:
2.3.1 创建一个业务层实现类 EmpServiceImpl,实现 UserDetailsService 接口
当什么也没有配置 的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目 中账号和密码都是从数据库 中查询出来的。 所以我们要通过自定义逻辑 控制认证逻辑。如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。
<-- 创建,↓实现接口(+ @Service 注解)

按住 Ctrl点击 UserDetailsService 进入 UserDetailsService 接口查看 ↓↓↓

因为数据库中存入的密码是加密的,用户输入内容在数据库中是查询不到的。
2.3.2 在 EmpServiceImpl 重写 loadUserByUsername 方法
右键点击Generate / alt+insert --> Override Methods --> 重写 loadUserByUsername 方法 一整个EmpServiceImpl的代码 ↓↓↓
java
@Service
public class EmpServiceImpl implements UserDetailsService {
@Autowired
private EmpMapper empMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Emp emp = empMapper.selectEmpByEname(s);
if (emp == null) {
throw new UsernameNotFoundException("用户名不存在,登录失败!");
}
// AuthorityUtils.NO_AUTHORITIES:默认
UserDetails user = new User(emp.getUsername(), emp.getPassword(), AuthorityUtils.NO_AUTHORITIES);
return user;
}
}
2.3.2-1 分析UserDetailsService接口中方法的返回值:
loadUserByUsername 方法的返回值 UserDetails 是一个接口 ↓↓↓
UserDetails 对象是SpringSecurity封装的,包含了 username 查到的对象的所有信息。

要想返回 UserDetails 的实例就只能返回接口的实现类。Spring Security 中提供了如下的实例。对于我们只需要使用里面的User 类即可。注意 User 的全限定路径是:
org.springframework.security.core.userdetails.User 此处经常和系统中自己开发的 User 类弄混。 不要导错包了!!

其中构造方法有两个,调用其中任何一个都可以实例化UserDetails实现类User类的实例。而三个参数的构造方法实际上也是调用7个参数的构造方法。

username:用户名, password:密码, authorities:用户具有的权限。此处不允许为null

此处的用户名 应该是客户端传递 过来的用户名。而密码 应该是从数据库中查询出来的密码。Spring Security 会根据 User 中的 password 和客户端传递过来的 password 进行比较。如果相同则表示认证通过,如果不相同表示认证失败。
2.3.3 编写测试类 DemoTest
在 test.java.com.jr 下创建测试类 DemoTest

java
public class DemoTest {
@Test
public void test1(){
// 创建解析器
PasswordEncoder encoder = new BCryptPasswordEncoder();
// 对密码进行加密
String password = encoder.encode("123");
System.out.println("------------"+password);
// 判断原字符加密后和内容是否匹配
boolean result = encoder.matches("123",password);
System.out.println("============="+result);
}
}
2.3.3-1 报错:
@Test 报红:导包

test1 方法前面没有运行按钮(绿色的三角)就到 pom.xml 里添加依赖
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <!-- 仅在测试环境生效,不打包到生产 -->
</dependency>
install 后控制台报错:程序包 org.junit不存在

修改 pom.xml 中 junit 依赖的版本(可能是版本低了)修改后刷新 Maven,重新clean,install
-->
2.3.3-2 PasswordEncoder 接口
encode():把参数按照特定的解析规则进行解析。
matches():验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。

2.3.3-3 了解SpringSecurity 的加密算法 ==> 密码解析器:
Spring security 中有多种密码加密方式,MD5 算法的 Md5PasswordEncoder、SHA 算法的 ShaPasswordEncoder,但由于是弱加密算法都被弃用了。推荐使用的是 BCrypt 算法的 BCryptPasswordEncoder。

BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析器。BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认10。
2.3.4 运行 test1
右下角的弹窗都点这个 Enable annotation processing 就好了

输出的是 123 加密后的字符串,把这个写到 Emp 表的 password 字段(每次运行出来的都不一样,存哪个都行)


为什么加密同一个内容 ,多次执行后,结果不一样 ?因为概率问题,假如两个人的密码一样,加密后也一样。一旦知道其中一个人的密码,那么就会知道其他人的,不安全。
三、自定义登录页面
3.1 编写登录页面( empLogin.html )
在 templates下创建 empLogin.html
在 body 里写:
html
<b>这是一个(相对)漂亮的登录页面!</b>
<hr/>
<form action="/elogin" method="post">
员工姓名:<input type="text" name="username"/><br/>
员工姓名:<input type="text" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
表单提交地址不指定默认是login;此时访问当前登录页面,需要地址栏输入具体页面名称和地址访问。
3.2 修改控制类( EmpController )
java
@Controller
public class EmpController {
@RequestMapping("/{url}")
public String show(@PathVariable String url){
return url;
}
}
有了这个控制器类之后,地址里输入页面名称就可以,这样 ↓↓↓( 这是 MyConfig )

3.3 编写自定义配置类 MyConfig
在 src.main.java.com.jr 下创建 config 包,在里面创建 MyConfig类

继承 WebSecurityConfigurerAdapter 类,重写 configure 方法(参数类型是 HttpSecurity,不要选错了),所有需要自定义 的地方都在这方法里配置

一整个 MyConfig 的代码 ↓↓↓
java
@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.配置表单认证:
http.formLogin()
.loginProcessingUrl("/elogin") //配置自定义登录页面中 表单的action提交地址。使用默认/login,可以不进行配哦之
.loginPage("/empLogin") //登录页面对应地址
.successForwardUrl("/success") //设置登录成功后的跳转页面
.failureForwardUrl("/empLogin");//设置登录失败后的跳转页面,也可以再整一个 failure.html 页面
//2.配置权限:
http.authorizeRequests()
.antMatchers("/empLogin").permitAll() //设置路径放行
.anyRequest().authenticated(); //设置路径拦截
//3.关闭csrf防护
http.csrf().disable();
}
/*指定SpringSecurity认证的加密算法*/
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
3.4 自定义登录成功和失败页面

3.5 编写登录成功后跳转的页面( success.html )
位置: 内容:
3.6 运行 SpringBootMain

在网址栏输入 127.0.0.1:8866/show ,会自动跳转到 127.0.0.1:8866/empLogin 页面

按 emp 表里的,输入 zs 和 123 再输入 127.0.0.1:8866/show 就可以跳转了
四、记住我 功能( Remember Me )
4.1. 创建 RememberMeConfig 配置类( + 建表)
在 config 包下创建 RememberMeConfig
java
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository getPersistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
jdbcTokenRepositoryImpl.setDataSource(dataSource);
// 自动 建表 ,第一次启动时需要,第二次启动时注释掉 ↓
jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
return jdbcTokenRepositoryImpl;
}
}
其中,倒数第二行的 jdbcTokenRepositoryImpl.setCreateTableOnStartup(true); 这段是用来建表 的,第一次运行时会建表,运行完要注释掉,不然下次运行会报错。
↓↓↓ 报的错有点多,主要的是最后一张里标出来的那个 Table 'persistent_logins' already exists



4.2 修改登录页面( empLogin.html )
添加代码:记住我:<input type="checkbox" name="remember-me" value="true"/> <br/>

4.3 修改配置文件( MyConfig )
4.3.1 remember Me 功能设置
java
//3.remember Me功能设置:
http.rememberMe()
.userDetailsService(empService) // 登录逻辑交给哪个对象
.tokenRepository(repository) // 持久层对象
.tokenValiditySeconds(60*30); //设置 remember Me 的 token 值有效时间为30分钟
位置:

报红:在 MyConfig 类里加下面这个,进行自动配置↓↓↓
4.3.2 依赖注入
java
@Autowired
private PersistentTokenRepository repository;
@Autowired
private EmpServiceImpl empService;
//EmpServiceImpl实现了SpringSecurity的认证接口的实现类
位置:

4.3.3 还可以设置有效时间
默认的有效时间是 14 天(1209600 秒)
上面( 4.3.1 )的 .tokenValiditySeconds(60*30) 就是,单位是秒
【说明】:重启服务器之后,仍然可以记住你!访问 /show,可以直接跳转到 show 页面,不会再要求登录了。
4.3.4 运行
先运行一遍 -- 建表)

到 RemenbermeConfig 注释掉建表的代码

在网址栏里输入:127.0.0.1:8866/show ,拦截到登录页面,输入信息,勾选"记住我"选项,登录
运行完去数据库找 persistent_logins 表:↓ 保存了这次登录的数据

重启项目(确定把建表的代码给注掉了),直接访问 127.0.0.1:8866/show 不会被拦截

4.3.4-1 如果不能自动创建保存我的信息表
可以使用建表语句:
sql
-- 创建数据库 security ,创建数据库表persistent_logins语句:
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
4.4 代码没错,但就是运行不了 / 报错:
在生命周期里 clean 再 install:↓ 在项目右侧找到 "m",单击 1,2~5 双击

又或者试试刷新一下 maven 项目(然后再 clean,install ):
方法一:

方法二:在项目右击,滑到底部找到 Maven,点击 Sync Project

五、授权逻辑
5.1 新建数据库表
5.2 修改EmpMapper接口
java
//@Mapper
public interface EmpMapper {
@Select("select * from emp where username=#{username}")
Emp selectEmpByEname(String username);
/**
* 根据用户名查询用户权限
* @param username
* @return
*/
List<String> selectPnameByUsername(String username);
}
5.3 添加 EmpMapper.xml 映射文件
在 resources 的 com\jr\mapper 下创建(这里必须用斜杠)
xml 映射文件的路径要跟 Mapper 接口的一样,名字、方法名、参数类型、返回值类型都要一样
-->
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.jr.mapper.EmpMapper">
<select id="selectPnameByUsername" resultType="string" parameterType="string">
select p.pname
FROM emp e
inner join role_emp re on e.eid=re.eid
inner join role r on re.rid=r.rid
inner join role_power rp on r.rid=rp.rid
inner join power p on rp.pid=p.pid
where e.username=#{username}
</select>
</mapper>

5.3.1 如果 mapper 接口上没出现小鸟图标:
刷新 Maven ( 本篇的 4.4 )
5.4 修改 EmpServiceImpl,为用户动态添加权限
java
@Service
public class EmpServiceImpl implements UserDetailsService {
@Autowired
private EmpMapper empMapper;
@Autowired
private Emp emp;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Emp emp = empMapper.selectEmpByEname(s);
if (emp == null) {
throw new UsernameNotFoundException("用户名不存在,登录失败!");
}
// ********这下面是添加,修改的********
// 查询用户对应的权限
List<String> listPermission = empMapper.selectPnameByUsername(s);
List<SimpleGrantedAuthority> listAuthority = new ArrayList<SimpleGrantedAuthority>();
for(String permisssion : listPermission){
listAuthority.add(new SimpleGrantedAuthority(permisssion));
}
return new org.springframework.security.core.userdetails.User(emp.getUsername(),emp.getPassword(),listAuthority);
// AuthorityUtils.NO_AUTHORITIES:默认
/*UserDetails user = new User(emp.getUsername(), emp.getPassword(), AuthorityUtils.NO_AUTHORITIES);
return user;*/
}
}
5.5 修改配置类:为指定路径 设置权限
修改 MyConfig类的 "2.配置权限" 部分:( 就是添加 "/show" 那一行 )
意思是只有 拥有 emp:remove 权限的用户才能登录 show 页面
java
//2.配置权限:
http.authorizeRequests()
.antMatchers("/empLogin").permitAll() //设置路径放行
.antMatchers("/show").hasAuthority("emp:remove") //为指定路径,设置权限
.anyRequest().authenticated(); //设置路径拦截
5.6 运行
在浏览器中输入http://localhost:8866/show 会要求进行登录认证,认证后如果用户具有emp:remove 权限会正常访问控制器,如果没有权限会报 403
· 用户 zs 登录,zs 是 rid 为 1 的管理员 ,有 pid 为 4 的 emp:remove 权限,登录成功后访问 show 页面会成功访问到
-->
· 用户 ls 登录,ls 是 rid 为 2 的普通用户 ,没有 pid 为 4 的 emp:remove 权限,登录成功后访问 show 页面会报403 错误(权限不匹配)
-->
5.7 添加错误页面
方式一:设置具体的状态码页面
在 templates下,创建 error 文件夹,创建 错误状态码 .html 页面。 500----》500.html 404----》404.html
方式二:使用X进行模糊匹配
在 templates下,创建 error 文件夹,创建 数字+通配符 .html 页面。 535------5XX.html 404------4XX.htm
注意:40X 或者50X的格式是错误的!
方式三:统一错误显示页面
在 templates下,创建 error.html 页面。页面显示优先级:方式一(具体状态码页面) > 方式二(X模糊匹配) > 方式三(统一的错误显示页面)

在 pom.xml 文件的最底下的 <includes> 里配置资源文件的包含规则,使它可以识别后缀为 .png 和 .jpg 的文件:(添加后刷新maven)
XML
<include>**/*.png</include>
<include>**/*.jpg</include>
添加位置:

运行:登录 ls ,访问 show 页面

六、 整合 Thymeleaf
Spring Security 可以在一些视图技术中进行控制显示效果。例如:JSP 或 Thymeleaf。在非前后端分离且使用 Spring Boot 的项目中多使用 Thymeleaf 作为视图展示技术。
Thymeleaf 对 Spring Security 的支持都放在 thymeleaf-extras-springsecurityX 中,目前最新版本为 5。所以需要在项目中添加此 jar 包的依赖和 thymeleaf 的依赖。
6.1 在 pom.xml 添加启动器
然后刷新Maven
XML
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
6.2 修改配置类 MyConfig
java
//2.配置权限:
http.authorizeRequests()
.antMatchers("/empLogin").permitAll() //设置路径放行
.antMatchers("/show").hasAuthority("emp:findAll") //认证后,为指定路径,设置权限
.antMatchers("/save").hasAuthority("emp:save")
.antMatchers("/remove").hasAuthority("emp:remove")
.antMatchers("/edit").hasAuthority("emp:edit")
.anyRequest().authenticated(); //设置路径拦截,认证后才可以访问!
添加对应的页面:save.html,remove.html,edit.html
6.3 修改 success 页面
最顶上的 xmlns:th 和 xmlns:sec :在html页面中引入 thymeleaf 命名空间和 security 命名空间
html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/save" sec:authorize="hasAuthority('emp:save')">添加</a>
<a href="/edit" sec:authorize="hasAuthority('emp:edit')">修改</a>
<a href="/show" sec:authorize="hasAuthority('emp:findAll')">查询</a>
<a href="/remove" sec:authorize="hasAuthority('emp:remove')">删除</a>
</body>
</html>
6.4 测试结果:
不用权限的用户,登录成功后,看到的success页面的链接个数,是不一样的
如果:ls 登录后也是四个功能:
1、确认数据库中的 ls 对应的rid(1是管理员,2是普通用户)
2、在 pom.xml 中确认thymeleaf 依赖的版本:是 <version>2.1.10.RELEASE</version>

七、退出 功能
用户只需要向 Spring Security 项目中发送 /logout 退出请求即可。
7.1 修改 success 页面:添加退出链接
html
<br/>
<a href="/logout">退出登录</a>

【说明】:点击 /logout 后,Spring Security 默认自动跳转到 login 页面!当然也可以设置自定义跳转策略,设置自定义退出登录页面 ; 同时还会删除掉数据库中,Remember Me 功能的数据!
登录后,退出前的 persistent_logins 表:刷新 --> 多一条数据(过期的不会自动删除,会留着

点击退出登录回到登录页面 ↓

刷新表:zs 的数据都删没了

八、 CSRF
8.1 什么是 CSRF
CSRF(Cross-site request forgery)跨站请求伪造,是通过伪造用户请求访问受信任站点的非法请求访问。
跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。
客户端与服务进行交互时,由于 http 协议本身是无状态协议,所以引入了cookie 进行记录客户端身份。在 cookie 中会存放 session id 用来识别客户端身份的。在跨域的情况下,session id 可能被第三方恶意劫持,通过这个 session id 向服务端发起请求时,服务端会认为这个请求是合法的,可能发生很多意想不到的事情。

8.2 Spring Security 中 CSRF
从Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。CSRF 为了保证不是其他第三方网站访问,要求访问时携带参数名为 _csrf 值为 token(token 在服务端产生)的内容,如果 token 和服务端的 token 匹配成功,则正常访问。
8.3 Spring Security 中 CSRF原理
-
当服务器加载登录页面。(loginPage中的值,默认/login),先生成 csrf 对象,并放入作用域中,key 为 _csrf。之后会对 ${_csrf.token} 进行替换,替换成服务器生成的 token 字符串。
-
用户在提交登录表单时,会携带 csrf 的 token。如果客户端的 token 和服务器的 token 匹配说明是自己的客户端,否则无法继续执行。
-
用户退出的时候,必须发起 POST 请求,且和登录时一样,携带 csrf 的令牌。
8.4 实现步骤
8.4.1 所有页面都要添加 csrf 防护:
html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
html
<input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>

8.4.2 修改配置类 MyConfig
取消:关闭 csrf 防护

九、重新编写退出功能
额。。退出功能出错了,重新写了一个
9.1 在配置类 MyConfig 添加完整的注销功能配置
java
//4.配置注销功能
http.logout()
.logoutUrl("/logout") // 设置注销的URL
.logoutSuccessUrl("/logoutSuccess") // 注销成功后跳转到注销成功页面
.invalidateHttpSession(true); // 使会话失效
这样 ↓ 更新了权限配置,允许匿名访问 /logoutSuccess 路径

9.2 修改成功页面( success.html )的注销功能
java
<form th:action="@{/logout}" method="post">
<input type="submit" value="退出登录"/>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
</form>
这样 ↓

9.3 创建新的注销成功页面 logoutSuccess.html
html
<h1>注销成功!</h1>
<p>您已成功退出系统。</p>
<a href="/empLogin">返回登录页面</a>
9.4 运行

ok呀差不多了,要是还有什么错误可以评论或者私信我
之后要是发现什么问题了也会进行修改更新的