简介
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求。
SpringBoot底层默认的安全框架技术选型就是Spring Security,我们只需要简单的配置即可实现安全管理。
特征
- 全面和可扩展的身份验证和授权支持
- 防御会话固定,点击劫持,跨站点请求伪造等攻击
- Servlet API集成
- 与Spring Web MVC的可选集成
- …
几个重要的类:
- WebSecurityConfiureAdapter:自定义Security策略
- AuthenticaManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebSecurity模式
认证和授权
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、编写基础配置类
参见官方文档https://docs.spring.io/spring-security/site/docs/5.2.7.RELEASE/reference/htmlsingle/#jc-custom-dsls
@EnableWebSecurity // 开启WebSecurity模式
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
3、定制请求的授权规则
@EnableWebSecurity//开启WebSecurity模式
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人都能访问
http.authorizeRequests().antMatchers("/").permitAll()
//level1下的资源必须拥有角色vip1才能访问
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
}
}
现在测试一下!
首页可以进入,但是无法直接访问其他页面!因为我们目前没有登录的角色,因为请求需要登录的角色拥有对应的权限才可以!
4、在configure()方法中加入以下配置,开启自动配置的登录功能!
// 开启自动配置的登录功能
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
//loginPage()定制登录页面
//loginProcessingUrl("/login")以post方式请求
//定制登录成功后跳转的页面
http.formLogin().loginProcessingUrl("/login").successForwardUrl("/index");
测试一下,发现没有访问权限时会自动跳转到登录页面。
5、定义认证规则,重写configure(AuthenticationManagerBuilder auth)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中定义,也可以在jdbc中去拿
auth.inMemoryAuthentication()
.withUser("chunni1").password("123").roles("vip1")
.and()
.withUser("chunni2").password("123").roles("vip2","vip3")
.and()
.withUser("chunni3").password("123").roles("vip3");
}
此时测试登录,会出现如下错误:
![](https://img-blog.csdnimg.cn/20210226162311601.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMDgwNzQx,size_16,color_FFFFFF,t_70)
为了安全,这是要求前端传回的密码需要进行加密处理。Security5默认要求密码使用加密。
6、密码加密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中定义,也可以在jdbc中去拿
//spring security 官方推荐的是使用bcrypt加密方式。
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("chunni1").password(new BCryptPasswordEncoder().encode("123")).roles("vip1")
.and()
.withUser("chunni2").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
.and()
.withUser("chunni3").password(new BCryptPasswordEncoder().encode("123")).roles("vip3");
}
登录测试,完成每个角色对应的规则认证。
注销与权限控制
1、开启注销功能
@EnableWebSecurity//开启WebSecurity模式
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
...
//注销
//定制注销后跳转页面
http.logout().logoutSuccessUrl("/login");
}
2、在首页welcome.html增加一个注销的按钮
<a th:href="@{/logout}">注销</a>
完成注销功能!
权限控制:在不同的情况下显示不同的界面内容。例如,没有登录时,就显示登录按钮;登录时,就显示注销按钮。拥有不同角色的用户登录时看到的内容是之前定制的认证规则中规定的内容。这里我们需要整合一下Thymeleaf
3、导入所需依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
4、修改前端首页welcome.html,完成登录注销及用户的显示
导入命名空间:
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
<div class="right menu">
<!-- 没登录则显示登录-->
<span sec:authorize="!isAuthenticated()"><a th:href="@{/login}">登录</a></span>
<div sec:authorize="isAuthenticated()">
<span sec:authentication="name"></span>您好,您的角色有:
<span sec:authentication="principal.authorities"></span>
</div>
<!-- 登录了就显示注销-->
<span sec:authorize="isAuthenticated()"><a th:href="@{/logout}">注销</a></span>
</div>
测试完成再往下!
5、修改前端首页welcome.html,完成角色功能块权限控制
<div sec:authorize="hasRole('vip1')">
<h3>level1</h3>
<ul>
<li><a th:href="@{/level1/1}">1</a></li>
<li><a th:href="@{/level1/2}">2</a></li>
<li><a th:href="@{/level1/3}">3</a></li>
</ul>
</div>
<div sec:authorize="hasRole('vip2')">
<h3>level2</h3>
<ul>
<li><a th:href="@{/level2/1}">1</a></li>
<li><a th:href="@{/level2/2}">2</a></li>
<li><a th:href="@{/level2/3}">3</a></li>
</ul>
</div>
<div sec:authorize="hasRole('vip3')">
<h3>level3</h3>
<ul>
<li><a th:href="@{/level3/1}">1</a></li>
<li><a th:href="@{/level3/2}">2</a></li>
<li><a th:href="@{/level3/3}">3</a></li>
</ul>
</div>
测试成功!搞定注销和权限控制。
记住我功能
很多网站都会有“记住我”的功能,也就是登录过一次之后,关闭浏览器,再打开不需要再次登录。
1、开启记住我功能
@EnableWebSecurity//开启WebSecurity模式
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
...
//记住我
http.rememberMe();
}
2、测试
![](https://img-blog.csdnimg.cn/20210226162908423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMDgwNzQx,size_16,color_FFFFFF,t_70)
再次打开登录页,发现多了一个记住我功能。勾选它,登录之后关闭浏览器,再次打开浏览器访问,发现用户依旧存在!
原因其实很简单,Java Web中就是使用cookie实现了记住用户的功能。
查看浏览器的cookie
![](https://img-blog.csdnimg.cn/20210226163014395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzMDgwNzQx,size_16,color_FFFFFF,t_70)
当我们点击登录时,会增加一个cookie。点击注销时,springsecurity会删除这个cookie。
当然,我们在实际的开发中不可能用springsecurity默认提供的登录页,需要使用我们自己的登录页。
就是配置一个loginPage(),在前面的代码里也有提到。
不过有些地方需要注意:
默认的登录页上用户名是用"username"接收,密码是用"password"接收,记住我是用"remember-me"接收。如果定制的登录页上对应属性的name不一致,需要我们自己配置,如:
http.formLogin().loginProcessingUrl("/login").successForwardUrl("/index")
.usernameParameter("usr")
.passwordParameter("pwd");
//记住我
http.rememberMe().rememberMeParameter("remember");
附上demo结构及welcome.html完整代码:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="right menu">
<!-- 没登录则显示登录-->
<span sec:authorize="!isAuthenticated()"><a th:href="@{/login}">登录</a></span>
<div sec:authorize="isAuthenticated()">
<span sec:authentication="name"></span>您好,您的角色有:
<span sec:authentication="principal.authorities"></span>
</div>
<!-- 登录了就显示注销-->
<span sec:authorize="isAuthenticated()"><a th:href="@{/logout}">注销</a></span>
</div>
<hr>
<div sec:authorize="hasRole('vip1')">
<h3>level1</h3>
<ul>
<li><a th:href="@{/level1/1}">1</a></li>
<li><a th:href="@{/level1/2}">2</a></li>
<li><a th:href="@{/level1/3}">3</a></li>
</ul>
</div>
<div sec:authorize="hasRole('vip2')">
<h3>level2</h3>
<ul>
<li><a th:href="@{/level2/1}">1</a></li>
<li><a th:href="@{/level2/2}">2</a></li>
<li><a th:href="@{/level2/3}">3</a></li>
</ul>
</div>
<div sec:authorize="hasRole('vip3')">
<h3>level3</h3>
<ul>
<li><a th:href="@{/level3/1}">1</a></li>
<li><a th:href="@{/level3/2}">2</a></li>
<li><a th:href="@{/level3/3}">3</a></li>
</ul>
</div>
</body>
</html>