作者:追梦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. Vue快速学习_第一节

    之前写CRM都是Django前后端一起写的,在大部分项目中实际上前后端是分离的,因此我们需要学习一个前端框架来进行前端页面的编写,这里选择了Vue进行学习,好了开始学习吧. 1.ES6部分知识点学习 ...

  2. Android Native 内存泄漏系统化解决方案

    导读:C++内存泄漏问题的分析.定位一直是Android平台上困扰开发人员的难题.因为地图渲染.导航等核心功能对性能要求很高,高德地图APP中存在大量的C++代码.解决这个问题对于产品质量尤为重要和关 ...

  3. PG利用Multicorn访问CSV外部数据源

    Multicorn 是一个 PostgreSQL 9.1+ 的扩展模块,用于简化外部数据封装开发,允许开发者使用 Python 编程语言开发. Install Multicorn Requiremen ...

  4. JS高级程序设计第4章--精简版

    前言:纯手打!!!按照自己思路重写!!!这次是二刷了,想暑假做一次完整的笔记,但用本子来写笔记的话太贵了,可能哪天还丢了..所以还是博客好== 第四章:变量.作用域和内存问题 4.1 基本类型和引用类 ...

  5. 集群之间配置 SSH无密码登录

    集群之间配置 SSH无密码登录 配置 ssh (1)基本语法 ssh 另一台电脑的 ip 地址 (2)ssh 连接时出现 Host key verification failed 的解决方法 # ss ...

  6. C#6.0 新增功能

    连载目录    [已更新最新开发文章,点击查看详细] C# 6.0 版本包含许多可提高开发人员工作效率的功能. 这些功能的总体效果是让你编写的代码更简洁.更具可读性. 该语法不像许多常见做法那样繁琐. ...

  7. MySQL图形工具SQLyog破解版

    最近一直在用MySQL,所以分享一下现在这套开发工具. SQLyog:链接:http://pan.baidu.com/s/1bLq2OA 密码:h5bj 注册信息用户名:yunjian注册码:81f4 ...

  8. PHP-2.数据库小功能

    <?php /* * <PHP数据库部分功能实现> */ $KCNUM = @$_POST['KCNUM']; //建立一个数据库连接 $conn = mysql_connect(' ...

  9. 一文彻底搞懂Java中的环境变量

    一文搞懂Java环境变量 记得刚接触Java,第一件事就是配环境变量,作为一个初学者,只知道环境变量怎样配,在加上各种IDE使我们能方便的开发,而忽略了其本质的东西,只知其然不知其所以然,随着不断的深 ...

  10. 2019牛客暑期多校训练营(第四场)K.number

    >传送门< 题意:给你一个字符串s,求出其中能整除300的子串个数(子串要求是连续的,允许前面有0) 思路: >动态规划 记f[i][j]为右端点满足mod 300 = j的子串个数 ...