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

news2024/11/16 9:20:23

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

文章目录

  • 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/31637.html

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

相关文章

既然有了ES,为何还用ClickHouse——从原理万字总结ClickHouse为何这么快

通过了解 CH 的几大特性了解千亿级企业 ClickHouse 实时处理引擎架构设计、核心技术设计、运行机理全流程。 文章目录1 初始 ClickHouse1.1 什么是 ClickHouse1.2 ClickHouse 的优缺点1.3 谁在用 ClickHouse3 数据引擎3.1 库引擎3.2 表引擎3.3 MergeTree 引擎4 工作原理4.1 数据…

浙大MBA经验分享:在工作生活的缝隙中奋勇上岸

非常高兴可以为大家分享我的浙大MBA备考经验&#xff01;首先针对我的背景简要介绍一下&#xff0c;我本科毕业于省内的普通大学浙江理工大学&#xff0c;学的是设计专业&#xff0c;就业于一家外企公司。在2022年的联考中获得了综合133&#xff0c;英语75&#xff0c;总分是20…

一个简单的音乐网站设计与实现(HTML+CSS)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 音乐网页设计 | 仿网易云音乐 | 各大音乐官网网页 | 明星音乐演唱会主题 | 爵士乐音乐 | 民族音乐 | 等网站的设计与制作 | HTML期末大学生网页设计作…

常见集群算法解析

Gossip协议 Gossip协议简介 定义 Gossip protocol&#xff0c;又叫 Epidemic Protocol &#xff08;流行病协议&#xff09;&#xff0c;也叫“流言算法” 、 “疫情传播算法”等。其名称已经形象的说明了算法的原理和工作方式 应用场景 分布式网络&#xff0c;无集中管理节…

同花顺l2数据接口的委托队列是什么?

我们都知道股票交易时有买方也有卖方&#xff0c;“买一”通俗理解就是此刻“买”价最“高”即第一的委托集合&#xff0c;卖一则是“卖”价最低的委托集合。 “一”并非指一笔委托或一手股票&#xff0c;它的背后是有多笔报价相同的买入或卖出委托组成&#xff0c;可能是主力…

cesium火箭发射,模型控制,模型动画,模型移动

起因&#xff1a;最近想做模型的动画&#xff0c;结果上网查资料&#xff0c;看到网上好多对于模型控制的文章都有限制。决定还是自己研究下。欢迎大家一起探讨&#xff0c;评论留言。 效果 火箭全部代码在最后 起步 模型控制&#xff0c;第一步当然是需要一个合适的模型&#…

链动2+1模式是否合法合规?它涉及多级传销吗?

根据国家《禁止传销条例》第2条规定&#xff0c;传销是指组织者或者经营者发展人员&#xff0c;通过对被发展人员以其直接或者间接发展的人员数量或者销售业绩为依据计算和给付报酬&#xff0c;或者要求被发展人员以交纳一定费用为条件取得加入资格等方式牟取非法利益&#xff…

【多线程】Thread的interrupt()

一、前言 如果子线程执行完毕终止状态&#xff0c;主线程再去调用interrupt()有什么效果&#xff1f;如果子线程还在执行过程中&#xff0c;主线程调用interrupt()有什么结果&#xff1f; 二、模拟实验 1、模拟子线程执行完毕再调用interrupt() ​ public class Test {publi…

2023年天津美术学院专升本报名考试须知

天津美术学院2023年高职升本科报考须知&#xff08;一&#xff09;专业考试1.报名方法&#xff1a; ①网上报名及缴费&#xff1a; 我校采取网上报名的方式,考生于2022年12月份&#xff08;具体时间关注公众号“高职接本科”另行公告&#xff09;(10:00-22:00)登录网站(网址&am…

web前端期末大作业 html+css学生心理 7页主题网页设计

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 家 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&#xff1a;样式 在操作方面上运用了html5和css3&#xff0c; 采用了divcss结构、表单、…

Oculus Deeplink

DeepLink 初始化 platform sdk 后设置 应用启动回调判断应用打开的方式发起应用跳转 接收应用跳转 GroupPresence 本文档基于 GroupPresenceSample 脚本逻辑编写&#xff0c;展示通过群组状态发起用户邀请&#xff0c;以及响应对应回调。参考 Oculus 工程 SharedSpaces 使…

【安装教程】vscode安装教程(超详细)

Visual Studio Code&#xff08;简称 VSCode&#xff09;是一款由微软开发且跨平台的免费源代码编辑器。该软件支持语法高亮、代码自动补全、代码重构功能&#xff0c;并且内置了命令行工具和 Git版本控制系统。用户可以更改主题和键盘快捷方式实现个性化设置&#xff0c;也可以…

想裁剪视频时长,用电脑怎么裁剪视频时长

大家有没有碰到过这种一种情况&#xff0c;就是我们在社交平台上发布视频时&#xff0c;会因为视频时长过长这个问题而导致视频发布失败。那我们要怎么处理这个问题呢&#xff1f;其实我们可以使用一些剪辑软件&#xff0c;将视频裁剪&#xff0c;只截取视频里面较为精彩的部分…

hadoop集群安装(三):创建同步工具并安装jdk

文章目录说明分享环境创建同步工具编写脚本设置为系统命令安装jdk总结说明 搭建好集群虚拟机&#xff0c;新建同步工具并安装jdk&#xff0c;同步工具方便管理集群&#xff0c;某些操作和一条命令&#xff0c;同步所有节点&#xff0c;增加集群操作效率。 分享 大数据博客列表…

组件之间通过bus中央事件总线进行通信

案例完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widt…

66. SAP ABAP Function Module 的动态调用方式使用方式介绍

在本教程前面的步骤 7,我们介绍了 ABAP Function Module 的基本使用方法: 7. ABAP function module 的使用最近我的知识星球有朋友提问: 大佬,我想问一下动态获取到物料主数据的字段名之后,如何将获取到的字段名去与bapi中的字段名对应起来从而去修改物料主数据中对应的字…

多维时序 | MATLAB实现CNN-GRU多变量时间序列预测

多维时序 | MATLAB实现CNN-GRU多变量时间序列预测 目录多维时序 | MATLAB实现CNN-GRU多变量时间序列预测基本介绍模型特点程序设计学习总结参考资料基本介绍 本次运行测试环境MATLAB2020b&#xff0c;MATLAB实现CNN-GRU多变量时间序列预测&#xff0c;卷积门控循环单元。 模型特…

ComponentAce FlexCompress强大功能

ComponentAce FlexCompress强大功能 FlexCompress是一个高速压缩库&#xff0c;旨在为您的应用程序提供归档功能。此解决方案提供了灵活的压缩和强大的加密算法&#xff0c;使您可以快速轻松地将归档或备份功能集成到程序中。 FlexCompress包括我们新的独特技术&#xff0c;即交…

分片架构设计技巧

Elasticsearch集群设计技巧 ES的基本架构 节点可以配置为不同角色&#xff0c;通过选举Master管理集群Coordinating&#xff1a;协调节点&#xff1b;Master&#xff1a;管理节点&#xff1b;Data&#xff1a;数据存储节点 数据是按照索引分片的&#xff0c;而不是按照节点分片…

在C#方法中 out、ref、in、params 关键字的用法

out&#xff1a;关键字&#xff1a; 指定的参数在进入函数时会清空自己&#xff0c;必须在函数内部赋初值 ref关键字&#xff1a; 指定的参数必须在进入函数时赋初值&#xff0c;在函数内部可以重新赋值 In关键字&#xff1a; 指定的参数必须在进入函数时赋初值&#xff0c;…