常用的框架技术-09 Spring Security Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架

news2024/11/19 15:27:40

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 1.Spring Security简介
    • 1.1 Spring Security概述
    • 1.2 Spring Security历史发展
    • 1.3 产品的对比
      • 1.3.1 Spring Security
      • 1.3.2 Shiro
    • 1.4 Spring Security 核心类
      • 1.4.1 Authentication接口
      • 1.4.2 SecurityContextHolder
      • 1.4.3 AuthenticationManager 和 AuthenticationProvider
      • 1.4.4 认证成功后清除凭证
      • 1.4.5 UserDetailsService
      • 1.4.5 JdbcDaoImpl
      • 1.4.6 InMemoryDaoImpl
      • 1.4.7 GrantedAuthority
  • 2.Spring通过XML配置整合Spring Security
    • 2.1 环境准备(都是假数据)
    • 2.2 入门案例
      • 2.2.1 导入依赖
      • 2.2.2 配置Spring Security Fillter过滤器
      • 2.2.3 编写spring-security.xml配置文件
    • 2.3 对入门案例进行改进
      • 2.3.1 配置可以匿名访问的资源
      • 2.3.2 配置指定登录页面
      • 2.3.3 配置从数据库查询访问数据
      • 2.3.4 对密码进行加密
      • 2.3.5 配置多种权限校验规则
      • 2.3.6 退出登录
    • 2.4 小结
  • 3.Spring注解整合Spring Security
    • 3.0 资源准备,导入初始化项目
    • 3.1 入门案例
      • 3.1.1 导入依赖
      • 3.1.2 配置Spring Security Fillter
      • 3.1.3 配置Spring Security
      • 3.1.3 测试
      • 3.1.4 配置内存用户名密码
      • 3.1.5 配置密码加密技术
    • 3.2 对入门案例进行改进
      • 3.2.1 设置允许iframe嵌套显示
      • 3.2.2 配置资源放行
      • 3.2.3 配置自定义登录界面
      • 3.2.4 配置从数据库查询用户访问数据
      • 3.2.4 注解配置访问权限
      • 3.2.4 自定义权限不足页面
      • 3.2.4 配置注销功能
  • 4.源代码与原始项目资源


1.Spring Security简介

1.1 Spring Security概述

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)用户授权(Authorization) 两个部分,这两点也是 Spring Security 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情

1.2 Spring Security历史发展

“Spring Security 开始于 2003 年年底,““spring 的 acegi 安全系统”。 起因是 Spring开发者邮件列表中的一个问题,有人提问是否考虑提供一个基于 spring 的安全实现。

Spring Security 以“The Acegi Secutity System for Spring” 的名字始于 2013 年晚些时候。一个问题提交到 Spring 开发者的邮件列表,询问是否已经有考虑一个机遇 Spring 的安全性社区实现。那时候 Spring 的社区相对较小(相对现在)。实际上 Spring 自己在2013 年只是一个存在于 ScourseForge 的项目,这个问题的回答是一个值得研究的领域,虽然目前时间的缺乏组织了我们对它的探索。

考虑到这一点,一个简单的安全实现建成但是并没有发布。几周后,Spring 社区的其他成员询问了安全性,这次这个代码被发送给他们。其他几个请求也跟随而来。到 2014 年一月大约有 20 万人使用了这个代码。这些创业者的人提出一个 SourceForge 项目加入是为了,这是在 2004 三月正式成立。

在早些时候,这个项目没有任何自己的验证模块,身份验证过程依赖于容器管理的安全性和 Acegi 安全性。而不是专注于授权。开始的时候这很适合,但是越来越多的用户请求额外的容器支持。容器特定的认证领域接口的基本限制变得清晰。还有一个相关的问题增加新的容器的路径,这是最终用户的困惑和错误配置的常见问题。

Acegi 安全特定的认证服务介绍。大约一年后,Acegi 安全正式成为了 Spring 框架的子项目。1.0.0 最终版本是出版于 2006 -在超过两年半的大量生产的软件项目和数以百计的改进和积极利用社区的贡献。

Acegi 安全 2007 年底正式成为了 Spring 组合项目,更名为 “Spring Security”。

1.3 产品的对比

1.3.1 Spring Security

Spring 技术栈的组成部分。通过提供完整可扩展的认证和授权支持保护你的应用程序。
SpringSecurity 特点:

  • 和 Spring 无缝整合。
  • 全面的权限控制。
  • 专门为 Web 开发而设计。
    • 旧版本不能脱离 Web 环境使用。
    • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。
  • 重量级。

1.3.2 Shiro

Apache 旗下的轻量级权限控制框架。
Shiro特点:

  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
  • 通用性。
    • 好处:不局限于 Web 环境,可以脱离 Web 环境使用。
    • 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。

以上只是一个推荐的组合而已,如果单纯从技术上来说,无论怎么组合,都是可以运行的。所以后面将通过Spring XML配置、注解两个方面来实现一边Spring Security框架的性能。在实现功能前要先了解几个SpringSecurity核心类。

1.4 Spring Security 核心类

1.4.1 Authentication接口

Authentication 是一个接口,用来表示用户认证信息的,在用户登录认证之前相关信息会封装为一个 Authentication 具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的 Authentication 对象,然后把它保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用,如访问权限的鉴定等。

1.4.2 SecurityContextHolder

SecurityContextHolder 是用来保存 SecurityContext 的。SecurityContext 中含有当前正在访问系统的用户的详细信息。默认情况下,SecurityContextHolder 将使用 ThreadLocal 来保存 SecurityContext,这也就意味着在处于同一线程中的方法中我们可以从 ThreadLocal 中获取到当前的 SecurityContext。因为线程池的原因,如果我们每次在请求完成后都将 ThreadLocal 进行清除的话,那么我们把 SecurityContext 存放在 ThreadLocal 中还是比较安全的。这些工作 Spring Security 已经自动为我们做了,即在每一次 request 结束后都将清除当前线程的 ThreadLocal。

SecurityContextHolder 中定义了一系列的静态方法,而这些静态方法内部逻辑基本上都是通过 SecurityContextHolder 持有的 SecurityContextHolderStrategy 来实现的,如 getContext()、setContext()、clearContext()等。而默认使用的 strategy 就是基于 ThreadLocal 的 ThreadLocalSecurityContextHolderStrategy。另外,Spring Security 还提供了两种类型的 strategy 实现,GlobalSecurityContextHolderStrategy 和 InheritableThreadLocalSecurityContextHolderStrategy,前者表示全局使用同一个 SecurityContext,如 C/S 结构的客户端;后者使用 InheritableThreadLocal 来存放 SecurityContext,即子线程可以使用父线程中存放的变量。

一般而言,我们使用默认的 strategy 就可以了,但是如果要改变默认的 strategy,Spring Security 为我们提供了两种方法,这两种方式都是通过改变 strategyName 来实现的。SecurityContextHolder 中为三种不同类型的 strategy 分别命名为 MODE_THREADLOCAL、MODE_INHERITABLETHREADLOCAL 和 MODE_GLOBAL。第一种方式是通过 SecurityContextHolder 的静态方法 setStrategyName() 来指定需要使用的 strategy;第二种方式是通过系统属性进行指定,其中属性名默认为 “spring.security.strategy”,属性值为对应 strategy 的名称。

Spring Security 使用一个 Authentication 对象来描述当前用户的相关信息。SecurityContextHolder 中持有的是当前用户的 SecurityContext,而 SecurityContext 持有的是代表当前用户相关信息的 Authentication 的引用。这个 Authentication 对象不需要我们自己去创建,在与系统交互的过程中,Spring Security 会自动为我们创建相应的 Authentication 对象,然后赋值给当前的 SecurityContext。但是往往我们需要在程序中获取当前用户的相关信息,比如最常见的是获取当前登录用户的用户名。在程序的任何地方,通过如下方式我们可以获取到当前用户的用户名。

public String getCurrentUsername() {
      Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      if (principal instanceof UserDetails) {
         return ((UserDetails) principal).getUsername();
      }
      if (principal instanceof Principal) {
         return ((Principal) principal).getName();
      }
      return String.valueOf(principal);
 }

通过 Authentication.getPrincipal() 可以获取到代表当前用户的信息,这个对象通常是 UserDetails 的实例。获取当前用户的用户名是一种比较常见的需求,关于上述代码其实 Spring Security 在 Authentication 中的实现类中已经为我们做了相关实现,所以获取当前用户的用户名最简单的方式应当如下。

public String getCurrentUsername() {
      return SecurityContextHolder.getContext().getAuthentication().getName();
}

此外,调用 SecurityContextHolder.getContext() 获取 SecurityContext 时,如果对应的 SecurityContext 不存在,则 Spring Security 将为我们建立一个空的 SecurityContext 并进行返回。

1.4.3 AuthenticationManager 和 AuthenticationProvider

AuthenticationManager 是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法 authenticate(),该方法只接收一个代表认证请求的 Authentication 对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的 Authentication 对象进行返回。

Authentication authenticate(Authentication authentication) throws AuthenticationException;

在 Spring Security 中,AuthenticationManager 的默认实现是 ProviderManager,而且它不直接自己处理认证请求,而是委托给其所配置的 AuthenticationProvider 列表,然后会依次使用每一个 AuthenticationProvider 进行认证,如果有一个 AuthenticationProvider 认证后的结果不为 null,则表示该 AuthenticationProvider 已经认证成功,之后的 AuthenticationProvider 将不再继续认证。然后直接以该 AuthenticationProvider 的认证结果作为 ProviderManager 的认证结果。如果所有的 AuthenticationProvider 的认证结果都为 null,则表示认证失败,将抛出一个 ProviderNotFoundException。校验认证请求最常用的方法是根据请求的用户名加载对应的 UserDetails,然后比对 UserDetails 的密码与认证请求的密码是否一致,一致则表示认证通过。Spring Security 内部的 DaoAuthenticationProvider 就是使用的这种方式。其内部使用 UserDetailsService 来负责加载 UserDetails,UserDetailsService 将在下节讲解。在认证成功以后会使用加载的 UserDetails 来封装要返回的 Authentication 对象,加载的 UserDetails 对象是包含用户权限等信息的。认证成功返回的 Authentication 对象将会保存在当前的 SecurityContext 中。

当我们在使用 NameSpace 时, authentication-manager 元素的使用会使 Spring Security 在内部创建一个 ProviderManager,然后可以通过 authentication-provider 元素往其中添加 AuthenticationProvider。当定义 authentication-provider 元素时,如果没有通过 ref 属性指定关联哪个 AuthenticationProvider,Spring Security 默认就会使用 DaoAuthenticationProvider。使用了 NameSpace 后我们就不要再声明 ProviderManager 了。

   <security:authentication-manager alias="authenticationManager">
      <security:authentication-provider
         user-service-ref="userDetailsService"/>
   </security:authentication-manager>

如果我们没有使用 NameSpace,那么我们就应该在 ApplicationContext 中声明一个 ProviderManager。

1.4.4 认证成功后清除凭证

默认情况下,在认证成功后 ProviderManager 将清除返回的 Authentication 中的凭证信息,如密码。所以如果你在无状态的应用中将返回的 Authentication 信息缓存起来了,那么以后你再利用缓存的信息去认证将会失败,因为它已经不存在密码这样的凭证信息了。所以在使用缓存的时候你应该考虑到这个问题。一种解决办法是设置 ProviderManager 的 eraseCredentialsAfterAuthentication 属性为 false,或者想办法在缓存时将凭证信息一起缓存。

1.4.5 UserDetailsService

通过 Authentication.getPrincipal() 的返回类型是 Object,但很多情况下其返回的其实是一个 UserDetails 的实例。UserDetails 是 Spring Security 中一个核心的接口。其中定义了一些可以获取用户名、密码、权限等与认证相关的信息的方法。Spring Security 内部使用的 UserDetails 实现类大都是内置的 User 类,我们如果要使用 UserDetails 时也可以直接使用该类。在 Spring Security 内部很多地方需要使用用户信息的时候基本上都是使用的 UserDetails,比如在登录认证的时候。登录认证的时候 Spring Security 会通过 UserDetailsService 的 loadUserByUsername() 方法获取对应的 UserDetails 进行认证,认证通过后会将该 UserDetails 赋给认证通过的 Authentication 的 principal,然后再把该 Authentication 存入到 SecurityContext 中。之后如果需要使用用户信息的时候就是通过 SecurityContextHolder 获取存放在 SecurityContext 中的 Authentication 的 principal。

通常我们需要在应用中获取当前用户的其它信息,如 Email、电话等。这时存放在 Authentication 的 principal 中只包含有认证相关信息的 UserDetails 对象可能就不能满足我们的要求了。这时我们可以实现自己的 UserDetails,在该实现类中我们可以定义一些获取用户其它信息的方法,这样将来我们就可以直接从当前 SecurityContext 的 Authentication 的 principal 中获取这些信息了。上文已经提到了 UserDetails 是通过 UserDetailsService 的 loadUserByUsername() 方法进行加载的。UserDetailsService 也是一个接口,我们也需要实现自己的 UserDetailsService 来加载我们自定义的 UserDetails 信息。然后把它指定给 AuthenticationProvider 即可。如下是一个配置 UserDetailsService 的示例。

<!-- 用于认证的 AuthenticationManager -->
   <security:authentication-manager alias="authenticationManager">
      <security:authentication-provider
         user-service-ref="userDetailsService" />
   </security:authentication-manager>

   <bean id="userDetailsService"
      class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
      <property name="dataSource" ref="dataSource" />
   </bean>

上述代码中我们使用的 JdbcDaoImpl 是 Spring Security 为我们提供的 UserDetailsService 的实现,另外 Spring Security 还为我们提供了 UserDetailsService 另外一个实现,InMemoryDaoImpl。

其作用是从数据库中加载 UserDetails 信息。其中已经定义好了加载相关信息的默认脚本,这些脚本也可以通过 JdbcDaoImpl 的相关属性进行指定。关于 JdbcDaoImpl 使用方式会在讲解 AuthenticationProvider 的时候做一个相对详细一点的介绍。

1.4.5 JdbcDaoImpl

JdbcDaoImpl 允许我们从数据库来加载 UserDetails,其底层使用的是 Spring 的 JdbcTemplate 进行操作,所以我们需要给其指定一个数据源。此外,我们需要通过 usersByUsernameQuery 属性指定通过 username 查询用户信息的 SQL 语句;通过 authoritiesByUsernameQuery 属性指定通过 username 查询用户所拥有的权限的 SQL 语句;如果我们通过设置 JdbcDaoImpl 的 enableGroups 为 true 启用了用户组权限的支持,则我们还需要通过 groupAuthoritiesByUsernameQuery 属性指定根据 username 查询用户组权限的 SQL 语句。当这些信息都没有指定时,将使用默认的 SQL 语句,默认的 SQL 语句如下所示。

select username, password, enabled from users where username=? -- 根据 username 查询用户信息
select username, authority from authorities where username=? -- 根据 username 查询用户权限信息
select g.id, g.group_name, ga.authority from groups g, groups_members gm, groups_authorities ga where gm.username=? and g.id=ga.group_id and g.id=gm.group_id -- 根据 username 查询用户组权限

使用默认的 SQL 语句进行查询时意味着我们对应的数据库中应该有对应的表和表结构,Spring Security 为我们提供的默认表的创建脚本如下。

create table users(
      username varchar_ignorecase(50) not null primary key,
      password varchar_ignorecase(50) not null,
      enabled boolean not null);

create table authorities (
      username varchar_ignorecase(50) not null,
      authority varchar_ignorecase(50) not null,
      constraint fk_authorities_users foreign key(username) references users(username));
      create unique index ix_auth_username on authorities (username,authority);

create table groups (
  id bigint generated by default as identity(start with 0) primary key,
  group_name varchar_ignorecase(50) notnull);

create table group_authorities (
  group_id bigint notnull,
  authority varchar(50) notnull,
  constraint fk_group_authorities_group foreign key(group_id) references groups(id));

create table group_members (
  id bigint generated by default as identity(start with 0) primary key,
  username varchar(50) notnull,
  group_id bigint notnull,
  constraint fk_group_members_group foreign key(group_id) references groups(id));

此外,使用 jdbc-user-service 元素时在底层 Spring Security 默认使用的就是 JdbcDaoImpl。

 <security:authentication-manager alias="authenticationManager">
      <security:authentication-provider>
         <!-- 基于 Jdbc 的 UserDetailsService 实现,JdbcDaoImpl -->
         <security:jdbc-user-service data-source-ref="dataSource"/>
      </security:authentication-provider>
 </security:authentication-manager>

1.4.6 InMemoryDaoImpl

InMemoryDaoImpl 主要是测试用的,其只是简单的将用户信息保存在内存中。使用 NameSpace 时,使用 user-service 元素 Spring Security 底层使用的 UserDetailsService 就是 InMemoryDaoImpl。此时,我们可以简单的使用 user 元素来定义一个 UserDetails。

   <security:user-service>
      <security:user name="user" password="user" authorities="ROLE_USER"/>
   </security:user-service>

如上配置表示我们定义了一个用户 user,其对应的密码为 user,拥有 ROLE_USER 的权限。此外,user-service 还支持通过 properties 文件来指定用户信息,如:

  <security:user-service properties="/WEB-INF/config/users.properties"/>

其中属性文件应遵循如下格式:

username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]

所以,对应上面的配置文件,我们的 users.properties 文件的内容应该如下所示:

#username=password,grantedAuthority[,grantedAuthority][,enabled|disabled]
user=user,ROLE_USER

1.4.7 GrantedAuthority

Authentication 的 getAuthorities() 可以返回当前 Authentication 对象拥有的权限,即当前用户拥有的权限。其返回值是一个 GrantedAuthority 类型的数组,每一个 GrantedAuthority 对象代表赋予给当前用户的一种权限。GrantedAuthority 是一个接口,其通常是通过 UserDetailsService 进行加载,然后赋予给 UserDetails 的。

GrantedAuthority 中只定义了一个 getAuthority() 方法,该方法返回一个字符串,表示对应权限的字符串表示,如果对应权限不能用字符串表示,则应当返回 null。

Spring Security 针对 GrantedAuthority 有一个简单实现 SimpleGrantedAuthority。该类只是简单的接收一个表示权限的字符串。Spring Security 内部的所有 AuthenticationProvider 都是使用 SimpleGrantedAuthority 来封装 Authentication 对象。

2.Spring通过XML配置整合Spring Security

2.1 环境准备(都是假数据)

通过idea打开SpringSecurityXMLDemo初始项目,该项目在资料文件中的初始项目文件。
在这里插入图片描述
项目结构:
在这里插入图片描述
部署项目:访问http://localhost:8080/user/index地址,效果如下图所示:
在这里插入图片描述
现在我们需要思考2个问题:

问题1:在生产环境下我们如果不登录后台系统就可以完成这些功能操作吗?

答案显然是否定的,要操作这些功能必须首先登录到系统才可以。(用户登录系统–>认证)

问题2:是不是所有用户,只要登录成功就都可以操作所有功能呢?

答案是否定的,并不是所有的用户都可以操作这些功能。不同的用户可能拥有不同的权限,这就需要进行授权了。(用户登录之后,对每个用户进行授权,通过授权去访问系统中不同的功能–>授权)

2.2 入门案例

在上述环境基础上进行操作。该入门案例的目标: 让用户访问管理后台中的资源的时候,需要输入用户名和密码进行登录。

2.2.1 导入依赖

<!-- spring security安全框架 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>

2.2.2 配置Spring Security Fillter过滤器

在WEB-INF下的web.xml中配置DelegatingFilterProxy过滤器。
DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件),配置代理过滤器主要目的是:用于整合Spring Security安全框架。

<!-- SpringSecurity Filter -->
<!-- DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件) -->
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

2.2.3 编写spring-security.xml配置文件

在spring-security.xml中主要配置Spring Security的拦截规则和认证管理器。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans.xsd
                  http://www.springframework.org/schema/mvc
                  http://www.springframework.org/schema/mvc/spring-mvc.xsd
                  http://www.springframework.org/schema/context
                  http://www.springframework.org/schema/context/spring-context.xsd
                  http://www.springframework.org/schema/security
                  http://www.springframework.org/schema/security/spring-security.xsd">

    <!--
        1.配置拦截器
            http:用于定义权限控制相关的标签
            auto-config:释放自动配置
                true:框架会默认提供一些配置,例如:登录去页面、推出处理等。
                false:序言显示提供登录表单的配置,否则报错
            use-expressions:用于指定intercept-url中的acess属性释放使用表达式
    -->
    <security:http auto-config="true" use-expressions="true">
        <!--
            intercept-url:定义一个拦截规则
                pattern:对哪些url进行权限控制
                access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                请求的用户只需拥有其中的一个角色就能成功访问对应的URL
        -->
        <security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
    </security:http>

    <!--
        2.定义一个认证管理器,并赋予权限
            (1):将用户名和密码:当前用户具有的角色,写死到配置文件中(现在:入门)
                security:user name="admin" :登录名
                authorities="ROLE_ADMIN"   :角色(ROLE_ADMIN),权限
                password="admin"          :密码
            (2):用户名和密码,当前用户具有的角色,从数据库中查询(后续)

            authentication-manager:认证管理器,用于处理认证操作
    -->
    <security:authentication-manager>
        <!--
            authentication-provider:认证提供者,执行具体的认证逻辑
        -->
        <security:authentication-provider>
            <!--
                user-service:用于获取用户信息,提供给authentication-provider进行认证
            -->
            <security:user-service>
                <!--
                    user:定义用户信息,可以指定用户名、密码、角色,
                    后期可以改为从数据库查询用户信息.
                    {noop}:表示当前使用的密码为明文
                -->
                <security:user name="admin" authorities="ROLE_ADMIN" password="{noop}admin"></security:user>
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

测试访问:http://localhost:8080/user/index
在这里插入图片描述
自动调整到登录页面(springSecurity自动提供的)
输入错误用户名和密码
在这里插入图片描述
输入正确用户名和密码(admin/admin),因为 spring security 提供了一套安全机制,登录的时候进行了拦截,参考系统源码PasswordEncoderFactories
登录成功后如图:
在这里插入图片描述

2.3 对入门案例进行改进

前面我们已经完成了Spring Security的入门案例,通过入门案例我们可以看到,Spring Security将我们
项目中的所有资源都保护了起来,要访问这些资源必须要完成认证而且需要具有ROLE_ADMIN角色。
但是入门案例中的使用方法离我们真实生产环境还差很远,还存在如下一些问题:
1、项目中我们将所有的资源(所有请求URL)都保护起来,实际环境下往往有一些资源不需要认证也
可以访问,也就是可以匿名访问。
2、登录页面是由框架生成的,而我们的项目往往会使用自己的登录页面。
3、直接将用户名和密码配置在了配置文件中,而真实生产环境下的用户名和密码往往保存在数据库
中。
4、在配置文件中配置的密码使用明文,这非常不安全,而真实生产环境下密码需要进行加密。
本章节需要对这些问题进行改进。

2.3.1 配置可以匿名访问的资源

语法格式

<security:http security="none" pattern="可访问资源路径" />

在spring-security.xml文件中配置,指定哪些资源可以匿名访问

<!--
	http:用于定义相关权限控制
	指定哪些资源不需要进行权限校验,可以使用通配符
-->
<security:http security="none" pattern="/static/**"></security:http>

直接访问:http://localhost:8080/static/css/bootstrap.min.css
在这里插入图片描述

2.3.2 配置指定登录页面

语法格式

<!--
form-login:定义表单登录信息
-->
<security:form-login login-page="登录页面访问路径"
		username-parameter="登录表单中用户名输入框的name属性值"
		password-parameter="登录表单中密码输入框的name属性值"
		login-processing-url="登录页请求"
		default-target-url="默认登录后的访问路径"
		authentication-failure-url="登录页面访问路径"
/>

<!--
csrf:对应CsrfFilter过滤器
disabled:是否启用CsrfFilter过滤器,
如果使用自定义登录页面需要关闭此项,
否则登录操作会被禁用(403)
-->
<security:csrf disabled="true"></security:csrf>

给入门案例配置登录页面
第一步:在webapp/WEB-INF/templates下创建并编写login.html作为项目的登录页面(初始化项目中已经存在)

<html>
<head>
    <title>登录</title>
</head>
<body>
<h1>自定义登录页面</h1>
<form action="/user/login" method="post">
    username:<input type="text" name="username"><br>
    password:<input type="password" name="password"><br>
    <input type="submit" value="submit">
</form>
</body>
</html>

第二步:修改spring-security.xml文件,指定login.html页面可以匿名访问

<!--
   放行登录页面
-->
<security:http security="none" pattern="/user/tologin"></security:http>

第三步:修改spring-security.xml文件,加入表单登录信息的配置

<!--
   form-login:定义表单登录信息
-->
<security:form-login login-page="/user/tologin"
                     username-parameter="username"
                     password-parameter="password"
                     login-processing-url="/user/login"
                     default-target-url="/user/index"
                     authentication-failure-url="/user/tologin"
/>
<!--
    csrf:对应CsrfFilter过滤器
    disabled:是否启用CsrfFilter过滤器,
	如果使用自定义登录页面需要关闭此项,
	否则登录操作会被禁用(403)
 -->
<security:csrf disabled="true"></security:csrf>

访问:http://localhost:8080/user/index
运行结果
在这里插入图片描述

2.3.3 配置从数据库查询访问数据

如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。
第一步:创建security包,在该包下创建UserService类,实现UserDetailsService接口。

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: TLC
 * @Date: 2022/11/22/11:49
 * @Description:
 */
public class UserDetailServiceImpl implements UserDetailsService {

    // 注入数据从Dao层
    @Autowired
    private UserDao userDao;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        /**
         * 根据名称用户名,查用户信息,并没用真正查寻数据库
         */
        User user = userDao.getByUsername(username);

        if (user == null){
            return null;
        }

        /**
         * 获取密码:这里的密码是在dao层写死。实际上并没用查询真正的数据库。
         */
        String password = "{noop}" + user.getPassword();

        /**
         * 设置权限,这里的权限也是应该从数据库中的权限表中查。
         * 这里直接写死。
         */
        ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("add"));
        authorities.add(new SimpleGrantedAuthority("update"));
        authorities.add(new SimpleGrantedAuthority("delete"));

        /**
         * 返回一个org.springframework.security.core.userdetails.User对象
         * 参数一:用户名
         * 参数二:密码
         * 参数三:权限集合
         */
        return new org.springframework.security.core.userdetails.User(username,password,authorities);
    }
}

第二步:编写Dao层

    public User getByUsername(String username) {
        for (User user : userDaoList) {
            if (user.getUsername().equals(username)){
                return user;
            }
        }
        return null;
    }

第三步:编写spring-security.xml配置文件,认证管理,定义登录账号名和密码,并授予访问的角色、权限authentication-manager:认证管理器,用于处理认证操作

  <!--
        2.定义一个认证管理,替换新的认证管理器。
    -->
    <!--将UserDetailSErviceImpl添加到容器中-->
    <bean id="userDetailService" class="com.tlc.security.UserDetailServiceImpl"></bean>
    <security:authentication-manager>
        <!--
            authentication-provider:认证提供者,执行具体的认证逻辑
        -->
        <security:authentication-provider user-service-ref="userDetailService"></security:authentication-provider>
    </security:authentication-manager>

运行结果
在这里插入图片描述
本章节我们提供了UserService实现类,并且按照框架的要求实现了UserDetailsService接口。在spring配置文件中注册UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。当我们进行登录操作时,spring security框架会调用UserService的loadUserByUsername方法查询用户信息,并根据此方法中提供的密码和用户页面输入的密码进行比对来实现认证操作。

2.3.4 对密码进行加密

前面我们使用的密码都是明文的,这是非常不安全的。一般情况下用户的密码需要进行加密后再保存到
数据库中。
常见的密码加密方式有:
3DES、AES、DES:使用对称加密算法,可以通过解密来还原出原始密码。
MD5、SHA1:使用单向HASH算法,无法通过计算还原出原始密码,但是可以建立彩虹表进行查表破解。
同样的密码值,盐值不同,加密的结果不同。
bcrypt:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题
Spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。
(1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。
(2)密码匹配(matches):用户登录时,密码匹配阶段并没有进行密码解密(因为密码经过Hash处理,是不可逆的),而是使用相同的算法把用户输入的密码进行hash处理,得到密码的hash值,然后将其与从数据库中查询到的密码hash值进行比较。如果两者相同,说明用户输入的密码正确。
这正是为什么处理密码时要用hash算法,而不用加密算法。因为这样处理即使数据库泄漏,黑客也很难破解密码。
加密后的格式一般为:

$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au

加密后字符串的长度为固定的60位。其中:

$是分割符,无意义;
2a是bcrypt加密版本号;
10是cost的值;
而后的前22位是salt值;
再然后的字符串就是密码的密文了。

实现步骤:
第一步:在spring-security.xml文件中指定密码加密对象,并在认证管理器中配置加密策略

<!--配置密码加密对象-->
<bean id="passwordEncoder"
      class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"
      />
<!--认证管理器,用于处理认证操作-->
<bean id="userDetailService" class="com.tlc.security.UserDetailServiceImpl"></bean>
<security:authentication-manager>
    <!--认证提供者,执行具体的认证逻辑-->
    <security:authentication-provider user-service-ref="userDetailService">
        <!--指定密码加密策略-->
        <security:password-encoder ref="passwordEncoder" />
    </security:authentication-provider>
</security:authentication-manager>
<!--开启spring注解使用-->
<context:annotation-config></context:annotation-config>

测试加密技术

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-security.xml")
public class PasswordEncoderTest {

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
    * @Description: 加密测试
    * @Author: TCL
    * @Date: 2022/11/22
    * @Time: 16:08
    */
    @Test
    public void test01(){
        String password = passwordEncoder.encode("123456");
        System.out.println("password = " + password);
        // 结果:password = $2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au
    }

    /**
    * @Description: 解密测试
    * @Author: TCL
    * @Date: 2022/11/22
    * @Time: 16:15
    */
    @Test
    public void test02(){
        String password = "$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au";
        boolean matches = passwordEncoder.matches("123456", password);
        System.out.println("matches = " + matches);
        // 结果:matches = true
    }
}

第二步:修改UserDetailServiceImpl类,添加加密、解密操作

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: TLC
 * @Date: 2022/11/22/11:49
 * @Description:
 */
public class UserDetailServiceImpl implements UserDetailsService {

    // 注入数据从Dao层
    @Autowired
    private UserDao userDao;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        /**
         * 根据名称用户名,查用户信息,并没用真正查寻数据库
         */
        User user = userDao.getByUsername(username);

        if (user == null){
            return null;
        }

        /**
         * 获取密码:这里的密码是在dao层写死。实际上并没用查询真正的数据库。
         */
		// String password = "{noop}" + user.getPassword();

        String password = user.getPassword();  // 去掉明文操作,security会自动进行密码匹配
        /**
         * 设置权限,这里的权限也是应该从数据库中的权限表中查。
         * 这里直接写死。
         */
        ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("update"));
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));


        /**
         * 返回一个org.springframework.security.core.userdetails.User对象
         * 参数一:用户名
         * 参数二:密码
         * 参数三:权限集合
         */
        return new org.springframework.security.core.userdetails.User(username,password,authorities);
    }
}

第三步:修改UserDaoImpl类,将写死的密码换成加密后的密码。直接将上述测试类中的加密密码复制进去

   public UserDaoImpl() {
        userDaoList.add(new User(1001,"张三","男",11,"beijing","1111@qq.com","1111@qq.com","zhangsan","1234455666","$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au"));
        userDaoList.add(new User(1002,"李四","男",16,"shangdong","222@qq.com","1111@qq.com","lisi","1234455666","$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au"));
        userDaoList.add(new User(1003,"王五","女",15,"wuhan","ssss@qq.com","1111@qq.com","wangwu","1234455666","$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au"));
        userDaoList.add(new User(1004,"赵六","男",13,"shanghai","aaaa@qq.com","1111@qq.com","zhaoliu","1234455666","$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au"));
        userDaoList.add(new User(1005,"涨幅","女",11,"changsha","vvv@qq.com","1111@qq.com","zhangfu","1234455666","$2a$10$ZlLdmlAj8LFlKuOmgV8vVOnQC.5R9HpzPMyHNsg/ZM5EmO/YzV1au"));
    }

第三步:启动项目测试
访问:http://localhost:8080/user/index
在这里插入图片描述
登录成功
在这里插入图片描述

2.3.5 配置多种权限校验规则

修改spring-security.xml文件:
前提:<security:http auto-config=“true” use-expressions=“true”>
语法格式

<!--在http标签中配置权限校验规则-->
<security:intercept-url pattern="页面访问路径" access="权限规则" />

为入门案例配置多种权限规则,修改spring-security.xml文件:

<!--
    intercept-url:定义一个拦截规则
        pattern:对哪些url进行权限控制
        access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                请求的用户只需拥有其中的一个角色就能成功访问对应的URL
-->
<!--登录后可以访问所有路径-->
<!--<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />-->
<security:intercept-url pattern="/user/list" access="hasRole('ROLE_ADMIN')" />
<!--只要认证通过就可以访问-->
<security:intercept-url pattern="/user/index" access="isAuthenticated()" />
<!--拥有add权限就可以访问delete.html页面-->
<security:intercept-url pattern="/user/todelete" access="hasAuthority('delete')" />
<!--拥有add权限就可以访问update.html页面-->
<security:intercept-url pattern="/user/toupdate" access="hasAuthority('update')" />

运行结果:
在这里插入图片描述
点击删除时:
在这里插入图片描述

2.3.6 退出登录

用户完成登录后Spring Security框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。那用户如何退出登录呢?我们可以在spring-security.xml文件中的http标签中进行如下配置:

<!--
  logout:退出登录
  logout-url:退出登录操作对应的请求路径
  logout-success-url:退出登录后的跳转页面
  invalidate-session="true" 默认为true,用户在退出后Http session失效
-->
<security:logout logout-url="/user/logout" logout-success-url="/user/login" invalidate-session="true"/>

通过上面的配置可以发现,如果用户要退出登录,只需要请求/user/logout这个URL地址就可以,同时会将当前session失效,最后页面会跳转到login.html页面。

2.4 小结

1.配置静态资源方式

<security:http security="none" pattern="可访问资源路径" />

2.配置指定登录页面

<security:form-login login-page="登录页面访问路径"
		username-parameter="登录表单中用户名输入框的name属性值"
		password-parameter="登录表单中密码输入框的name属性值"
		login-processing-url="登录页请求"
		default-target-url="默认登录后的访问路径"
		authentication-failure-url="登录页面访问路径"
/>

<security:csrf disabled="true"></security:csrf>

3.对密码进行加密

<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>

4.从数据库查询用户信息

<security:authentication-manager>
    <security:authentication-provider user-service-ref="userService">
        <security:password-encoder ref="bCryptPasswordEncoder"></security:password-encoder>
    </security:authentication-provider>
</security:authentication-manager>

5.配置多种校验规则(对访问的页面做权限控制)

<security:intercept-url pattern="页面访问路径" access="权限"></security:intercept-url>

6.推出登录

<security:logout logout-url="推出请求路径" logout-success-url="推出成功后跳转的页面" invalidate-session="true"></security:logo>

3.Spring注解整合Spring Security

3.0 资源准备,导入初始化项目

同上一样都有初始化项目,直接导入即可
在这里插入图片描述

在这里插入图片描述
访问:http://localhost:8080/user/index

在这里插入图片描述
在这里插入图片描述

3.1 入门案例

目标: 让用户访问管理后台中的资源的时候,需要输入用户名和密码进行登录

3.1.1 导入依赖

<!-- spring security安全框架 -->
<spring.security.version>5.2.8.RELEASE</spring.security.version>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring.security.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring.security.version}</version>
</dependency>

3.1.2 配置Spring Security Fillter

<!-- SpringSecurity Filter -->
<!-- DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件) -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3.1.3 配置Spring Security

通过java注解配置类来对Spring Security进行配置。在SpringSecurityAnnotationsDemo项目中创建com.tlc.config.WebSecurityConfig类,继承WebSecurityConfigurerAdapter类

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: TLC
 * @Date: 2022/11/23/11:18
 * @Description: Spring Security配置类
 */
@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {

}

3.1.3 测试

仅需三个步骤,我们就已经集成好了Spring Security,其他的事情就可以交给Spring Security为我们处理。

启动项目,访问:http://localhost:8080/user/index
在这里插入图片描述
所有资源访问受限(包括静态资源)

url自动跳转到了一个默认的登录页面(框架自带的),我们目前没有定义login页面及login controller方法。

但是当前没有账号啊!下面我们测试一个最简单的内存分配用户名密码。

3.1.4 配置内存用户名密码

在操作类:WebSecurityConfig
重写configure(AuthenticationManagerBuilder auth)方法

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("root")
                .password("{noop}root")// {noop}不使用密码加密技术
                .roles(""); // 权限
    }
}

请求:http://localhost:8080/
在这里插入图片描述
如果报错:springsecurity There is no PasswordEncoder mapped for the id "null"
在这里插入图片描述
解决方式:
1.在密码前加一个{noop}不使用密码加密技术。
2.配置密码加密技术 PasswordEncoder。

3.1.5 配置密码加密技术

在操作类:WebSecurityConfig中配置PasswordEncoder。将该对象注入的IOC容器中,重新编写configure方法。

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {

    /**
    * @Description: 配置密码加密
    * @Author: TCL
    * @Date: 2022/11/23
    * @Time: 11:53
    */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();//指定解码器,会自动解码
    }

    /**
    * @Description: 配置认证管理相关参数
    * @Author: TCL
    * @Date: 2022/11/23
    * @Time: 11:52
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("root")
                .password(new BCryptPasswordEncoder().encode("root"))// 对密码进行加密
                .roles(""); // 权限
    }
}

登录成功。
在这里插入图片描述

3.2 对入门案例进行改进

3.2.1 设置允许iframe嵌套显示

在WebSecurityConfig配置类中的configure(HttpSecurity http)方法中配置允许iframe嵌套显示:

/**
 * @Description: 用于定义权限控制相关的方法
 * @Author: TCL
 * @Date: 2022/11/23
 * @Time: 13:59
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    //必须调用父类的方法,否则就不需要认证即可访问
    super.configure(http);

    //允许iframe嵌套显示
    http.headers().frameOptions().disable();
}

3.2.2 配置资源放行

在WebSecurityConfig配置类中的configure(HttpSecurity http) 方法中配置静态资源放行:
语法格式:

// 设置资源放行
http.authorizeRequests()
    //放行静态资源, login.html页面
    .antMatchers("资源路径1","资源路径2",...,"资源路径n")
    .permitAll()
    //其他资源登录后即可访问
    .anyRequest()
    .authenticated();

配置静态资源的放行

// 设置资源放行,同时注释掉前面的super.configure(http);
http.authorizeRequests()
    //放行静态资源
    .antMatchers("/static/**").permitAll()
    //其他资源登录后即可访问
    .anyRequest().authenticated();

运行结果
在这里插入图片描述
注意事项:
如果启动报错IllegalStateException: Can't configure antMatchers after anyRequest是应为你的configure(HttpSecurity http)方法中super.configure(http);没有被注释掉导致antMatchers after anyRequest重复定义。

3.2.3 配置自定义登录界面

在WebSecurityConfig配置类中的configure(HttpSecurity http) 方法中配置自定义登录界面:
语法格式

// 设置登录
http.formLogin()
    //一旦用户的请求没有权限就跳转到这个页面
    .loginPage("登录页面路径")
    //登录表单form中action的地址,也就是处理认证请求的路径
    .loginProcessingUrl("action的地址")
    //登录表单form中用户名输入框input的name名,不修改的话默认是username
    .usernameParameter("username")
    //form中密码输入框input的name名,不修改的话默认是password
    .passwordParameter("password")
    //登录成功跳转路径
    .defaultSuccessUrl("登录成功跳转路径")
    //登录失败转发路径
    .failureUrl("登录失败转发路径");

配置自定义登录界面

//设置资源放行
http.authorizeRequests()
    //放行静态资源, login.html页面
    .antMatchers("/static/**","/user/tologin").permitAll()
    //其他资源登录后即可访问
    .anyRequest().authenticated();
// 设置登录
http.formLogin()
    //一旦用户的请求没有权限就跳转到这个页面
    .loginPage("/user/tologin")
    //登录表单form中action的地址,也就是处理认证请求的路径
    .loginProcessingUrl("/user/login")
    //登录表单form中用户名输入框input的name名,不修改的话默认是username
    .usernameParameter("username")
    //form中密码输入框input的name名,不修改的话默认是password
    .passwordParameter("password")
    //登录成功跳转路径
    .defaultSuccessUrl("/user/index")
    //登录失败转发路径
    .failureUrl("/user/tologin");
// 禁用csrf
http.csrf().disable();

运行程序测试
访问:http://localhost:8080/user/index
在这里插入图片描述
禁用csrf
csrf :Cross-site request forgery , 跨站请求伪造。
image-20220823212103247
一定要配置http.csrf().disable();否则会报出如下错误
在这里插入图片描述

3.2.4 配置从数据库查询用户访问数据

第一步:我们在SpringSecurityAnnotationsDemo项目中创建com.tlc.config.UserDetailsServiceImpl实现类,实现UserDetailsService接口

@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1.根据用户名查询用户信息
        User user = userDao.getByUsername(username);

        // 2.判断用户是否存在
        if (user == null){
            return null;
        }

        // 3.获取密码
        String password = user.getPassword();

        // 4.添加权限,这里时写死的,可以从数据库中查询
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        authorities.add(new SimpleGrantedAuthority("update"));

        return new org.springframework.security.core.userdetails.User(username,password,authorities);
    }
}

第二步:注释掉或者删除WebSecurityConfig中认证管理方法configure(AuthenticationManagerBuilder auth)
在这里插入图片描述
第三步:测试
访问:http://localhost:8080/user/index
在这里插入图片描述
点击登录,结果
在这里插入图片描述

3.2.4 注解配置访问权限

**目标:**给各个Controller的方法指定对应的操作权限。
语法格式

//  1.在com.tlc.config.WebSecurityConfig配置类上添加如下注解,开启全局方法权限
@EnableGlobalMethodSecurity(prePostEnabled = true)
// 2.给Controller方法添加权限注解
 @PreAuthorize("hasAnyAuthority('权限名称')")

代码示例
1.在com.tlc.config.WebSecurityConfig配置类上添加如下注解,开启全局方法权限。
在这里插入图片描述

2.给Controller方法添加权限注解

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/index")
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    public String index(){
        return "index";
    }

    @GetMapping("/tologin")
    public String tologin(){
        return "login";
    }

    @GetMapping("/login")
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    public String login(User user){
        System.out.println("user = " + user);
        return "index";
    }
    @GetMapping("/list")
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    public String list(Model model){
        // 创建假数据
        List<User> all = userService.findAll();
        model.addAttribute("userList",all);
        return "list";
    }

    @GetMapping("/toupdate")
    @PreAuthorize("hasAnyAuthority('update')")
    public String toUpdate(){
        return "update";
    }

    @GetMapping("/todelete")
    @PreAuthorize("hasAnyAuthority('delete')")
    public String toDelete(){
        return "delete";
    }
}

测试程序
我们在UserDetailsServiceImpl类中配置了两个权限
在这里插入图片描述
没有配置delete权限,所以该用户没有删除权限。
我们直接来测试修改按钮与删除按钮。
点击修改按钮:
在这里插入图片描述
点击删除按钮
在这里插入图片描述

3.2.4 自定义权限不足页面

在这里插入图片描述
上面这样提示很不友好,我们自定义提示页面
在项目中创建templates/auth.html

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body style="position: relative;">
        <div style="text-align:center;margin-top: 100px;font-size: 20px;">
            <strong>没有权限</strong>
        </div>
    </body>
</html>

在UserController中设置跳转页面

	@GetMapping("/auth")
    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    public String auth(){
        return "auth";
    }

创建com.tlc.config.MyAccessDeniedHandler实现AccessDeniedHandler接口

public class MyAccessDeniedHandler implements AccessDeniedHandler {


    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.sendRedirect("/auth");
    }
}

在Spring Security配置类中配置AtguiguAccessDeniedHandler

// 自定义403页面
http.exceptionHandling()
    .accessDeniedHandler(new MyAccessDeniedHandler());

测试程序
在这里插入图片描述

3.2.4 配置注销功能

在WebSecurityConfig配置类中的configure(HttpSecurity http) 方法中配置注销功能
语法格式

   // 设置注销
    http.logout()
        .logoutUrl("注销请求路径")
        .logoutSuccessUrl("注销成功后的访问路径");

代码示例

   // 设置注销
    http.logout()
        .logoutUrl("/user/logout")
        .logoutSuccessUrl("/user/tologin");

4.源代码与原始项目资源

源代码与原始项目资源
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/29422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

qemu 线程 vhost

[rootlocalhost cloud_images]# lsmod | grep vhost_net vhost_net 262144 0 vhost 262144 1 vhost_net tap 262144 1 vhost_net tun 262144 2 vhost_net [rootlocalhost cloud_images]#vhost-net网卡的…

[附源码]SSM计算机毕业设计基于实时定位的超市配送业务管理JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

低碳世界杂志低碳世界杂志社低碳世界编辑部2022年第7期目录

节能环保 挥发性有机物的全厂控制措施 董少军; 1-3 《低碳世界》投稿&#xff1a;cnqikantg126.com 佛山市市政排水管网通沟污泥处理处置工艺设计 张红; 4-6 “双碳”背景下海岸带地区适应气候变化评估与对策研究 王鸿浩;邬乐雅;吴晓晨;张丽佳;黄婧蓼琦;胡斐…

【毕业设计】基于情感分析的网络舆情热点分析系统

文章目录0 前言1 课题背景2 数据处理3 文本情感分析3.1 情感分析-词库搭建3.2 文本情感分析实现3.3 建立情感倾向性分析模型4 数据可视化工具4.1 django框架介绍4.2 ECharts5 Django使用echarts进行可视化展示5.1 修改setting.py连接mysql数据库5.2 导入数据5.3 使用echarts可视…

Java编程实战9:统计只差一个字符的子串数目

目录统计只差一个字符的子串数目题目示例 1示例 2示例 3示例 4提示解答解题思路完整代码统计只差一个字符的子串数目 题目 给你两个字符串 s 和 t &#xff0c;请你找出 s 中的非空子串的数目&#xff0c;这些子串满足替换 一个不同字符 以后&#xff0c;是 t 串的子串。换言…

实验1:Arduino的nRF24L01单向收发实验

实验结果: 发送端发送“Hello World”,发送成功打印1 接收端接收到“Hello World”,在串口中打印出“Hello World” OK,直接讲代码 因为我用的Arduino和nRF24L01 是用扩展板连接的,而我的嵌入式硬件开发,也就是AD实在不擅长,就不解释了 其中(9,10)CE,CSN 那么我…

通关算法题之 ⌈数组⌋ 下

二分搜索 704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 输入: nums [-1,0,3,5,9,12], target 9 输出…

【后台技术】异步编程指北,问题和重点

导语&#xff1a;同步、异步&#xff0c;并发、并行、串行&#xff0c;这些名词在我们的开发中会经常遇到&#xff0c;这里对异步编程做一个详细的归纳总结&#xff0c;希望可以对这方面的开发有一些帮助。 内容大纲&#xff1a; 1、几个名词的概念 多任务的时候&#xff0c;…

jmeter压力测试报告

出版社智能智造测试报告 &#xff08;二期版本&#xff09; 2022年11月 目 录 1. 测试背景 1.1. 项目背景 1.2. 测试目的 1.3. 测试时间 1.4. 测试资源 1.5. 参考资料 2. 测试范围 3. 性能需求指标 3.1. 业界指标 4. 测试工具 5. 测试环境 5.1. 阿里云测试环境软…

搭建Gitlab

Gitlab是目前被广泛使用的基于git的开源代码管理平台, 基于Ruby on Rails构建, 主要针对软件开发过程中产生的代码和文档进行管理 一、搭建gitlab服务器&#xff0c;统一管理软件项目 第一步&#xff1a; 创建一个4G内存的虚拟机&#xff0c;否则很容易启动不了&#xff0c;报…

(附源码)计算机毕业设计Java“华商转转”平台的设计和实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven Vue 等等组成&#xff0c;B/…

python常用进制转换

整数之间转换 # 1. 10 -> 16 hex(number)# 2. 10 -> 2 bin(number)# 3. 10 -> 8 oct(number)# 4. x进制 -> 10 int(Union[str, bytes, bytearray],basex) ------------------ print(int("0x16", base16)) // 22字符串转整数 # 10进制 val int(10) pri…

SPP-学习笔记

Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition SPP提出的原因 1、现有的深度卷积神经网络(spp出现之前的)需要固定大小的输入图像(例如224224)。往往需要对图片裁剪或者resize&#xff0c;导致图片信息损失或者产生几何畸变。这样可能会损…

奥比中光亮相全球1024开发者节,与科大讯飞达成战略合作

作者 | 奥比中光 编辑 | 3D视觉开发者社区 11月17日-23日&#xff0c;第五届世界声博会暨2022科大讯飞全球1024开发者节在安徽合肥举办&#xff0c;奥比中光作为3D视觉感知头部企业参展&#xff0c;并与科大讯飞达成战略合作&#xff0c;共同赋能3D视觉行业应用开发。 本次参…

如何利用现代工具来管理多项目

多项目管理是如今现代企业管理时常常遇到的一个难题。不同于单项目管理&#xff0c;多个项目同时进行管理要复杂得很多。而单纯的手工管理方式已经满足不了多管理的复杂需求&#xff0c;项目负责人想要保障在预定的时间内&#xff0c;又快又好地完成整体项目&#xff0c;便需要…

工厂模式解耦-交由spring来完成

上面两个小节一直在谈论解耦&#xff0c;从入门的多例到升级的单例BeanFactory工厂类是我们自己手工写的。 BeanFactory主要做了3件事&#xff1a; 1.读取配置文件&#xff08;可以是properties或xml类型的文件&#xff0c;示例中用的是properties文件&#xff09; 2.获取类…

OC RSA加密解密

好久好久没有更新了。。。你们等的急不急。。这不&#xff0c;我就姗姗来迟了。。。本文重点讲解一下iOS系统下的RSA加密解密问题。 一般为了安全&#xff0c;私钥是不会给前端暴露出来 的&#xff0c;只会通过私钥生成一个公开的公钥提供给外部对数据进行加密。将加密后的数据…

残差网络ResNet解读

一、残差网络的定义 残差网络的核心是解决增加深度带来的退化问题&#xff0c;这样能够通过单纯增加网络深度来提高网络性能。 残差单元以短连接的形式&#xff0c;将单元的输入直接与单元输出加在一起&#xff0c;然后再进行激活。 Weight为抽取特征的网络层 Addition时xl和…

RK3568平台开发系列讲解(视频篇)摄像头采集视频的相关配置

🚀返回专栏总目录 文章目录 一、权限配置二、配置摄像头2.1、打开摄像头2.2、预览格式2.3、预览尺寸沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Android 平台的摄像头的采集核心部分都是在 Native 层构建的,所以这就会涉及 JNI 层的一些转换操作。 一、权限配置…

Linux | 进程间通信 | 匿名管道 | 命名管道 | 模拟代码实现进程通信 |

文章目录进程通信的意义匿名管道通信原理管道的访问控制进程控制管道的特点命名管道进程通信的意义 之前聊进程时&#xff0c;讲过一个性质&#xff0c;即进程具有独立性&#xff0c;两个进程之间的交互频率是比较少的。就连父子进程也只是共享代码&#xff0c;修改父子进程中…