初识Spring Security

为什么需要安全框架

大多数项目应该都有权限一类的控制,目的在于让用户不能去访问不该访问的内容。在没有使用安全框架之前,都是通过各种拦截器,监听器来控制用户的访问,或者是通过Spring的AOP控制用户对某个方法的访问,在当时看来还比较方便,但是到后来维护越来越麻烦,如果要增加或者是修改一个逻辑,那这个拦截器或者是监听器又会改动很多。
例如一个博客系统,对不同的用户需要有不同的功能显示,如游客不能评论,正式用户只能管理自己发表的博客等等,这在小型项目中还方便实现,可如果项目变得越来越大,对权限的划分也越来越细,比如对正式用户进行分级,三级以上的正式用户可以开启付费博客,甚至到某一个方法,虽然原来的方式也能实现,但会非常繁琐。

安全框架的种类与区分

Shiro

Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:

在这里插入图片描述

  • Authentication: 身份认证 / 登录,验证用户是不是拥有相应的身份;
  • Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Management: 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
  • Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support: Web 支持,可以非常容易的集成到 Web 环境;
  • Caching: 缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
  • Concurrency: shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing: 提供测试支持;
  • Run As: 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me: 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

记住一点,Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 即可。


接下来我们分别从外部和内部来看看 Shiro 的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的 API,且 API 契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。
首先,我们从外部来看 Shiro 吧,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图:

可以看到:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject;其每个 API 的含义:

  • Subject: 主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;
  • SecurityManager: 安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
  • Realm: 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

也就是说对于我们而言,最简单的一个 Shiro 应用:

  1. 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
  2. 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

从以上也可以看出,Shiro 不提供维护用户 / 权限,而是通过 Realm 让开发人员自己注入。
接下来我们来从 Shiro 内部来看下 Shiro 的架构,如下图所示:

  • Subject: 主体,可以看到主体可以是任何可以与应用交互的 “用户”;
  • SecurityManager: 相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
  • Authenticator: 认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authrizer: 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm: 可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
  • SessionManager: 如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
  • SessionDAO: DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;
  • CacheManager: 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography: 密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。

Spring Security

Spring Security 提供了基于javaEE的企业应有个你软件全面的安全服务。这里特别强调支持使用SPring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。如果你还没有使用Spring来开发企业应用程序,我们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security。
人们使用Spring Secruity的原因有很多,单大部分都发现了javaEE的Servlet规范或EJB规范中的安全功能缺乏典型企业应用场景所需的深度。提到这些规范,重要的是要认识到他们在WAR或EAR级别无法移植。因此如果你更换服务器环境,这里有典型的大量工作去重新配置你的应用程序员安全到新的目标环境。使用Spring Security 解决了这些问题,也为你提供许多其他有用的,可定制的安全功能。
正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中。
在身份验证层,Spring Security 的支持多种认证模式。这些验证绝大多数都是要么由第三方提供,或由相关的标准组织,如互联网工程任务组开发。另外Spring Security 提供自己的一组认证功能。具体而言,Spring Security 目前支持所有这些技术集成的身份验证:

  • HTTP BASIC 认证头 (基于 IETF RFC-based 标准)
  • HTTP Digest 认证头 ( IETF RFC-based 标准)
  • HTTP X.509 客户端证书交换 ( IETF RFC-based 标准)
  • LDAP (一个非常常见的方法来跨平台认证需要, 尤其是在大型环境)
  • Form-based authentication (用于简单的用户界面)
  • OpenID 认证
  • Authentication based on pre-established request headers (such as Computer Associates Siteminder) 根据预先建立的请求有进行验证
  • JA-SIG Central Authentication Service (CAS,一个开源的SSO系统 )
  • Transparent authentication context propagation for Remote Method Invocation (RMI) and HttpInvoker (Spring远程协议)
  • Automatic “remember-me” authentication (你可以勾选一个框以避免预定的时间段再认证)
  • Anonymous authentication (让每一个未经验证的访问自动假设为一个特定的安全标识)
  • Run-as authentication (在一个访问应该使用不同的安全标识时非常有用)
  • Java Authentication and Authorization Service (JAAS)
  • JEE container autentication (所以如果愿你以可以任然使用容器管理的认证)
  • Kerberos
  • Java Open Source Single Sign On (JOSSO) *
  • OpenNMS Network Management Platform *
  • AppFuse *
  • AndroMDA *
  • Mule ESB *
  • Direct Web Request (DWR) *
  • Grails *
  • Tapestry *
  • JTrac *
  • Jasypt *
  • Roller *
  • Elastic Path *
  • Atlassian Crowd *
  • Your own authentication systems (see below)
  • 表示由第三方提供

很多独立软件供应商,因为灵活的身份验证模式二选择Spring Security。这样做允许他们快速的集成到他们的终端客户需求的解决方案而不用进行大量工程或者改变客户的环境。如果上面的验证机制不符合你的需求,Spring Security 是一个开放的平台,要实现你 自己的验证机制检查。Spring Security 的许多企业用户需要与不遵循任何安全标准的“遗留”系统集成,Spring Security可以很好的与这类系统集成。
无论何种身份验证机制,Spring Security提供一套的授权功能。这里有三个主要的热点区域,授权web请求、授权方法是否可以被调用和授权访问单个域对象的实例。为了帮助让你分别了解这些差异,认识在Servlet规范网络模式安全的授权功能,EJB容器管理的安全性和文件系统的安全。

基本概念

什么是认证

进入互联网时代,各种各样的应用与网站丰富了人们的日常生活。以常用的微信距离,在初次使用微信前,需要先注册成为微信用户,然后输入账号密码登陆微信。登陆微信的过程就是认证。
这些系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法即可访问该系统的资源。
**认证:**用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时,系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的身份身份认证方式有:用户名密码登陆,二维码登陆,手机短信登陆,指纹认证等方式。

什么是会话

用户认证通过后,为了避免用户的每次操作都进行认证,可将用户的信息保存在会话中。会话就是系统为了保持当前用户的的呢露骨状态所提供的机制,常见的有基于session的方式,基于token的方式等。

基于Session的认证方式

它的交互流程是,用户认证陈宫后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的session_id存放到cookie中,这样用户客户端请求时带上session_id就可以验证服务器端是否存在session数据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就失效了。如下图
image.png

基于Token的认证方式

它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到cookie或localStorage等存储中,每次请求时带上Token,服务端收到Token通过验证后即可确认用户身份。
image.png
基于Session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持cookie;基于Token的方式一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代,更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于Token的方式更适合。

什么是授权

还以微信举例,微信登录成功后用户即可使用微信的功能,比如,发红包,发朋友圈,添加好友等。没有绑定银行卡的用户是无法发送红包的,绑定银行卡的用户才可以发红包,发红包功能,发朋友圈功能都是微信的资源,即功能资源,用户拥有发红包的权限才可以正常使用发送红包功能,拥有发朋友圈功能的权限才可以使用发朋友圈功能,这个根据用户的权限来控制用户使用资源的过程就是收取那。
为什么要授权?
认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是认证通过后发生的,控制不同的用户能够访问不同的资源。
授权: 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。

授权的数据模型

如何进行授权,即如何对用户访问资源进行控制,首先需要学习授权相关的数据模型。
授权可简单的理解为Who对What(Which)进行How操作,包括如下:
Who,即主体(Subject),主体一般是指用户,也可以是程序,需要访问系统中的资源。WHat,即资源(Resource),如系统菜单、页面、代码方法、系统商品信息、系统订单信息等。系统菜单、页面、按钮、代码方法都属于系统功能资源,对于Web系统,每个功能资源通常对应一个URL;系统商品信息,系统订单信息都属于实体资源(数据资源),实体资源由资源类型和资源实例组成,比如商品信息为资源类型,商品编号为001的商品为资源实例。How,权限/许可(Permission),规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限,用户添加权限,某个代码方法的调用权限、编号为001的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可。
主体、资源、权限关系如下图
image.png
主体、资源、权限相关的数据模型如下:
主体(用户id、账号、密码……)
资源(资源id、资源名称、访问地址、……)
权限(权限id、权限标识、权限名称、权限id、……)
角色(角色id、角色名称、……)
角色和权限关系(角色id、权限id、……)
主体(用户)和角色关系(用户id、角色id、……)
主体(用户)、资源、权限关系如下图:
image.png
通常企业开发中将资源和权限表合并为一张权限表,如下:
资源(资源id,资源名称、访问地址、……)
权限(权限id、权限表示、权限名称、资源id、……)
合并为:
权限(权限id、权限标识、权限名称、资源名称、资源访问地址、……)
修改之后的数据模型之间的关系如下图:
image.png

RBAC

如何实现授权?业界通常基于RBAC实现授权

基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
image.png
根据上图的判断逻辑,授权代码可表示如下:

if(主体.hasRole("总经理角色id"){
    查询工资
 }	

如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是总经理或部门经理”,修改代码如下:

if(主体.hasRole("总经理角色id" || 主体.hasRole("部门经理id"){
    查询工资
 }	

根据上面的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。

基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,比如用户必须具有查询查询工资权限才可以查询员工工资信息,访问控制流程如下:
image.png
根据上图的判断,授权代码可以表示为:

if(主体.hasPermission("查询工资权限表示")){
    查询工资
}

优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强。

使用SpringSecurity实现登录认证与授权

本部分实现一个SpringSecurity进行登录认证与授权的Demo

一个最简单的SpringSecurity项目

  • 创建SpringBoot项目,选择依赖,模板引擎可以选择其他的,如Thymleaf,个人习惯使用Freemarker

image.png

  • 创建项目之后,配置项目端口,即可启动SpringBoot应用
  • 用户名user,密码已经打印在控制台上

image.png

  • 登录成功页面

image.png

  • 也可以在配置文件中配置用户名和密码
server:
  port: 8088
spring:
  security:
    user:
      name: user
      password: user

从数据库中获取用户名和密码进行登录

  • pom文件中引入数据库与模板引擎等相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<artifactId>spring-sercurity-demo</artifactId>
		<groupId>cn.aixuxi</groupId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>security</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>security</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
    <!-- 使用MP ORM框架,依据各人喜好 -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.3.1</version>
		</dependency>
    <!-- 模板引擎使用Freemarker,也可使用Thymleaf等其他模板引擎 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-freemarker</artifactId>
		</dependency>
    <!-- MySQL -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
    <!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
    <!--- Web核心 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
    <!-- 简化开发 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>
  • 配置文件中相关配置
server:
  port: 8088

spring:
	# 配置数据库相关信息
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
  # 模板引擎设置
  freemarker:
    # 是否启用模板引擎
    enabled: true
    # 设定模板的加载路径
    template-loader-path: classpath:/templates/
    # Content-Type 的值
    content-type: text/html
    # 设定模板的后缀
    suffix: .ftl
    # 是否启用模板缓存
    cache: false
    # 是否优先从文件系统加载template,以支持热加载,默认为true
    prefer-file-system-access: true
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml

用户授权与测试

创建Spring Security安全用户实体类

SpringSecurity登录需要的用户,可以自定义实现,实现UserDetails接口

package cn.aixuxi.security.entity;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 类描述 Security用户
 *
 * @author ruozhuliufeng
 * @version 1.0
 * @date 2021/4/14 21:15
 */
@Data
@Slf4j
public class SecurityUser implements UserDetails {
    //当前登录用户
    private transient SysUser currentUserInfo;
    //当前权限
    private List<String> permissionValueList;
    public SecurityUser() {
    }
    public SecurityUser(SysUser user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for (String permissionValue : permissionValueList) {
            if (StringUtils.isEmpty(permissionValue)) {
                continue;
            }
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities.add(authority);
        }
        return authorities;
    }
    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }
    @Override
    public String getUsername() {
        return currentUserInfo.getUsername();
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}

创建Spring Security的用户实现类UserDetailsService

需要自定义实现UserDetailsService,连接数据库并根据用户名查询用户及权限列表

package cn.aixuxi.security.service.impl;

import cn.aixuxi.security.entity.SecurityUser;
import cn.aixuxi.security.entity.SysUser;
import cn.aixuxi.security.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 类描述
 *
 * @author ruozhuliufeng
 * @version 1.0
 * @date 2021/4/14 21:35
 */
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询数据
        SysUser sysUser = sysUserService.selectByUsername(username);
        // 判断用户是否为空
        if (sysUser == null){
            throw new UsernameNotFoundException("用户不存在");
        }
        SecurityUser securityUser = new SecurityUser();
        securityUser.setCurrentUserInfo(sysUser);
        // 一般而言,这里需要根据用户的id,查询所有的角色id,并根据角色id,查询所有的权限id,再据此获取权限编码
        // 在这个demo中,取巧,直接赋值
        List<String> permissionValueList = new ArrayList<>();
        permissionValueList.add("test:view");
        securityUser.setPermissionValueList(permissionValueList);
        return securityUser;
    }
}

Spring Security配置

package cn.aixuxi.security.config;

import cn.aixuxi.security.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
            	// 放行前端资源,支持Ant风格
                .antMatchers("/css/**","/images/**","/fonts/**")
            	// 代表不需要授权
                .permitAll()
            	// 除此之外的其他所有连接都需要授权
                .anyRequest().authenticated()
                .and()
            	// 配置自定义表单登录
                .formLogin()
            	// Controller中配置跳转路径
                .loginPage("/login.ftl")
            	// 登录路径
                .loginProcessingUrl("/login")
            	// 登录成功访问路径
                .defaultSuccessUrl("/index")
                .permitAll()
                .and()
            	// 关闭CSRF
                .csrf().disable()
                .logout().logoutUrl("/logout")
    }

    /**
     * 功能描述: 自定义UserDetailsService及密码加密
     * @param auth 1
     * @author : ruozhuliufeng
     * @date : 2021/4/22 20:42
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    /**
     * 功能描述: 密码加密
     * @return : PasswordEncoder
     * @author : ruozhuliufeng
     * @date : 2021/4/22 20:42
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        // 配置加密次数
        return new BCryptPasswordEncoder(12);
    }
}

其他相关

由于MyBatist Plus已经为我们提供了Mapper、Service层基本的增删改查功能,我们只需要继承相关的类即可,以SysUser为例:
mapper下的SysUserMapper,BaseMapper就是MyBatis Plus为我们提供的DAO层基础增删改查的接口

package cn.aixuxi.security.mapper;

import cn.aixuxi.security.entity.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;


public interface SysUserMapper extends BaseMapper<SysUser> {
}

Service层的SysUserService接口

package cn.aixuxi.security.service;

import cn.aixuxi.security.entity.SysUser;
import com.baomidou.mybatisplus.extension.service.IService;

public interface SysUserService extends IService<SysUser> {
    // 根据用户名查询用户信息
    SysUser selectByUsername(String username);
}

实现类SysUserServiceImpl

package cn.aixuxi.security.service.impl;

import cn.aixuxi.security.entity.SysUser;
import cn.aixuxi.security.mapper.SysUserMapper;
import cn.aixuxi.security.service.SysUserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * 类描述 用户实体类,继承MP通用服务实现类,实现基础增删改查
 *
 * @author ruozhuliufeng
 * @version 1.0
 * @date 2021/4/14 21:35
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    @Override
    public SysUser selectByUsername(String username) {
        return getBaseMapper().selectOne(new QueryWrapper<SysUser>().eq("username",username));
    }
}

前端

可以自己编写个人喜好的登录网页,我为了方便,从网络上找了一个登录模板,需要注意的是,要在SpringSecurity中放行前端资源,用户名只能是username,密码password,这是SpringSecurity中要求的格式,POST方式提交到/login
权限校验等页面使用的是光年后台模板

代码地址:

小结

这是一个简单的Spring Security的小demo,实现很简单,Spring Security的本质是一个过滤器链,后面我将尝试分析登录的过滤过程。
后续基于Spring Security实现单点登录认证,以及OAuth2.0的小demo,我在继续学习并编码中。
天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ruozhuliufeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值