一、简介
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。
对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理。
几个类:
WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
@EnableWebSecurity:开启WebSecurity模式
应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。
“认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。
“授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。
这个概念是通用的而不只在Spring Security中。
二、测试Spring Security
1.项目向导中映入Web模块、Thymeleaf模板引擎、还有Spring Security模块
自动引入的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.创建相关页面以及跳转的controller
@Controller public class KungfuController { private final String PREFIX = "pages/"; /** * 欢迎页 * @return */ @GetMapping("/") public String index() { return "welcome"; } /** * 登陆页 * @return */ @GetMapping("/userlogin") public String loginPage() { return PREFIX+"login"; } /** * level1页面映射 * @param path * @return */ @GetMapping("/level1/{path}") public String level1(@PathVariable("path")String path) { return PREFIX+"level1/"+path; } /** * level2页面映射 * @param path * @return */ @GetMapping("/level2/{path}") public String level2(@PathVariable("path")String path) { return PREFIX+"level2/"+path; } /** * level3页面映射 * @param path * @return */ @GetMapping("/level3/{path}") public String level3(@PathVariable("path")String path) { return PREFIX+"level3/"+path; } }
3.编写SpringSecurity的配置类(继承SecurityConfigurerAdapter 标注注解@EnableWebSecurity)
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); //定制请求的授权规则 http.authorizeRequests().antMatchers("/").permitAll() //允许所有访问首页/ .antMatchers("/level1/**").hasRole("VIP1") //允许VIP1的角色访问/level1下的页面 .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); } }
4.开启服务进行测试
首页访问成功
点击罗汉拳或者其它都是403,没有权限
5.修改SpringSecurity配置类——开启自动配置的登录功能
不指定登录页默认转向security的登录页
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); //定制请求的授权规则 http.authorizeRequests().antMatchers("/").permitAll() //允许所有访问首页/ .antMatchers("/level1/**").hasRole("VIP1") //允许VIP1的角色访问/level1下的页面 .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); //开启自动配置的登录功能 http.formLogin(); //1、/login来到security默认的登录页 //2、重定向到/login?error表示登录失败 //3、更多详细规则 } }
再次访问罗汉拳或者其它没有权限时会自动跳转到登录页
6.修改SpringSecurity配置类——重写定制授权规则方法
//定制授权规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); //这些用户名和密码本该从数据库取的,这里只是做演示,所以是从内存中取 auth.inMemoryAuthentication() .withUser("zhangsan").password("123456").roles("VIP1","VIP2") .and() .withUser("lisi").password("123456").roles("VIP2","VIP3") .and() .withUser("wangwu").password("123456").roles("VIP1","VIP3"); }
重启服务,点击罗汉跳转到登录页用用户名zhangsan,密码123456进行登录测试
但却报错500
控制台显示报错信息
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
这是因为Spring security 5.0中新增了多种加密方式,也改变了密码的格式
具体原因查看https://blog.csdn.net/canon_in_d_major/article/details/79675033
所以我们修改定制授权规则的方法
//定制授权规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取 auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3"); } }
重启服务再次进行登录测试
登录成功
由于zhangsan只有VIP1和VIP2的角色,所以只能有VIP1和VIP2的访问全选
7.修改SpringSecurity配置类——开启注销功能
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); http.authorizeRequests().antMatchers("/").permitAll() //允许所有访问首页/ .antMatchers("/level1/**").hasRole("VIP1") //允许VIP1的角色访问/level1下的页面 .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); //开启自动配置的登录功能 http.formLogin(); //1、/login来到登录页 //2、重定向到/login?error表示登录失败 //3、更多详细规则 //开启自动配置的注销功能 http.logout(); //访问/logout 表示用户注销,并清空session //注销成功会默认访问访问/login?logout } //定制授权规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取 auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3"); } }
在主页面添加一个表单访问/logout 提交方式必须是post方式
<form th:action="@{/logout}" method="post"> <input th:type="submit" th:value="注销"> </form>
登录成功点击注销:
默认跳转到/login?logout
想要指定注销成功之后去哪个页面需要在后面增加logoutSuccessUrl("")方法;修改为跳转到首页:
http.logout().logoutSuccessUrl("/");
三、thymeleaf和springSecurity的整合操作
1、导入整合的依赖
我的springboot的版本是2.2.6,所以默认的thymeleaf和thymeleaf-layout-dialect偏高,需要导入的是thymeleaf-extras-springsecurity5
根据自己thymeleaf和thymeleaf-layout-dialect的版本进行是否选择为thymeleaf-extras-springsecurity4
想选择相应的thymeleaf-extras-springsecurity则可以在pom.xml文件中的properties标签中指定 thymeleaf 以及 thymeleaf-layout-dialect版本默认springboot默认的版本
<dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>
2、在页面中添加springsecurit的名称空间
虽然我导入的依赖是thymeleaf-extras-springsecurity5,但是在页面中导入命名空间是xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5时,使用sec的时候并不会提示,但页面效果是有的;但是命名空间改为4的话,则代码提示功能就有,功能也还有,具体原因不太清除,抱歉学艺不精。
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
3.修改页面中的部分代码(判断是否登录)
<!--如果没有登录认证--> <div sec:authorize="!isAuthenticated()"> <h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录</a></h2> </div> <hr> <!--如果登录认证了--> <div sec:authorize="isAuthenticated()"> <h2><span sec:authentication="name"></span>您好,您的角色有 <span sec:authentication="principal.authorities"></span></h2> <form th:action="@{/logout}" method="post"> <input th:type="submit" th:value="注销"> </form> </div>
访问页面查看效果
没有登录时:
登录了之后:
4.修改页面中的部分代码(根据角色显示页面)
<!--有VIP1的角色才显示--> <div sec:authorize="hasRole('VIP1')"> <h3>普通武功秘籍</h3> <ul> <li><a th:href="@{/level1/1}">罗汉拳</a></li> <li><a th:href="@{/level1/2}">武当长拳</a></li> <li><a th:href="@{/level1/3}">全真剑法</a></li> </ul> </div> <!--有VIP2的角色才显示--> <div sec:authorize="hasRole('VIP2')"> <h3>高级武功秘籍</h3> <ul> <li><a th:href="@{/level2/1}">太极拳</a></li> <li><a th:href="@{/level2/2}">七伤拳</a></li> <li><a th:href="@{/level2/3}">梯云纵</a></li> </ul> </div> <!--有VIP3的角色才显示--> <div sec:authorize="hasRole('VIP3')"> <h3>绝世武功秘籍</h3> <ul> <li><a th:href="@{/level3/1}">葵花宝典</a></li> <li><a th:href="@{/level3/2}">龟派气功</a></li> <li><a th:href="@{/level3/3}">独孤九剑</a></li> </ul> </div>
没有登录时都不显示
登录张三(拥有VIP1和VIP2角色)后,显示普通武功和高级武功,绝世武功不显示
5.设置记住登录用户
修改security配置类
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); http.authorizeRequests().antMatchers("/").permitAll() //允许所有访问首页/ .antMatchers("/level1/**").hasRole("VIP1") //允许VIP1的角色访问/level1下的页面 .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); //开启自动配置的登录功能 http.formLogin(); //1、/login来到登录页 //2、重定向到/login?error表示登录失败 //3、更多详细规则 //开启自动配置的注销功能 http.logout().logoutSuccessUrl("/"); //访问/logout 表示用户注销,并清空session //注销成功会默认访问访问/login?logout //后面增加.logoutSuccessUrl("")方法指定注销成功后去哪个页面 //开启记住我功能 http.rememberMe(); } //定制授权规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取 auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3"); } }
开启之后在登录页会有记住我的选项
勾选登录之后浏览器就会把用户名密码保存到cookie中(默认保存14天的),关闭浏览器再次访问就不用登录了
当然注销之后cookie也就会删除了
6.使用自己的登录页
之前一直跳转的是security的默认登录页,现在我们使用自己的登录页
我们的controller跳转到登录页的映射是/userlogin
修改security配置类,指定跳转到默认登录页
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); http.authorizeRequests().antMatchers("/").permitAll() //允许所有访问首页/ .antMatchers("/level1/**").hasRole("VIP1") //允许VIP1的角色访问/level1下的页面 .antMatchers("/level2/**").hasRole("VIP2") .antMatchers("/level3/**").hasRole("VIP3"); //开启自动配置的登录功能 http.formLogin().loginPage("/userlogin"); //1、/login来到登录页,不指定登录页默认来到security的登录页 //2、重定向到/login?error表示登录失败 //3、更多详细规则 //开启自动配置的注销功能 http.logout().logoutSuccessUrl("/"); //访问/logout 表示用户注销,并清空session //注销成功会默认访问访问/login?logout //后面增加.logoutSuccessUrl("")方法指定注销成功后去哪个页面 //开启记住我功能 http.rememberMe(); } //定制授权规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); //这些用户名和密码本该从用户名取的,这里只是做演示,所以是从内存中取 auth.inMemoryAuthentication() .passwordEncoder(new BCryptPasswordEncoder()).withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("lisi").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3") .and() .passwordEncoder(new BCryptPasswordEncoder()).withUser("wangwu").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP3"); } }
当我们访问需要权限的链接时
此时就跳转到我们自己的登录页
但是想要security进行处理我们的表单登录需要一定的规则:*******
1、如同上诉的如果指定了登录页面的话,好比登录页指定为userlogin
则表单的action为/userlogin(即登录页名) 且提交方法为post时,springsecurity进行登录验证处理
如果一个访问的是/userlogin,且提交方式是get时,则springsecurity会跳转到我们的登录页
没有指定登录页的话,访问的链接是/login;提交方式也是想对应
当然我们也可以指定访问的链接
http.formLogin().loginPage("/userlogin").loginProcessingUrl("/login");
2、input的name值分别是username和password时;security才能进行进行验证
也可以指定input的name属性
http.formLogin().usernameParameter("user").passwordParameter("pwd") .loginPage("/userlogin").loginProcessingUrl("/login");
以上修改一些属性后,想让security帮我们验证登录登录页面代码如下
<h1 align="center">欢迎登陆武林秘籍管理系统</h1> <hr> <div align="center"> <form th:action="@{/login}" method="post"> 用户名:<input name="user"/><br> 密码:<input name="pwd"><br/> <input type="submit" value="登陆"> </form> </div>
此时我们进行测试,
访问需要权限的页面
跳转到了我们的登录页
输入账号和密码,登录成功
7、在自己的登录页上添加记住用户
之前我们配置了security记住用户的功能
修改登录页代码,添加记住用户选项
<body> <h1 align="center">欢迎登陆武林秘籍管理系统</h1> <hr> <div align="center"> <form th:action="@{/login}" method="post"> 用户名:<input name="user"/><br> 密码:<input name="pwd"><br/> <input type="checkbox" name="remember-me">记住我<br> <input type="submit" value="登陆"> </form> </div> </body>
当开启security记住用户的功能时,想要功能有效,则checkbox的name值默认是remember-me
也可以指定checkbox的name值
//开启记住我功能 http.rememberMe().rememberMeParameter("remember");
则checkbox的name值也要相应改变
<input type="checkbox" name="remember">记住我<br>
此时登录页就有记住用户的选项
勾选进行登录之后用户名和密码就会记录在cookie中了,下次访问页面就会自动登录了,(cookie的默认保存时间是14天)