一、创建测试工程
1、引入依赖:这时还没有引入spring-security
<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、编写前端页面:
level1/1.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>罗汉拳</h1>
<p>罗汉拳站当央,打起来不要慌</p>
</body>
</html>
level1/2.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>武当长拳</h1>
<p>长一点在长一点</p>
</body>
</html>
level1/3.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>全真剑法</h1>
<p>全都是真的</p>
</body>
</html>
level2/1.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>太极拳</h1>
<p>
一个西瓜圆又圆 劈它一刀成两半 你一半来 给你你不要 给他他不收 那就不给 把两人撵走 他们不走你走 走啦,一挥手,伤自尊
不买西瓜别缠我,缓慢纠缠様 两人缠我赖皮,手慢动作左右挥动 看我厉害,转头缓步拍苍蝇状 拍死了,手抱西瓜状+奥特曼十字手+广播操准备运动的站立
</p>
</body>
</html>
level2/2.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>七伤拳</h1>
<p>练这拳的人全都死了</p>
</body>
</html>
level2/3.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>梯云纵</h1>
<p>踩自己的脚往上跳</p>
</body>
</html>
level3/1.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>葵花宝典</h1>
<p>欲练神功,挥刀自宫</p>
</body>
</html>
level3/2.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>龟派气功</h1>
<p>龟-派-气-功-波</p>
</body>
</html>
level3/3.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a th:href="@{/}">返回</a>
<h1>独孤九剑</h1>
<p>欲练此剑,必先犯贱</p>
</body>
</html>
login.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">欢迎登陆武林秘籍管理系统</h1>
<hr>
<div align="center">
<form action="" method="post">
用户名:<input name=""/><br>
密码:<input name=""><br/>
<input type="submit" value="登陆">
</form>
</div>
</body>
</html>
welcome.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">欢迎光临武林秘籍管理系统</h1>
<h2 align="center">游客您好,如果想查看武林秘籍 <a th:href="@{/login}">请登录</a></h2>
<hr>
<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>
<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>
<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>
</body>
</html>
3、编写访问页面的controller:此时访问/时会路由到welcome.html
@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;
}
}
4、启动测试:http://localhost:8080/
若SpringBoot版本太低,在访问时可能会因为引入的thymeleaf版本太低(不支持html的一些新标签)而报错,此时可在pom中更改SpringBoot默认引用的thymeleaf的版本:在properties节点中更改
<properties>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
</properties>
二、认证和授权
安全框架的两个主要职能是“认证”和“授权”(或者访问控制),这两个主要职能也是Spring Security 的两个目标。
认证(Authentication):辨明主体是谁,是建立一个安全主体的过程(主体一般是指用户、设备或一些可以在你的应用程序中执行动作的其他系统)
授权(Authorization):主体在系统中能干什么,确定一个主体是否允许在你的应用程序执行一个动作(比如查看某个页面资源)的过程。授权之前主体的身份已经有认证,即授权是在认证之后决定主体拥有哪些权限
三、使用Spring Security
1、引入spring security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、参照官网编写一个spring security的配置类:示例地址
示例:配置类继承于WebSecurityConfigurerAdapter,并须添加@EnableWebSecurity注解
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**", "/index").permitAll()
.antMatchers("/user/**").hasRole("USER")
.and()
.formLogin()
.loginPage("/login").failureUrl("/login-error");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
自定义SecurityConfig :定制授权规则
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//定制授权规则
http.authorizeRequests().antMatchers("/").permitAll()//所有用户可访问"/",即主页
.antMatchers("/level1/**").hasRole("VIP1")//访问URL中含有/level1的资源需有VIP1权限
.antMatchers("/level2/**").hasRole("VIP2")//访问URL中含有/level2的资源需有VIP2权限
.antMatchers("/level3/**").hasRole("VIP3");//访问URL中含有/level3的资源需有VIP3权限
}
}
此时再访问未授权的资源时会被拒绝
开启登录功能:调用HttpSecurity的formLogin()
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()//所有用户可访问"/",即主页
.antMatchers("/level1/**").hasRole("VIP1")//访问URL中含有/level1的资源需有VIP1权限
.antMatchers("/level2/**").hasRole("VIP2")//访问URL中含有/level2的资源需有VIP2权限
.antMatchers("/level3/**").hasRole("VIP3");//访问URL中含有/level3的资源需有VIP3权限
//开启自动配置的登录功能
http.formLogin();
}
}
在访问未经授权的资源时会发送/login请求跳转到Security提供的登录页面,如果填写的登录信息有误会发送(login?error)请求跳转到Security提供的登录错误页面,我们也可以设置登录的用户名密码等字段名的信息
登录页:
登录失败:
3、认证:以上为Security的授权,接下来是Security的认证:
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 授权
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()//所有用户可访问"/",即主页
.antMatchers("/level1/**").hasRole("VIP1")//访问URL中含有/level1的资源需有VIP1权限
.antMatchers("/level2/**").hasRole("VIP2")//访问URL中含有/level2的资源需有VIP2权限
.antMatchers("/level3/**").hasRole("VIP3");//访问URL中含有/level3的资源需有VIP3权限
//开启自动配置的登录功能
http.formLogin();
}
/**
* 开启认证功能
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()//在内存的数据中认证
.withUser("zhangsan").password("123456").roles("VIP1", "VIP2")//配置用户名、密码和角色信息
.and()
.withUser("lisi").password("654321").roles("VIP3");
}
}
在高版本的Spring Security中使用以上的认证方式可能会报错:
There is no PasswordEncoder mapped for the id "null"
这是因为高版本中需要指定密码的加密方式,代码如下:
/**
* 开启认证功能
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()//在内存的数据中认证
.passwordEncoder(new BCryptPasswordEncoder())//指定密码的编码方式
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("VIP1", "VIP2")//配置用户名、密码和角色信息
.and()
.withUser("lisi").password(new BCryptPasswordEncoder().encode("654321")).roles("VIP3");//使用Bcrypt对密码进行加密
}
4、注销:调用HttpSecurity.logout()开启注销功能,前端访问/logout请求即可
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 授权
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()//所有用户可访问"/",即主页
.antMatchers("/level1/**").hasRole("VIP1")//访问URL中含有/level1的资源需有VIP1权限
.antMatchers("/level2/**").hasRole("VIP2")//访问URL中含有/level2的资源需有VIP2权限
.antMatchers("/level3/**").hasRole("VIP3");//访问URL中含有/level3的资源需有VIP3权限
//开启自动配置的登录功能
http.formLogin();
//开启自动配置的注销功能
http.logout();
}
}
前端请求:请求地址/logout,请求方式必须是post
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
开启自动配置的注销功能后只需要访问/logout请求即可清空session(Spring Security已为我们提供了/logout请求的处理,无需我们编写处理逻辑,应该也可以自定义处理逻辑);注销成功后默认会重定向到登录页(访问/login?logout),我们可以定制重定向的路径:设置logoutSuccessUrl的值即可
http.logout().logoutSuccessUrl("/");
四、页面根据授权展示内容
1、引入Spring Security整合thymeleaf的依赖:注意版本要和Spring Security对应
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version><!-- 该版本可以不指定 -->
</dependency>
由于SpringBoot默认指定了Spring Security和thymeleaf以及他们整合时的版本
<spring-security.version>5.1.3.RELEASE</spring-security.version>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
<thymeleaf-extras-springsecurity.version>3.0.4.RELEASE</thymeleaf-extras-springsecurity.version>
但是该版本可能和thymeleaf-extras-springsecurity5不匹配,这时我们可以在pom文件中修改版本:若匹配则无需修改
<properties>
<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity.version>
</properties>
2、在页面上引入Security的名称空间
<html xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
在页面上使用引入的名称空间:
①未登录则显示登录按钮,否则不显示:sec:authorize="!isAuthenticated()"
<div sec:authorize="!isAuthenticated()">
<h2 align="center">
游客您好,如果想查看武林秘籍
<a th:href="@{/userlogin}">请登录</a>
</h2>
</div>
②登录成功显示注销按钮,并显示当前登录者和授予权限:sec:authentication="name"获取登录者,sec:authentication="principal.authorities"获取授予权限
<div sec:authorize="isAuthenticated()">
<h2>
<span sec:authentication="name"></span>
,您好,您的角色有:
<span sec:authentication="principal.authorities"></span>
</h2>
<form th:action="@{/logout}" method="post">
<input type="submit" value="注销"/>
</form>
</div>
③根据登录者拥有的角色显示对应的武功秘籍:sec:authorize=“hasRole(‘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>
五、记住我
1、开启记住我功能:调用HttpSecurity的rememberMe()
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()//所有用户可访问"/",即主页
.antMatchers("/level1/**").hasRole("VIP1")//访问URL中含有/level1的资源需有VIP1权限
.antMatchers("/level2/**").hasRole("VIP2")//访问URL中含有/level2的资源需有VIP2权限
.antMatchers("/level3/**").hasRole("VIP3");//访问URL中含有/level3的资源需有VIP3权限
//开启自动配置的登录功能
http.formLogin();
//开启自动配置的注销功能
http.logout().logoutSuccessUrl("/");
//开启记住我功能
http.rememberMe();
}
原理:开启Spring Security的记住我功能之后,一旦我们勾选了记住我,Spring Security就会给浏览器发送一个默认名为remember-me的cookie,默认的有效期限为14天,这样我们14天内(14天后该cookie会自动删除)使用该浏览器再次访问该网址的时候浏览器就会携带着这个cookie,Spring Security就会根据该cookie查找cookie的有效性以及登录人员的信息等,我们点击注销后,Spring Security会删除浏览器端的cookie
注销: