目录:
- 一、SpringBoot 中 自定义 "用户授权管理" ( 总体内容介绍 )
- 二、实现 "CSRF" 防护功能 ( 通过 "HttpSecurity类" 的 csrf( )方法来实现 "CSRF" 功能 ) :
- 三、Security "管理前端页面" ( Security 与 Thymeleaf "整合实现前端页面" 的 "管理" )
作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!
该文章参考学习教材为:
《Spring Boot企业级开发教程》 黑马程序员 / 编著
文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!(侵权可联系我,进行删除,如果雷同,纯属巧合)
一、SpringBoot 中 自定义 “用户授权管理” ( 总体内容介绍 )
当 一个系统建立之后,通常需要适当地做一些权限控制,使得 不同用户具有不同的权限 操作系统。
例如,一般的项目中都会做一些简单的登录控制,只有特定用户才能登录访问。接下来将 针对 Web 应用中常见 的 自定义用户授权管理 进行 介绍。SpringBoot 中 自定义 “用户授权管理” 的 实现方式 :
① 创建类 继承(extens) WebSecurityConfigurerAdapter类② 重写 WebSecurityConfigurerAdapter类 中的 configure( HttpSecurity http )方法
③ 通过 HttpSecurity类中的 Xxx方法 来 实现自定义 “用户授权管理”。
( 通过 configure( HttpSecurity http ) 方法 中的 HttpSecurity 类 实现/进行 “用户授权管理” )
HttpSecurity类 的 主要方法 及 说明 ( 通过 该类 中的方法 来 实现 "用户授权管理"):
方法 描述 authorizeRequests( ) :
授权请求开启基于 “HttpServletRequest” 请求访问 的 限制。
ps :
用于实现 “自定义用户访问控制”。
( 通过configure( HttpSecurity http)方法 中的 HttpSecurity类 的 authorizeRequests( )方法来 实现 “自定义用户访问控制” ,其他方法则是以此类推。)formLogin( ) 开启基于表单的用户登录。
ps :
① 用于实现 “自定义用户登录页面”。
② 使用该方法就是 使用 security提供 的" 默认登录"页面 进行" 登录验证" ( 如果没有指定 "自定义的登录页面" 的话 )httpBasic( ) 开启基于 HTTP 请求的 Basic 认证登录。 logout( ) 开启退出登录 的 支持。 sessionManagement( ) 开启 Session 管理配置。 rememberMe( ) 开启 记住我 功能。 csrf( ) 配置 “CSRF” 跨站请求伪造防护功能。 补充 :
configure ( HttpSecurity http )方法的 参数类型是 HttpSecurity 类 ,HttpSecurity 类提供了 Http请求的限制 、权限、Session 管理配置、CSRF跨站请求问题等方法。
二、实现 “CSRF” 防护功能 ( 通过 “HttpSecurity类” 的 csrf( )方法来实现 “CSRF” 功能 ) :
CSRF ( Cross-site request forgery,跨站请求伪造 ),也被称为“One Click Attack”( 一键攻击 )或者 “Session Riding”( 会话控制 ),通常 缩写 为 CSRF 或者 XSRF,是一种 对网站的恶意利用。
与传统的 XSS 攻击( Cross-site Scripting,跨站脚本攻击) 相比,CSRF 攻击更加难以防范,被认为比 XSS 更具危险性。CSRF 攻击可以在 受害者毫不知情 的情况下以受害者的名义 伪造请求发送给 攻击页面,从而 在用户未授权的情况 下 执行在权限保护之下 的 操作。
例如,一个用户 Tom 登录银行站点服务器准备进行转账操作,在 此用户信息有效期内,Tom被 诱导查看了一个 黑客恶意网站,该网站就会 获取到 Tom 登录后的浏览器与银行网站之间尚未过期的 Session 信息,而 Tom 浏览器的 Cookie 中含有 Tom 银行账户的 认证信息,此时黑客就会伪装成 Tom 认证后的合法用户对银行账户进行非法操作。
在讨论 如何抵御 CSRF 攻击 之前,先要 明确 CSRF 攻击的对象,也就是 要保护的对象。从上面的例子可知,CSRF 攻击 是 黑客 借助 受害者 的 Cookie 骗取服务器的信任,但是黑客并不能获取 Cookie,也看不到 Cookie 的具体内容。另外,对于服务器返回的结果,由于浏览器同源策略的限制,黑客无法进行解析。
黑客所能做的就是伪造正常用户给服务器发送请求,以执行请求中所描述的命令,在服务器端直接改变数据的值,而 非窃取服务器中的数据。因此,针对 CSRF 攻击要保护的对象是那些可以直接产生数据变化的服务,而对于读取数据的服务,可以不进行CSRF 保护。例如 ,银行转账操作会 改变账号金额,需要进行 CSRF 保护。获取银行卡等级信息是 读取操作,不会改变数据,可以不需要保护。
在业界目前防御 CSRF 攻击 ( 防御 “跨站请求伪造” )主要有 以下3种策略。
(1) 验证 HTTP Referer 字段。
(2) 在 请求地址 中 添加 Token 并验证。
(3) 在 HTTP 头 中 自定义属性并验证。Spring Security 安全框架提供了 CSRF 防御相关方法,具体如下表所示 :
CSRF防御相关的 **主要方法**及 说明 disable( )方法 “关闭” Security 默认开启 的 CSRF 防御功能。 csrfTokenRepository( CsrfTokenRepositor csrfTokenRepository )方法 指定 要使用 的 CsrfTokenRepository ( Token 令牌持久化仓库 )。默认 是由 LazyCsrfTokenRepository 包装 的 HttoSessionCsrfTokenRepository 。 requireCsrfProtectionMatcher( RequestMatcher requireCsrfProtectionMatcher )方法 指定 针对什么类型的 请求应用 CSRF 防护功能。默认设置是 忽略 GET、HEAD、TRACE 和 OPTIONS 请求,而 处理并防御 其他所有请求。 接下来我们 结合上表中的方法 对 Spring Boot 中的 CSRF 防护功能进行说明 :
1. “关闭” CSRF 防护功能 :
- Spring Boot 整合 Spring Security 默认开启了 CSRF 防御功能,并要求 "数据修改"的 请求方法 ( 例如 PATCH、POST、PUT和 DELETE ) 都 需要经过 Security 配置 的 安全认证后 方可正常访问,否则无法正常发送请求 。为了演示 Security 的 CSRF 实际默认防护效果,编写一个页面 进行 演示说明。
① 创建好 “基本的项目”
② 创建数据修改页面
打开项目 resources/templates 目录,在该目录下创建一个名为 csrf 的文件夹,在该文件夹中编写一个模拟修改用户账号信息的Thymeleaf 页面 : csrfTest.html 用来 进行 CSRF 测试,代码文件如下所示 :
csrfTest.html :
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/html"> <head> <meta charset="UTF-8"> <title>用户修改</title> </head> <body> <!-- 该修改页面将会被Spring Security框架拦截(因为安全框架默认启用了CSRF 安全防护功能)--> <!-- 该修改页面提交的请求也会被拦截,因为其没有携带Token令牌,Spring Security默认的不安全的请求,将其进行拦截,且后台也不会做出响应--> <div align="center"> <form method="post" action="/updateUser"> 用户名: <input type="text" name="username"/> </br> 密 码<input type="password" name="password"/> </br> <button type="submit">修改</button> </form> </div> </body> </html>
③ 编写后台控制层方法
在项目的 controller包 下,创建一个用于 CSRF 页面请求测试的控制类 : CSRFController,代码文件如下 :
CSRFController.java :
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; @Controller public class CSRFController { //关于 CSRF页面请求测试的控制层类 /* 向用户修改页面跳转 */ @GetMapping("/toUpdate") public String toUpdate() { //返回值为String类型,可用于返回一个视图页面 return "csrf/csrfTest"; } /* 用户修改处理 */ @ResponseBody //将返回值转换为"指定类型"并写入/存入响应体中(响应给前端) , 所以此处返回的不再是是"视图页面"了 @PostMapping(value = "/updateUser") //使用@RequestParam()注解来绑定"前后端参数" -- 该注解能解决"前后端参数名不一致"的问题 public String updateUser(@RequestParam String username, @RequestParam String password, HttpServletRequest request) { System.out.println(username); System.out.println(password); String csrf_token = request.getParameter("_csrf"); System.out.println(csrf_token); return "ok"; } }
④ CSRF 默认防护效果测试
重启项目,通过浏览器访问 “http://localhost8080/toUpdate” 用户修改页面,由于前面配置了 请求拦截 ,会先 被拦截跳转到 用户登录页面。
在用户登录页面输入正确的用户信息后,就会自动跳转到用户修改页面,该页面如下图所示 :
在上图所示的用户修改页面中,随意输入修改后的 用户名和密码 ,单击【修改】按钮进行 数据提交,效果如下图所示 :
从上图可以看出,在 代码业务逻辑没有错误 的 情况下,表单中 正确提交 POST 的 请求数据被拦截,出现了 403 和 Forbidden(禁止) 的 错误提示信息,而 后台也没有任何响应。这说明整合使用的 Spring Security 安全框架 默认启用 了 CSRF 安全防护功能, 而上述示例 被拦截的 本质原因就是数据修改请求中没有携带 CSRF Token(CSRF 令牌) 相关的参数信息,所以被认为是 不安全的请求。
通过上述示例可以看出,在整合 Spring Security 安全框架后,项目 默认启用了 CSRF 安全防护功能,项目中所有涉及数据修改方式的 请求都会被拦截 ,如果没有携带CSRF Token信息 将不会放行 。 针对这种情况,可以 有两种处理方式 :
一种方式是直接关闭 Security 默认开启的 CSRF 防御功能 ;另一种 方式就是 配置 Security需要 的 CSRF Token。
⑤ 关闭 Security 的 " CSRF 防御功能"
打开配置类 : SecurityConfig , 在重写的 configure( HttpSecurity http )方法 中进行 “关闭配置” :
@EnableWebSecurity // 开启MVC security安全支持 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { /** * 关闭 Spring Security的 CSRF防御功能 */ http.csrf() .disable(); } }
上述示例中展示了 关闭 CSRF 防御功能的配置方式,这种 直接关闭 CSRF防御的方式简单粗暴,不太推荐使用,如果 强行关闭后 网站 可能会面临 CSRF攻击 的危险。
2. 为 “数据修改” 操作提供 “CSRF Token 令牌” ( 有CSRF才能执行该"数据操作" )
- Spring Security 针对 不同类型 的**数据修改请求提供了不同方式**的 CSRF Token 配置 ( 为 “数据修改” 操作 提供 "CSRF Token" ) ,主要包括有:
① 针对 Form 表单数据 "修改"的 CSRF Token 配置
② 针对 Ajax 数据 "修改"请求的 CSRF Token配置。下面将分别 对这两种配置方式进行 讲解。
(1) 针对 “Form表单” “数据修改” 的 “CSRF Token令牌” 配置
方式一 : Form表单中 “显式配置” “CSRF Token令牌信息” ( 需"手动配置")
针对 Form 表单类型的 数据修改请求,Security 支持在 Form 表单 中提供一个携带 CSRFToken 信息的 隐藏域,与其他修改数据一起提交,这样后台就可以 获取并验证 该请求 是否为安全 的,示例代码如下 :
csrfTest.html :
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/html"> <head> <meta charset="UTF-8"> <title>用户修改</title> </head> <body> <!-- 该修改页面将会被Spring Security框架拦截(因为安全框架默认启用了CSRF 安全防护功能)--> <!-- 该修改页面提交的请求也会被拦截,因为其没有携带Token令牌,Spring Security默认的不安全的请求,将其进行拦截,且后台也不会做出响应--> <div align="center"> <form method="post" action="/updateUser"> <!-- 该Form表单中提供了一个携带 CSRF Token信息的 "隐藏域",与其他数据一起提交,以让后端认定该请求是安全的 --> <!-- ${_csrf.getParameterName()} : 获取 "Security默认提供" 的 "CSRF Token令牌"对应的 "key值" th:name="${_csrf.getParameterName()}" : 将该 CSRF Token令牌对应的key值,作为name属性的"属性值" ${_csrf.token} : 获取 "Security随机生成" 的 "CSRF Token令牌" 对应的 "value值" th:value="${_csrf.token} : 将给 "CSRF Token令牌值"作为value属性的"属性值" 该Form表单中添加了CSRF Token令牌,后台配置的Security会自动获取并识别请求中的CSRF Token令牌信息并进行用户信息验证, 从而判断是否是安全的 --> <input type="hidden" th:name="${_csrf.getParameterName()}" th:value="${_csrf.token}"> 用户名: <input type="text" name="username"/> </br> 密 码<input type="password" name="password"/> </br> <button type="submit">修改</button> </form> </div> </body> </html>
在 Form 表单中 添加上述 CSRF 配置后,无须其他配置就可以正常实现数据修改请求 ( 因为 给后端提供 了 CSRF Token令牌 ),后台配置的 Security 会 自动获取并识别请求中的 CSRF Token 信息并进行用户信息验证,从而判断是否安全。
方式二 : 使用 “Thymeleaf 模板” 的 “th:action 属性” 配置 “CSRF Token令牌信息” ( 无需"手动配置")
使用 Thymeleaf 模板 的 th:action 属性 配置 CSRF Token 信息,示例代码如下 :
csrfTest.html :
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/html"> <head> <meta charset="UTF-8"> <title>用户修改</title> </head> <body> <div align="center"> <!-- th:action="@{/updateUser} : 此处使用Thymeleaf的 th:action属性来 "配置请求", 其会默认会携带CSRF Token信息(给后端进行"安全验证"),这样就 无需开发者手动添加 --> <form method="post" th:action="@{/updateUser}"> 用户名: <input type="text" name="username"/> </br> 密 码<input type="password" name="password"/> </br> <button type="submit">修改</button> </form> </div> </body> </html>
上述代码中,使用了 Thymeleaf 模板的 th:action 属性配置了 Form 表单数据修改后 的 请求路径 ( 使用Thymeleaf 的 th:action属性来配置Url请求时, 其"自动默认携带" CSRF Token令牌信息 ,这个 令牌信息被 url请求携带一起到后端 ,不再需要 “隐藏域” 的方式来 传递 “CSRF Token令牌” 给后端。 ),而在 表单中并没有提供携带 CSRFToken 信息的隐藏域 ,但仍然可以正常地执行数据修改请求。这是因为使用 Thymeleaf 模板的 th:action 属性配置请求时,会默认携带 CSRF Token令牌 信息,无须开发者手动添加,这也解释了在前面编写的login.html 页面进行用户登录时为何可以正常执行的原因。
(2) 针对 “Ajax” “数据修改” 的 “CSRF Token令牌” 配置
对于 Ajax 类型的 数据修改请求 来说,Security 提供了通过添加 HTTP “header头” 信息的方式携带 CSRF Token令牌信息进行请求验证。( 也是 要携带 CSRF Token信息 给后端才能进行 “数据修改” 操作 ) 。
首先,在 页面 的 <head>标签中添加<meta>子标签 , 并配置 CSRF Token令牌 信息 ,示例代码如下 :
<html> <head> <!-- Ajax的数据修改配置CSRF Token令牌信息 --> <!-- 获取 CSRF Token令牌 --> <meta name="_csrf" th:content="${_csrf.token}"/> <!-- 获取 CSRF头,默认为 X-CSRF-TOKEN --> <meta name="_csrf_header" th:content="${_csrf.headerName}"/> </head> ......
然后,在 具体的 Ajax 请求中 获取 <meta>子标签 中 设置 的 CSRF Token 信息 并 绑定 在 HTTP 请求头 中进行 请求验证,示例代码如下 :
$(function() { //获取<meta>标签中封装的 CSRF Token令牌信息 var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); //将头中的 "CSRF Token令牌信息" 进行发送 $(document).ajaxSend(function (e,xhr,options) { xhr.setRequestHeader(header, token); }) });
上述代码中,首先 获取<meta>标签中 设置的 CSRF Token 信息,然后通过 HTTP 请求 将CSRF Token 信息给后台进行验证。
三、Security “管理前端页面” ( Security 与 Thymeleaf “整合实现前端页面” 的 “管理” )
- 在前面的内容中,我们只是通过 Spring Security 对 后台增加了 权限控制,前端页面并 没有做任何处理,前端页面显示的还是对应的链接等内容,用户体验较差。接下来我们在前面案例的基础上,讲解如何使用 Security 与 Thymeleaf 整合 实现前端页面的 管理。
① 添加 thymeleaf-extras-springsecurity5 “依赖启动器”
在项目 pom.xml 中 添加 thymeleaf-extras-springsecurity5 依赖启动器 :
<!-- Security与Thymeleaf整合实现前端页面安全访问控制 --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>
需要注意的是,上述添加的 thymeleaf-extras-springsecurity5 依赖启动器中,其版本号同样是由 Spring Boot 统一整合并管理的。如果引用thymeleaf-extras-springsecurity4 依赖启动器,那么还 需要添加<version>标签手动进行版本管理。
② 修改前端页面,使用 “Security相关标签” 进行 “页面控制”
打开 项目中的 项目首页 : index.html,引入 Security 安全标签,并在页面中根据需要使用Security 标签进行 显示控制,修改后的项目首页内容 如下所示 :
index.html :
<!DOCTYPE html> <!-- 配置开启thymeleaf模板引擎页面配置 --> <!-- xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5" : 通过 xmlns:sec 引入 " Security安全标签" --> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta charset="UTF-8">m <title>影视直播厅</title> </head> <body> <h1 align="center">欢迎进入电影网站首页</h1> <!-- div1: sec:authorize="isAnonymous()" : 判断用户是否 "未登录",只有匿名用户( 未登录用户 )才会显示 "请登录" 链接提示 --> <div sec:authorize="isAnonymous()"> <h2 align="center">游客你好,如果想查看电影<a th:href="@{/userLogin}">请登录</a></h2> </div> <!-- div2: sec:authorize="isAuthenticated()" : 判断用户是否 "已登录",只有认证用户( 登录用户 )才会显示 "用户信息" 和 "链接提示" sec:authentication="name" : 显示 "登录用户名" sec:authentication="principal.authorities" : 显示权限"authority" --> <div sec:authorize="isAuthenticated()"> <h2 align="center"><span sec:authentication="name" style="color: #007bff"></span> 您好,您的用户权限为<span sec:authentication="principal.authorities" style="color: darkkhaki"></span>,你有权观看以下电影 </h2> <form th:action="@{/mylogout}" method="post"> <input th:type="submit" th:value="注销"> </form> </div> <!-- div3: sec:authorize="hasAnyRole('common','vip')" : 只有角色有common 或 vip (对应权限Authority为 ROLE_common 和 ROLE_vip)且登录的用户 才会显示普通电影列表信息 --> <div sec:authorize="hasAnyRole('common','vip')"> <h3>普通电影</h3> <ul> <li><a th:href="@{/detail/common/1}">飞驰人生</a></li> <li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li> </ul> </div> <!-- div4: sec:authorize="hasRole('common')" : 只有角色有 vip (对应权限Authority为 ROLE_vip)且登录的用户 才会显示vip电影列表信息 --> <div sec:authorize="hasRole('vip')"> <h3>VIP专享</h3> <ul> <li><a th:href="@{/detail/vip/1}">速度与激情</a></li> <li><a th:href="@{/detail/vip/2}">猩球崛起</a></li> </ul> </div> </body> </html>