一、Spring Security简介

Java中的安全框架通常是指解决Web应用安全问题的框架。自行实现Web应用的安全性并不容易,需要考虑不同的认证和授权机制、网络关键数据传输加密等多方面的问题。
Java中常用的安全框架有Spring Security和Shiro,这两个安全框架都提供了强大功能,可以很容易实现Web应用的很多安全防护。下面对这两个安全框架的特点进行讲解
Spring Security是Spring生态系统中重要的一员,是一个基于AOP思想和Servlet过滤器实现的安全框架,它提供了完善的认证机制和方法级的授权功能,是一款非常优秀的权限管理框架。

在进行安全管理的过程中都涉及权限管理的两个重要概念:Authentication(认证)
和Authorization(授权)。权限管理是指根据系统设置的安全规则或者安全策略,用户可以访问且只能访问自己被授权的资源。
实现权限管理通常需要三个对象,分别为用户、角色、权限,这三个对象的说明如下。

  • 用户:主要包含用户名、密码和当前用户的角色信息,可以实现认证操作。
  • 角色:主要包含角色名称、角色描述和当前角色拥有的权限信息,可以实现授权操作。
  • 权限:权限也可以称为菜单,主要包含当前权限名称、url地址等信息,可以实现动态展示菜单。

二、结构总览

在这里插入图片描述

  • SpringSecurity安全管理的实现主要是由过滤器链中一系列过滤器相互配合完成,下面对过滤器链中主要的几个过滤器及其作用分别进行说明。
  • SecurityContextPersistenceFilter:是整个拦截过程的入口和出口,也就是第一个和最后一个拦截器。
  • UsernamePasswordAuthenticationFilter:用于处理来自表单提交的认证,提交的表单必须提供对应的用户名和密码。
  • FilterSecurityInterceptor:用于保护Web资源,获取所配置资源访问的授权信息。
  • CsrfFilter:SpringSecurity会对所有Post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错,起到防止csrf攻击的效果。
  • ExceptionTranslationFilter:能够捕获来自FilterChain所有的异常,并进行处理。
  • DefaultLoginPageGeneratingFilter:如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。

三、认证流程

在这里插入图片描述
Spring Security的认证流程进行详细介绍。
① 用户提交用户名和密码进行认证请求后,被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter过滤器获取到,将用户名和密码封装到UsernamePasswordAuthenticationToken对象中,该对象为Authentication的实现类。

② 过滤器将封装用户名和密码的Authentication对象提交至AuthenticationManager(认证管理器)进行认证。

③ AuthenticationManager根据当前的认证类型进行认证,而AuthenticationManager是一个包装类认证时会根据提交的用户信息最终返回一个SpringSecurity的UserDetails对象,如果返回的UserDetails对象为空,则说明认证失败,抛出异常。

  1. AuthenticationManager 协调认证

    AuthenticationManager 是 Spring Security 中认证的核心接口,它定义了
    authenticate(Authentication authentication) 方法,用于对用户的认证请求进行处理。当接收到一个
    Authentication 对象时,AuthenticationManager 会将其传递给一个或多个
    AuthenticationProvider 进行验证。

  2. AuthenticationProvider 验证用户

    DaoAuthenticationProvider 是 AuthenticationProvider
    的一个常用实现,它负责验证用户的用户名和密码。在验证过程中,它会调用 UserDetailsService 来获取用户的详细信息。

  3. UserDetailsService 查询数据库

    UserDetailsService 是一个接口,它定义了一个 loadUserByUsername(String username)
    方法,用于根据用户名从数据库或其他数据源中加载用户信息。开发人员需要实现这个接口,并在其中编写具体的数据库查询逻辑,根据查询的结果处理封装到UserDetails对象并返回。

④ 如果返回的UserDetails对象不为空,则返回UserDetails对象,最后AuthenticationManager 认证管理器返回一个被填充满了信息的Authentication 实例,包括权限信息, 身份信息,细节信息,但密码通常会被移除。

⑤ SecurityContextHolder安全上下文容器存放填充了信息(包括用户的权限信息不包括用户密码)的Authentication,认证成功后通过 SecurityContextHolder.getContext().setAuthentication()方法,将Authentication设置到其中。后面的权限管理将通过SecurityContextHolder(SecurityContextHolder 是一个工具类,主要负责管理 SecurityContext 的存储和访问,SecurityContextHolder 提供了一种机制来存储和检索 SecurityContext 实例。它使用 ThreadLocal 来存储 SecurityContext,这意味着每个线程都有自己独立的 SecurityContext 副本,保证了线程安全。)获取SecurityContext中存储的Authentication。

四、自定义认证方式

尽管项目启动时,Spring Security会提供了默认的用户信息,可以快速认证和启动,但大多数应用程序都希望使用自定义的用户认证。对于自定义用户认证,Spring Security提供了多种认证方式,常用的有In-Memory Authentication(内存身份认证)、JDBC Authentication(JDBC身份认证)和UserDetailsService(身份详情服务)。下面对Spring Security的这三种自定义身份认证进行详细讲解。

内存身份认证

以内存身份认证时,需要在Spring Security的相关组件中进行指定当前认证方式为内存身份认证。Spring Security 5.7.1开始Spring Security将WebSecurityConfigurerAdapter类标注为过时,推荐直接声明配置类,在配置类中直接定义组件的信息。
我使用Spring Boot 2.7.6,其对应的Spring Security版本为5.7.5。自定义内存身份认证时,可以通过InMemoryUserDetailsManager类实现,InMemoryUserDetailsManager是UserDetailsService的一个实现类,方便在内存中创建一个用户。通过修改UserDetailsService的实现类来更换认证方式,如何更换UserDetailsService的实现类呢?在WebSecurityConfig配置类userDetailsService()方法,将该方法创建的实例对象修改为InMemoryUserDetailsManager实例,在该实例中指定该实例的认证信息,并存入在Spring容器中。即可修改UserDetailsService的一个实现类。

示例

import  org.springframework.context.annotation.Bean;
import  org.springframework.context.annotation.Configuration;
import  org.springframework.security.core.userdetails.User;
import  org.springframework.security.core.userdetails.UserDetailsService;
import  org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public  class  WebSecurityConfig {
	@Bean
	public  UserDetailsService userDetailsService() {
	InMemoryUserDetailsManager users = new  InMemoryUserDetailsManager();
	users.createUser(User.withUsername("zhangsan")
	.password("{noop}1234")
	.roles("ADMIN")
	.build());
	return  users;
	}
}

进行自定义用户认证时,需要注意以下几个问题。

  • 提交认证时会对输入的密码使用密码编译器进行加密并与正确的密码进行校验。如果不想要对输入的密码进行加密,需要在密码前对使用{noop}进行标注。
  • 从Spring Security 5开始,自定义用户认证如果没有设置密码编码器,也没有在密码前使用{noop}进行标注,会认证失败。
  • 自定义用户认证时,可以定义用户角色roles,也可以定义用户权限authorities,在进行赋值时,权限通常是在角色值的基础上添加“ROLE_”前缀。
  • 自定义用户认证时,可以为某个用户一次指定多个角色或权限。

JDBC身份认证

JDBC身份认证是通过JDBC连接数据库,基于数据库中已有的用户信息进行身份认证,这样避免了内存身份认证的弊端,可以实现对系统已注册的用户进行身份认证。JdbcUserDetailsManager是Spring Security内置的UserDetailsService的实现类,同样的,只需
更换UserDetailsService的实现类为JdbcUserDetailsManager即可,更换方法:在WebSecurityConfig配置类userDetailsService()方法,将该方法创建的实例对象修改为JdbcUserDetailsManager实例,在该实例中指定该实例的认证信息,并存入在Spring容器中,
即可修改UserDetailsService的一个实现类。和内存身份认证不同的是,由于JDBC身份认证所需的用户信息(用户名、密码)存储在数据库里,所以JdbcUserDetailsManager实例需要配置数据源、查询用户信息的sql语句等。

示例
用户名、密码表
在这里插入图片描述
权限表
在这里插入图片描述
用户和权限的对照表
在这里插入图片描述

import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.context.annotation.Bean;
import  org.springframework.context.annotation.Configuration;
import  org.springframework.security.core.userdetails.UserDetailsService;
import  org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import  org.springframework.security.crypto.password.PasswordEncoder;
import  org.springframework.security.provisioning.JdbcUserDetailsManager;
import  javax.sql.DataSource;
@Configuration
public  class  WebSecurityConfig {
	@Autowired
	private  DataSource dataSource;
	@Bean
	public  PasswordEncoder passwordEncoder() {
		return  new  BCryptPasswordEncoder();
	}
	@Bean
	public  UserDetailsService userDetailsService() {
		String userSQL ="SELECT username,password, valid " +
		"FROM user WHERE username = ?";
		String authoritySQL="SELECT u.username,p.authority " +
		"FROM user u,priv p,user_priv up " +
		"WHERE up.user_id=u.id AND up.priv_id=p.id and u.username =?";
		JdbcUserDetailsManager users = new  JdbcUserDetailsManager();
		users.setDataSource(dataSource);
		users.setUsersByUsernameQuery(userSQL);
		users.setAuthoritiesByUsernameQuery(authoritySQL);
		return  users;
	}
}

进行JDBC身份认证时,需要注意以下几个问题。

  • 由于不同的开发者的用户、权限表格式都是不一样的,所以在配置JdbcUserDetailsManager实例时,需要自行编写sql查询语句。
  • 由于示例中的密码经过BCrypt 算法加密,所以还需要在passwordEncoder()方法中配置对应的密码加密和验证的工具类示例,并添加到spring容器中。

自定义UserDetailsService身份认证

使用InMemoryUserDetailsManager和JdbcUserDetailsManager进行身份认证时,其真正的认证逻辑都在UserDetailsService接口重写的loadUserByUsername()方法中。对于一个完善的项目来说,通常会实现用户信息查询服务,对此可以自定义一个UserDetailsService实现类,重写该接口的loadUserByUsername()方法,在该方法中查询用户信息,将查询到的用户信息填充到UserDetails对象返回,以实现用户的身份认证。下面通过案例对自定义UserDetailsService进行身份验证的实现进行演示 。

自定义UserDetailsService身份认证和上面两者不同的是,上面两者均使用Spring Security框架实现好的UserDetailsService实现类,分别是InMemoryUserDetailsManager和JdbcUserDetailsManager。当Spring Security框架没有我们需要的UserDetailsService实现类时,就需要更深层次的自定义,自定义UserDetailsService实现类,即自定义UserDetailsService身份认证。

示例

import  com.itheima.chapter07.dao.UserDao;
import  com.itheima.chapter07.entity.UserDto;
import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.security.core.userdetails.User;
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.List;
@Service
public  class  UserDetailsServiceImpl implements  UserDetailsService {
	@Autowired
	UserDao userDao;
	//根据用户名查询用户信息
	@Override
	public  UserDetails loadUserByUsername(String username) throws  UsernameNotFoundException {
		//连接数据库根据账号查询用户信息
		UserDto userDto = userDao.getUserByUsername(username);
		if (userDto == null){
			//如果用户查不到,返回null,会抛出异常
			return  null;
		}
		//根据用户的id查询用户的权限
		List<String> privileges = userDao.findPrivilegesByUserId(userDto.getId());
		//将privileges转成数组
		String[] privilegeArray = new  String[privileges.size()];
		privileges.toArray(privilegeArray);
		UserDetails userDetails = User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(privilegeArray).build();
		return  userDetails;
	}
}

进行自定义UserDetailsService身份认证时,需要注意以下几个问题。

  • loadUserByUsername(String username)方法返回的是userDetails对象,在内存身份认证和JDBC身份认证中,处理并返回 userDetails对象这些步骤都是由SpringSecurity框架自动完成。
  • 内存身份认证和JDBC身份认证中都是在WebSecurityConfig配置类userDetailsService()方法来实现替换UserDetailsService实现类,属于一个官方为预先实现好的UserDetailsService实现类提供的一个配置入口。而自定义UserDetailsService身份认证是实现UserDetailsService接口并重写loadUserByUsername(String username)方法。虽然操作方法不同,但其真正的认证逻辑都在UserDetailsService接口重写的loadUserByUsername()方法中。
  • 由于示例中的密码经过BCrypt算法加密,所以还需要在passwordEncoder()方法中配置对应的密码加密和验证的工具类示例,并添加到spring容器中。

注意

通常情况下,无论那种认证方式,Spring Security 会自动处理密码验证逻辑。当用户登录时,Spring Security 会将用户输入的密码进行加密处理,并与 UserDetails 中存储的加密密码进行比较。但如果你有特殊的密码验证需求,也可以自行编写密码验证逻辑,比如重写 PasswordEncoder或重写认证过滤器。

五、授权管理

授权是Spring Security的核心功能之一,是根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则可正常访问,没有访问的权限时则会被拒绝访问。认证是为了保证用户身份的合法性,而授权则是为了更细粒度地对隐私数据进行划分,授权是在认证通过后发生的,以控制不同的用户访问不同的资源。Spring Security提供了授权方法,开发者通过这些方法进行用户访问控制,下面对Spring Security的授权流程以及自定义授权进行讲解。

在这里插入图片描述
工作原理
①拦截请求。已认证用户访问受保护的Web资源将被SecurityFilterChain中FilterSecurityInterceptor实例对象拦截。

②获取资源访问策略。FilterSecurityInterceptor实例对象会通过SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource实例对象中获取要访问当前资源所需要的权限,权限封装在Collection实例对象中。 SecurityMetadataSource是读取访问策略的抽象,具体读取的内容,就是开发者配置的访问规则。

③FilterSecurityInterceptor通过AccessDecisionManager进行授权决策,若决策通过,则允许访问资 源,否则将禁止访问。AccessDecisionManager中包含一系列AccessDecisionVoter,可对当前认证过的身份是否有权访问对应的资源进行投票,AccessDecisionManager根据投票结果做出最终决策。

六、自定义授权

根据授权的位置和形式,通常可以将授权的方式分为Web授权和方法授权,这两种授权方式都会调用AccessDecisionManager进行授权决策。下面分别对这两种自定义授权的方式进行讲解。

1. Web授权

Spring Security的底层实现本质是通过多个Filter形成的过滤器链完成,过滤器链中提供了默认的安全拦截机制,设置安全拦截规则,以控制用户的访问。HttpSecurity是SecurityBuilder接口的实现类,是HTTP安全相关的构建器,Spring Security中可以通过HttpSecurity对象设置安全拦截规则,并通过该对象构建过滤器链。

简单来说就是:HttpSecurity是Spring Security安全拦截规则的构建器,我们可以通过调用HttpSecurity对象对应的方法来配置HttpSecurity以实现自定义的安全拦截规则。

HttpSecurity类的常用方法

  • authorizeRequests()开启基于HttpServletRequest请求访问的限制;
  • formLogin()开启基于表单的用户登录;
  • httpBasic()开启基于HTTP请求的Basic认证登录; logout()开启退出登录的支持等。

通过authorizeRequests()方法可以添加用户请求控制的规则(比如:哪些什么请求路径风格可访问,antMatchers(String… antPatterns)开启Ant风格的路径匹配;用户是否登陆才能访问,authenticated()匹配已经登录认证的用户;用户是否具有哪些权限才能访问,hasAnyAuthority(String… authorities)匹配用户是否有参数中的任意权限)。这些规则通过用户请求控制的相关方法指定。

通过HttpSecurity类的formLogin()方法开启基于表单的用户登录后,可以指定表单认证的相关设置。基于表单的身份验证的常见方法有:loginPage(String loginPage)指定自定义登录界面,不使用SpringSecurity默认登录界面;loginProcessingUrl(String loginProcessingUrl)指定处理登录的请求url,为表单提交用户信息的Action;successForwardUrl(String forwardUrl) 指定登录成功后默认跳转的路径等。

Web授权方法
WebSecurityConfig配置类中使用HttpSecurity对象设置安全拦截规则,并创建SecurityFilterChain对象交由Spring管理。

Web授权示例代码

import  org.springframework.context.annotation.Bean;
import  org.springframework.context.annotation.Configuration;
import  org.springframework.security.config.annotation.web.builders.HttpSecurity;
import  org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import  org.springframework.security.crypto.password.PasswordEncoder;
import  org.springframework.security.web.SecurityFilterChain;
@Configuration
public  class  WebSecurityConfig {
	@Bean
	public  PasswordEncoder passwordEncoder() {
		return  new  BCryptPasswordEncoder();
	}
	@Bean
	public  SecurityFilterChain securityFilterChain(HttpSecurity http) throws  Exception{
		http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
		.mvcMatchers("/loginview","/css/**","/img/**").permitAll()
		.mvcMatchers("/book/admin/**").hasRole("ADMIN")
		.anyRequest().authenticated() // 任何请求,登录后可以访问
		.and()
		.formLogin()
		.loginPage("/loginview")
		.loginProcessingUrl("/doLogin")
		.and()
		.csrf().disable()//禁止csrf 跨站请求保护;
		.headers().frameOptions().sameOrigin();
		return  http.build();
	}
}

2. 方法授权

Spring Security除了可以在配置类中通过创建过滤器链设置安全拦截规则外,还可以使用@Secured、@RolesAllowed和@PreAuthorize注解控制类中所有方法或者单独某个方法的访问权限,以实现对访问进行授权管理。

使用@Secured和@RolesAllowed注解时,只需在注解中指定访问当前注解标注的类或方法所需要具有的角色,允许多个角色访问时,使用大括号对角色信息进行包裹,角色信息之间使用分号分隔即可。

@RequestMapping("list")
@Secured({"ROLE_ADMIN","ROLE_COMMON"})
public String findList() {
        return "book_list";
}
@RequestMapping("admin/manag")
@RolesAllowed("ROLE_ADMIN")
public String findManagList() {
        return "book_manag";
}

使用@PreAuthorize注解会在方法执行前进行权限验证,支持SpEL表达式。

@RequestMapping("list")
@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_COMMON')")
public String findList() {
        return "book_list";
}
@RequestMapping("admin/manag")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String findManagList() {
       return "book_manag";
}

相同点:

  • @Secured、@RolesAllowed和@PreAuthorize注解都可以对方法的访问进行权限控制。
  • 依赖 Spring Security:它们都依赖于 Spring Security 框架来实现权限验证的功能,需要在项目中引入Spring Security 相关依赖才能正常使用。

不同点:

所属规范和框架

  • @Secured:是 Spring Security 框架特有的注解,它是 Spring 为了简化权限控制而提供的一种方式。
  • @RolesAllowed:是 JSR - 250 规范中定义的注解,该规范是 Java 平台关于安全注解的标准,很多 Java EE容器和框架都支持这个注解,Spring Security 也对其进行了支持。
  • @PostAuthorize:是 Spring Security 框架提供的注解,用于在方法执行后进行权限验证。

权限验证时机

  • @Secured 和@RolesAllowed:这两个注解的权限验证发生在方法执行之前。当请求调用被这两个注解标注的方法时,Spring Security会先检查当前用户是否具有指定的角色或权限,如果不具备则会拒绝访问,方法不会被执行。
  • @PostAuthorize:该注解的权限验证发生在方法执行之后。方法会先正常执行,然后 Spring Security 会根据@PostAuthorize 注解中指定的表达式来判断方法的返回结果是否符合权限要求,如果不符合则会抛出异常,阻止结果返回给调用者。

权限表达式支持

  • @Secured:只支持简单的角色名称作为参数,不支持复杂的权限表达式。
  • @RolesAllowed:同样只支持简单的角色名称作为参数,不支持复杂的权限表达式。
  • @PostAuthorize:支持使用 Spring EL表达式进行复杂的权限验证。可以根据方法的返回值、方法参数等进行动态的权限判断。

配置方式和灵活性

  • @Secured 和 @RolesAllowed:配置相对简单,只需要指定角色名称即可,适用于简单的权限控制场景。
  • @PostAuthorize:由于支持复杂的表达式,配置更加灵活,可以根据具体的业务需求进行精细的权限控制,但也需要开发者对Spring EL 表达式有一定的了解。

异常处理和错误信息

  • @Secured:当用户不具备所需角色时,Spring Security 会抛出 AccessDeniedException异常。开发人员可以通过配置 Spring Security 的异常处理器来处理该异常,并返回合适的错误信息给客户端。
  • @RolesAllowed:在 Java EE 应用中,当用户不具备所需角色时,通常会返回 HTTP 403 Forbidden响应。具体的异常处理和错误信息取决于应用服务器的配置。

语法

  • @Secured注解接受一个字符串数组作为参数,数组中的每个元素代表一个角色。在 Spring Security 中,角色默认以ROLE_ 作为前缀。
  • @RolesAllowed 注解也接受一个字符串数组作为参数,代表允许访问的角色,但可以不需要 ROLE_ 前缀

七、会话管理

在传统的基于表单登录的 Web 应用中,Spring Security 默认使用基于会话(Session)的机制来管理用户的认证状态,这里的 “令牌” 实际上就是会话 ID。

工作原理

Spring Security对用户信息认证通过后,会将用户信息存入Spring Security应用的上下文对象SecurityContext中,SecurityContext与当前线程进行绑定,需要获取用户信息时,可以通过SecurityContextHolder获取SecurityContext对象,进而使用SecurityContext对象获取用户信息。

其底层原理类似于SessionId。当接收到一个请求时,Spring Security 会从请求中提取会话 ID(通过 Cookie 或 URL 重写),然后根据会话 ID 从服务器的会话管理机制中查找对应的会话。如果找到匹配的会话,说明该请求来自同一个会话;如果未找到匹配的会话,则可能是一个新的会话或者会话已经过期。使用 Session ID 并不意味着 Spring Security 完全不能进行跨域请求,但是确实会在跨域场景下带来一些挑战和限制,具体解决方法有配置 CORS和使用 JSONP 或代理等。

从SecurityContext获取获取用户信息示例

        Authentication authentication = 
            SecurityContextHolder.getContext().getAuthentication();
        if (!authentication.isAuthenticated()) {
            return null;
        }
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        String username =userDetails.getUsername();

默认情况下,Spring Security会为每个登录成功的用户新建一个Session对象进行会话管理,开发者也可以根据具体的需求对Session的创建进行控制。

  • Spring Security管理Session的创建策略有以下四种。 always:如果没有Session就创建一个。
  • ifRequired:如果需要就创建一个Session,是默认的创建策略。
  • never:Spring Security将不会创建Session,但是如果项目中其他地方创建了Session,那么Spring
    Security可以使用它。
  • stateless:Spring Security将绝对不会创建Session,也不使用Session。

当项目中不想自动创建Session,但是想要使用项目中其他地方创建的Session时,可以选择使用never策略。

配置方法:和web授权一样,通过对HttpSecurity对象配置实现。在WebSecurityConfig配置类中使用HttpSecurity对象设置安全拦截规则,并创建SecurityFilterChain对象交由Spring管理。

示例

@Bean
public  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.sessionManagement()
              .sessionCreationPolicy(SessionCreationPolicy.NEVER);
       return http.build();
}

八、动态展示菜单

Thymeleaf引入Spring Security安全标签,并在页面中根据需求使用Spring Security标签指定为不同角色显示不同的页面内容,实现动态展示控制。

示例

    <section class="sidebar">
        <ul class="sidebar-menu">
            <!-- 具有 ROLE_COMMON 或 ROLE_ADMIN 角色的用户可见的菜单项 -->
            <li sec:authorize="hasAnyAuthority('ROLE_COMMON','ROLE_ADMIN')">
                <a th:href="@{/book/list}" target="iframe">
                    <i class="fa fa-circle-o"></i> 图书阅读
                </a>
            </li>
            <!-- 仅具有 ROLE_ADMIN 角色的用户可见的菜单项 -->
            <li sec:authorize="hasAuthority('ROLE_ADMIN')">
                <a th:href="@{/book/admin/manag}" target="iframe">
                    <i class="fa fa-circle-o"></i> 图书管理
                </a>
            </li>
        </ul>
    </section>

九、用户退出

SpringBoot+Thymeleaf(需要Thymeleaf实现)

Spring security默认实现了用户退出的功能,用户退出主要考虑退出后会话如何管理以及跳转到哪个页面。HttpSecurity类提供了logout()方法开启退出登录的支持,默认触发用户退出操作的URL为“/logout”,用户退出时同时也会清除Session等默认用户配置。

用户退出登录的逻辑是由过滤器LogoutFilter执行的,但是项目开发时一般不会选择直接操作​​LogoutFilter,而是通过LogoutConfigurer对LogoutFilter进行配置,HttpSecurity类logout()方法的返回值就是一个LogoutConfigurer对象,该对象提供了一系列设置用户退出的方法(如logoutUrl(String logoutUrl)用户退出处理控制URL,默认为post请求的/logout)。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

/**
 * 安全配置类,用于配置 Spring Security 的相关设置
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    /**
     * 定义安全过滤器链的 Bean
     * 
     * @param http HttpSecurity 对象,用于配置 HTTP 安全相关设置
     * @return 配置好的 SecurityFilterChain 对象
     * @throws Exception 配置过程中可能抛出的异常
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 开始配置 HttpSecurity
        http
            // 配置请求授权规则
            .authorizeRequests()
                // 所有请求都需要进行身份验证
                .anyRequest().authenticated()
                .and()
            // 配置表单登录
            .formLogin()
                .and()
            // 配置退出登录相关设置
            .logout()
                // 自定义退出登录的 URL,默认是 /logout,这里改为 /my-logout
                .logoutUrl("/my-logout")
                // 退出登录成功后跳转的页面
                .logoutSuccessUrl("/login?logout")
                // 清除认证信息
                .invalidateHttpSession(true)
                // 清除指定的 Cookie
                .deleteCookies("JSESSIONID")
                .and()
            // 配置 HTTP 基本认证
            .httpBasic();

        // 构建并返回配置好的 SecurityFilterChain
        return http.build();
    }
}

前后端分离项目

通常是删除浏览器端的localStorage或Cookie中存储的用户信息 。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐