自定义用户授权
Spring Security的安全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权),上一节介绍了自定义认证,接下来介绍自定义授权。
configure(HttpSecurity http)方法用来定制基于HTTP请求的用户访问控制,重写此方法可实现用户的访问控制
自定义用户访问控制
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义用户授权管理
http.authorizeRequests()
.antMatchers("/").permitAll()//访问路径为/的直接放行
.antMatchers("/login/**").permitAll()// 需要对static文件夹下静态资源进行统一放行
.antMatchers("/detail/common/**").hasRole("common")//只有有common角色的才允许访问
.antMatchers("/detail/vip/**").hasRole("vip")
.anyRequest().authenticated();//其余请求会要求用户进行登录认证
}
自定义用户登录
默认会提供一个用户登录界面,可以自定义用户登录控制,通过formLogin()方法,可以实现如下:
// 自定义用户登录控制
http.formLogin()
.loginPage("/userLogin").permitAll()
.usernameParameter("name").passwordParameter("pwd")
.defaultSuccessUrl("/")
.failureUrl("/userLogin?error");
自定义登陆界面
在templates\login\login.html中编写:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>用户登录界面</title>
<link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/login/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/userLogin}" th:method="post">
<img class="mb-4" th:src="@{/login/img/login.jpg}" width="72px" height="72px" alt="">
<h1 class="h3 mb-3 font-weight-normal">请登录</h1>
<!-- 用户登录错误信息提示框 -->
<div th:if="${param.error}" style="color: red;height: 40px;text-align: left;font-size: 1.1em">
<img th:src="@{/login/img/loginError.jpg}" width="20px" alt="">用户名或密码错误,请重新登录!
</div>
<label>
<input type="text" name="name" class="form-control" placeholder="用户名" required="" autofocus="">
</label>
<label>
<input type="password" name="pwd" class="form-control" placeholder="密码" required="">
</label>
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="rememberme"> 记住我
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">登录</button>
<p class="mt-5 mb-3 text-muted">Copyright© 2019-2020</p>
</form>
</body>
</html>
自定义用户登录跳转
controller层中:
@GetMapping("/userLogin")
public String toLoginPage() {
return "login/login";
}
再次运行测试 ,可以发现自定义的登录界面已经生效
自定义用户退出
自定义用户退出
logout()方法:
// 自定义用户退出控制
http.logout()
.logoutUrl("/mylogout")
.logoutSuccessUrl("/");
在templates\index.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 http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>影视直播厅</title>
</head>
<body>
<h1 align="center">欢迎进入电影网站首页</h1>
<div sec:authorize="isAnonymous()">
<h2 align="center">游客您好,如果想查看电影<a th:href="@{/userLogin}">请登录</a></h2>
</div>
<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>
<hr>
<div sec:authorize="hasRole('common')">
<h3>普通电影</h3>
<ul>
<li><a th:href="@{/detail/common/1}">飞驰人生</a></li>
<li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
</ul>
</div>
<div sec:authorize="hasAuthority('ROLE_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>
运行测试
启动项目,用户登录和注销功能已经实现,其中,注销后会根据自定义的设置重定向到项目首页。
用户登录信息获取
使用HttpSession获取
@GetMapping("/getuserBySession")
@ResponseBody
public void getUser(HttpSession session) {
// 从当前HttpSession获取绑定到此会话的所有对象的名称
Enumeration<String> names = session.getAttributeNames();
while (names.hasMoreElements()) {//遍历
// 获取HttpSession中会话名称
String element = names.nextElement();
// 获取HttpSession中的应用上下文
SecurityContextImpl attribute = (SecurityContextImpl) session.getAttribute(element);
System.out.println("element: " + element);
System.out.println("attribute: " + attribute);
// 获取用户相关信息
Authentication authentication = attribute.getAuthentication();
UserDetails principal = (UserDetails) authentication.getPrincipal();
System.out.println(principal);
System.out.println("username: " + principal.getUsername());
}
}
使用SecurityContextHolder获取登录用户信息
@GetMapping("/getuserByContext")
@ResponseBody
public void getUser2() {
// 获取应用上下文
SecurityContext context = SecurityContextHolder.getContext();
System.out.println("userDetails: " + context);
// 获取用户相关信息
Authentication authentication = context.getAuthentication();
UserDetails principal = (UserDetails) authentication.getPrincipal();
System.out.println(principal);
System.out.println("username: " + principal.getUsername());
}
}
运行测试
启动项目,分别访问http://localhost:8080/getuserBySession以及http://localhost:8080/getuserByContext,控制台效果如下
记住我功能
基于简单加密Token方式
当选择记住我功能并成功登录后,会生成一个Cookie并发送到客户端浏览器
1)在前端页面加入记住我功能选框(上面已添加)
2)重写configure(HttpSecurity http)方法,进行记住我功能设置,添加如下代码:
http.rememberMe()
.rememberMeParameter("rememberme")
.tokenValiditySeconds(200)
这种方式,在设置的Token有效期内不需要重新进行登录认证,但如果其他人获取到此Token,都可以在Token有效期内进行自动登录,存在安全隐患
基于持久化Token方式
1)用户登录成功后,会将username、随机产生的序列号、生成的Token信息进行持久化存储(比如放入表中),同时将其以Cookie的形式发送给客户端
2)用户再次访问,检查客户端的Cookie,若完全一致,则自动登录,同时会生成新的Token替换原来的,并将新的发送给客户端浏览器
3)若不匹配,则很可能是Cookie被盗用。盗用者初次生成的Token进行登陆时,会生成一个新Token,所以当用户在不知情时再次登录就会出现Token不匹配的情况,此时就需要重新登录,生成新Token与Cookie,同时SpringCloud就会发现Cookie可能被盗用的情况,删除数据库中相关Token记录,使元Cookie不能再次登录
4)若无Cookie,或不符合,就会进入登录界面
创建存储Cookie信息的表
create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
+ "token varchar(64) not null, last_used timestamp not null)
配置
重写configure(HttpSecurity http)方法,进行记住我功能设置
http.rememberMe()
.rememberMeParameter("rememberme")
.tokenValiditySeconds(200)
// 对cookie信息进行持久化管理
.tokenRepository(tokenRepository());
// 可以关闭Spring Security默认开启的CSRF防护功能
// http.csrf().disable();
}
//持久化Token存储
@Bean
public JdbcTokenRepositoryImpl tokenRepository() {
JdbcTokenRepositoryImpl jr = new JdbcTokenRepositoryImpl();
jr.setDataSource(dataSource);
return jr;
}
其中,在Token有效期内再次登录,数据库中Token会更新但其它数据不变。若用户手动注销,数据库中的持久化用户信息会随之删除,但如果用户是在Token有效期后自动退出的,那么数据库中的信息不回删除。当用户再次登录时,会新增一条持久化用户信息。
CSRF防护功能
CSRF攻击
攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。典型的CSRF攻击如图
防御CSRF攻击
攻击者并不能通过CSRF攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义运行操作。因此,针对CSRF攻击要保护的对象,是那些可以直接产生数据变化的服务,而对于读取数据的服务,可以不进行CSRF保护。
- 检查Referer字段HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于www.examplebank.com之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于www.examplebank.com之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其Referer字段的可能。 - 添加校验token由于CSRF的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在cookie中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行CSRF攻击。这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验token的值为空或者错误,拒绝这个可疑请求。
1)CSRF防护功能关闭
创建csrfTest.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户修改</title>
</head>
<body>
<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层
@Controller
public class CSRFController {
// 向用户修改页跳转
@GetMapping("/toUpdate")
public String toUpdate() {
return "csrf/csrfTest";
}
// 用户修改提交处理
@ResponseBody
@PostMapping(value = "/updateUser")
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";
}
}
运行测试
访问http://localhost:8080/toUpdate,输入正确的用户信息后,会跳转到测试界面
随意输入数据后修改,会出现403。这是因为数据修改的请求中没有携带CSRF Token相关参数,被认为是不安全的请求。有两种处理方式
1.直接关闭CSRF防护功能
//可以关闭Spring Security默认开启的CSRF防护功能
http.csrf().disable();
但可能会面临遭遇CSRF攻击的风险
2.配置所需要的CSRF Token
2)针对Form表单数据修改的CSRF Token
在form表单中添加一个带有CSRF Token的隐藏域
<form method="post" action="/updateUser">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
用户名: <input type="text" name="username" /><br />
密 码: <input type="password" name="password" /><br />
<button type="submit">修改</button>
</form>
th:name=”${_csrf.parameterName}”会获取CSRF Token对应的key值_csrf
th:value=”${_csrf.token}”会获取相应的value值
此外,还可以使用Thymeleaf模板的th:action属性配置,使用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>
3)针对Ajax数据修改请求的CSRF Token
通过添加HTTP header头信息的方式
<head>
<meta name="_csrf" th:content="${_csrf.token}">
<meta name="_csrf_header" th:content="${_csrf.headerName}">
<meta charset="UTF-8">
<title>用户修改</title>
</head>
$(function () {
//获取信息
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
//将头中的CSRF信息进行发送
$(document).ajaxSend(function (e,xhr,options) {
xhr.setRequestHeader(header,token);
});
});
源码下载
文件下载 | 文件名称:本节代码.zip | 文件大小:2.92MB |
下载声明:本站文件大多来自于网络,仅供学习和研究使用,不得用于商业用途,如有版权问题,请联系博主! | ||
下载地址:点击下载 |