作者:追梦1819

原文:https://www.cnblogs.com/yanfei1819/p/11350255.html

版权声明:本文为博主原创文章,转载请附上博文链接!

引言

  系统的安全的重要性人人皆知,其也成为评判系统的重要标准。

  Spring Security 是基于 Spring 的安全框架。传统的 Spring Security 框架需要配置大量的 xml 文件。而 SpringBoot 的出现,使其简单、方便、上手快。

版本信息

  • JDK:1.8
  • SpringBoot :2.1.6.RELEASE
  • maven:3.3.9
  • Thymelaf:2.1.4.RELEASE
  • IDEA:2019.1.1

数据库设计

  系统的底层数据库,设计的表格是五张:用户表、角色表、用户角色对应表、权限表、角色权限对应表。用户与角色对应,角色与权限对应,从而使用户与权限间接对应。同时考虑到了扩展性和健壮性。这就是底层设计的核心思想。

  上述的底层设计基本上是千篇一律的,没啥可以讲的。不是本文的重点。本文的重点是通过项目的需求来演示完整的功能实现。

搭建环境

  为了便于项目的演示,本章的实例用 SpringBoot + thymelaf 构建一个简单的页面。同时,由于功能点比较多,并保证能够同时讲解晚上功能,以下将分阶段详解各个功能点。

第一阶段:

第一步,创建项目:

对以上的项目目录说明:

com.yanfei1819.security.config.SecurityConfig:security配置

com.yanfei1819.security.web.controller.IndexController:测试接口

com.yanfei1819.security.SecurityApplication:启动类

src\main\resources\templates\index.html:首页

src\main\resources\templates\springboot-1.html:同以下三个页面都是菜单的详细页,用来模拟菜单

src\main\resources\templates\springboot-2.html:

src\main\resources\templates\work-1.html:

src\main\resources\templates\work-2.html:

src\main\resources\application.properties:主配置文件

第二步,引入 maven 依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>

注意,在引入 security 依赖后,如果没有做配置,它会将所有的请求拦截,并跳转到自定义的登录界面(端口号被定义为8085)。如下图:

第三步,创建配置类 SecurityConfig ,并继承 WebSecurityConfigurerAdapter:

package com.yanfei1819.security.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /**
* Created by 追梦1819 on 2019-06-27.
*/
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception { // 定制授权规则
http.authorizeRequests().antMatchers("/").permitAll(). // 所有角色可访问
antMatchers("/springboot/**").hasAnyRole("admin","test"). // 只有xx角色才能访问
antMatchers("/work/**").hasRole("admin"); // 只有xx角色才能访问
}
}

定义授权规则,需要重写 configure(HttpSecurity http) 方法。该配置类的写法,可以参照 Spring Security官网。该方法中是定制授权规则。

hasAuthority([auth]):等同于hasRole

hasAnyAuthority([auth1,auth2]):等同于hasAnyRole

hasRole([role]):当前用户是否拥有指定角色。

hasAnyRole([role1,role2]):多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true

Principle:代表当前用户的principle对象

authentication:直接从SecurityContext获取的当前Authentication对象

permitAll():总是返回true,表示允许所有的

denyAll():总是返回false,表示拒绝所有的

isAnonymous():当前用户是否是一个匿名用户

isAuthenticated():表示当前用户是否已经登录认证成功了

isRememberMe():表示当前用户是否是通过Remember-Me自动登录的

isFullyAuthenticated():如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true

hasPermission():当前用户是否拥有指定权限

第四步,定义接口:

package com.yanfei1819.security.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; /**
* Created by 追梦1819 on 2019-06-27.
*/
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
return "index";
}
@GetMapping("/springboot/{id}")
public String springbootById(@PathVariable int id){
return "springboot-"+id;
}
@GetMapping("/work/{id}")
public String work(@PathVariable int id){
return "work-"+id;
}
}

第五步,编写页面 index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是首页</h1>
<di>
<h3>追梦1819的博客系列</h3>
<ul>
<li><a th:href="@{/springboot/1}">SpringBoot 第一章</a></li>
<li><a th:href="@{/springboot/2}">SpringBoot 第二章</a></li>
<li><a th:href="@{/work/1}">work 第一章</a></li>
<li><a th:href="@{/work/2}">work 第二章</a></li>
</ul>
</di>
</body>
</html>

SpringBoot-1.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>SpringBoot-1</h1> </body>
</html>

另外的 springboot-2.html、work-1.html、work-2.html 与以上类似,此不再赘述。

第六步,启动类是:

package com.yanfei1819.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}

最后,启动项目。直接访问 http://localhost:8085/ ,进入首页:

点击其中任意一个链接:

可以看到是没有权限访问的。因此,上述的 security 配置成功。

第二阶段:

  开启自动配置的登录功能,也就是在 SecurityConfig 配置类中加入以下代码:

        http.formLogin();

该功能的作用是,进入首页后,点击菜单,如果没有权限,则跳转到登录页。

第三阶段:

下面阐述设置登录账号和密码。

在 SecurityConfig 配置类重写 configure(AuthenticationManagerBuilder auth) 方法:

    // 定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("admin", "test")
.and().withUser("test").password("123456").roles("test");
}

注意,此处会有一个问题。如以上地址认证规则,在使用配置的账号登录时会报错:

这是由于在 Spring Security5.0 版本后,新增了加密方式,改变了密码的格式。

官网中有描述:

The general format for a password is:

{id}encodedPassword.

Such that id is an identifier used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with { and end with }. If the id cannot be found, the id will be null. For example, the following might be a list of passwords encoded using different id. All of the original passwords are "password".

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 1
{noop}password 2
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 3
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= 4
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 5
1 The first password would have a PasswordEncoder id of bcrypt and encodedPassword of $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG. When matching it would delegate to BCryptPasswordEncoder
2 The second password would have a PasswordEncoder id of noop and encodedPassword of password. When matching it would delegate to NoOpPasswordEncoder
3 The third password would have a PasswordEncoder id of pbkdf2 and encodedPassword of 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc. When matching it would delegate to Pbkdf2PasswordEncoder
4 The fourth password would have a PasswordEncoder id of scrypt and encodedPassword of $e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=When matching it would delegate to SCryptPasswordEncoder
5 The final password would have a PasswordEncoder id of sha256 and encodedPassword of 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0. When matching it would delegate to StandardPasswordEncoder

上面这段话的解释了为什么会报错:There is no PasswordEncoder mapped for the id "null",同时给出了解决方案。也就是 configure(AuthenticationManagerBuilder auth) 方法修改为:

    // 定义认证规则
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("admin","test")
.and().passwordEncoder(new BCryptPasswordEncoder())
.withUser("test").password(new BCryptPasswordEncoder().encode("123456")).roles("test");
}

修改后重启项目,登录可正常访问:

访问结果是:账号 admin/123456 可以访问所有菜单:SpringBoot 第一章、SpringBoot 第二章、work 第一章、work 第二章,账号 test/123456 只能访问 SpringBoot 第一章、SpringBoot 第二章。

第四阶段:

  开启自动配置的注销功能,并清除 session,在配置类 SecurityConfig 中的 configure(HttpSecurity http) 方法中添加:

http.logout();

然后在首页 index.html 中添加一个注销按钮:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是首页</h1>
<di>
<h3>追梦1819的博客系列</h3>
<ul>
<li><a th:href="@{/springboot/1}">SpringBoot 第一章</a></li>
<li><a th:href="@{/springboot/2}">SpringBoot 第二章</a></li>
<li><a th:href="@{/work/1}">work 第一章</a></li>
<li><a th:href="@{/work/2}">work 第二章</a></li>
</ul>
</di>
<div>
<form method="post" th:action="@{/logout}">
<input type="submit" value="logout">
</form>
</div>
</body>
</html>

启动项目,进入首页,点击 【logout】,会跳转到登录界面,同时链接中带了参数 ?logout

当然,也可以跳转到定制的页面,只要将属性修改为:

        http.logout()  // 退出并清除session
.logoutSuccessUrl("/");

第五阶段:

  以上的功能基本都满足了我们项目中的需求。不过只讲述了功能点。下面我们将阐述如何在页面展示以上功能。

  首先,我们必须引入以下依赖,以便使用 sec:authentication和sec:authorize 属性。

<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

注意: 此处有版本冲突问题,以上的演示的 SpringBoot 用的版本都是 2.1.6.RELEASE。但是在此如果继续使用该版本,则无法使用以上依赖中的 sec:authentication和sec:authorize 属性。作者在做此演示时,对 SpringBoot 版本作了降级处理,版本为 2.1.4.RELEASE。而旧的版本有很多不同的地方,例如旧版本的登录界面是:

此处需要特别注意!

引入上述依赖后,我们将首页进行改造:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是首页</h1>
<!--没有登录-->
<div sec:authorize="!isAuthenticated()">
<a th:href="@{/login}">login</a>
</div>
<!--已登录-->
<div sec:authorize="isAuthenticated()">
<div>
<form method="post" th:action="@{/logout}">
<input type="submit" value="logout">
</form>
</div>
登陆者:<span sec:authentication="name"></span>
登陆者角色:<span sec:authentication="principal.authorities"></span>
</div>
<div>
<h3>追梦1819的博客系列</h3>
<ul>
<!-- 通过角色判断是否展示-->
<div sec:authorize="hasRole('admin')">
<li><a th:href="@{/springboot/1}">SpringBoot 第一章</a></li>
<li><a th:href="@{/springboot/2}">SpringBoot 第二章</a></li>
</div>
<div sec:authorize="hasRole('test')">
<li><a th:href="@{/work/1}">work 第一章</a></li>
<li><a th:href="@{/work/2}">work 第二章</a></li>
</div>
</ul>
</div>
</body>
</html>

启动项目,分别用不登录、 admin/123456、test/123456 登录,查看效果:

第六阶段:

  最后我们讲解一个常用的功能,就是登陆的记住功能,配置很简单,在配置类 SecurityConfig 中的 configure(HttpSecurity http) 方法中添加即可:

        http.rememberMe() // 记住功能
.rememberMeParameter("remember") //自定义rememberMe的name值,默认remember-Me
.tokenValiditySeconds(10); // 记住时间

进入登陆界面:

添加该方法后,登录页会出现记住功能的复选框。

总结

  还有很多详细的功能。由于篇幅所限,本章中不做一一细解。如果想了解更多,作者给读者的建议是,可以多看看 WebSecurityConfigurerAdapterHttpSecurityAuthenticationManagerBuilder 等类的源码,比较简单,很容易上手。另外就是其文档非常的详细、清晰(文档详细是Spring的一个特色)。可以让大家先感受一下 Spring 源码文档的强大:

功能描述、示例一应俱全。

结语

  其实对以上功能的了解,不算很难。但是这篇博客前后写了六七个小时。作者看了翻阅了不少的资料,通读对应的官方文档,听了一些比较好的课程,然后自己一一校验,思考,排版,解决版本冲突等。最终是希望让读者能够看到一篇准确、美观、较详细的资料,不至于陷入网上的乱七八糟的资料中无法自拔。

参考

  1. Spring Security Reference
  2. Hello Spring Security with Boot
  3. WebSecurityConfigurerAdapterHttpSecurityAuthenticationManagerBuilder 等类的源码

![](https://img2018.cnblogs.com/blog/1183871/201908/1183871-20190816111539095-867953393.png)

SpringBoot第二十三篇:安全性之Spring Security的更多相关文章

  1. Python开发【第二十三篇】:持续更新中...

    Python开发[第二十三篇]:持续更新中...

  2. SpringBoot非官方教程 | 第二十三篇: 异步方法

    转载请标明出处: 原文首发于https://www.fangzhipeng.com/springboot/2017/07/11/springboot-ansy/ 本文出自方志朋的博客 这篇文章主要介绍 ...

  3. SpringBoot | 第二十三章:日志管理之整合篇

    前言 在本系列<第四章:日志管理>中,由于工作中日志这块都是走默认配置,也没有深入了解过,因为部署过程中直接使用了linux中的输出重定向功能,如java -jar xx.jar > ...

  4. SpringBoot第二十一篇:整合ActiveMQ

    作者:追梦1819 原文:https://www.cnblogs.com/yanfei1819/p/11190048.html 版权声明:本文为博主原创文章,转载请附上博文链接! 引言   前一章节中 ...

  5. SpringBoot第二十篇:初识ActiveMQ

    本文是作者之前写的关于 ActiveMQ 的一篇文章.此处为了保证该系列文章的完整性,故此处重新引入. 一.消息中间件的介绍 介绍 消息队列 是指利用 高效可靠 的 消息传递机制 进行与平台无关的 数 ...

  6. 第二十三篇:在SOUI中使用LUA脚本开发界面

    像写网页一样做客户端界面可能是很多客户端开发的理想. 做好一个可以实现和用户交互的动态网页应该包含两个部分:使用html做网页的布局,使用脚本如vbscript,javascript做用户交互的逻辑. ...

  7. Python之路【第二十三篇】:Django 初探--Django的开发服务器及创建数据库(笔记)

    Django 初探--Django的开发服务器及创建数据库(笔记) 1.Django的开发服务器 Django框架中包含一些轻量级的web应用服务器,开发web项目时不需再对其配置服务器,Django ...

  8. Python之路(第二十三篇) 面向对象初级:静态属性、静态方法、类方法

    一.静态属性 静态属性相当于数据属性. 用@property语法糖装饰器将类的函数属性变成可以不用加括号直接的类似数据属性. 可以封装逻辑,让用户感觉是在调用一个普通的数据属性. 例子 class R ...

  9. 【Python之路】第二十三篇--Django【进阶篇】

    文件配置 1.模版Templates文件配置: TEMPLATE_DIRS = ( os.path.join(BASE_DIR,'templates'), ) 2.静态文件static配置: STAT ...

随机推荐

  1. c语言学习书籍推荐《C语言学习路线图•C语言必须知道的300个问题》下载

    下载地址:点我 <C语言学习路线图•C语言必须知道的300个问题>以基础知识为框架,介绍了c语言各部分知识所对应的常见开发疑难问题,并作了透彻地解析.<C语言学习路线图•C语言必须知 ...

  2. git rebase VS git merge? 更优雅的 git 合并方式值得拥有

    写在前面 如果你不能很好的应用 Git,那么这里为你提供一个非常棒的 Git 在线练习工具 Git Online ,你可以更直观的看到你所使用的命令会产生什么效果 另外,你在使用 Git 合并分支时只 ...

  3. 【POJ - 2253】Frogger (Floyd算法)

    -->Frogger 中文翻译 Descriptions: 湖中有n块石头,编号从1到n,有两只青蛙,Bob在1号石头上,Alice在2号石头上,Bob想去看望Alice,但由于水很脏,他想避免 ...

  4. [原创]Flask+uwsgi+virtualenv+nginx部署配置

    1.创建工程python2.7版本虚目录: #virtualenv -p /usr/bin/python2.7 CDN_resource #cd CDN_resource #source ./bin/ ...

  5. React躬行记(3)——组件

    组件(Component)由若干个React元素组成,包含属性.状态和生命周期等部分,满足独立.可复用.高内聚和低耦合等设计原则,每个React应用程序都是由一个个的组件搭建而成,即组成React应用 ...

  6. 深入理解 JavaScript 单例模式 (Singleton Pattern)

    概念 单例模式,也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对象的类必须保证只有一个实例存在. 核心:确保只有一个实例,并提供全局访问. 实现思路 一个类能返回对象一个引用(永远是同 ...

  7. 不同版本2.5的Servlet web.xml 头信息

    <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" ...

  8. 基站搭建与IMSI捕获

     写在前面 : 实验目的是为了教学交流,坚决抵制违法行为. 一.实验目的 搭建基于OpenBTS的基站,手机接入该基站,进行短信.语音等测试. 二.所用仪器 USRP B210 1台,天线2根,PC机 ...

  9. NPOI 日期类型的判断

    NPOI目前我用到有两套类,一套是为了读写XLS:一套是读写XLSX 在读取文件时大都会判断单元格类型,方式大同小异,只有日期类型不同. 默认日期类型的单元格在NPOI都认为是数值类型(CellTyp ...

  10. typedef int a[10];怎么解释?

    typedef int a[10]; a b[10]; 为什么分配400个字节的空间? int a[10];为什么分配了40个字节的空间? 问题:应该怎么解释typedef的这种行为呢?而如果换成是# ...