Spring Boot | Spring Boot 实现 “记住我“ 功能

目录:

  • 一、SpringBoot 中 自定义 "用户授权管理" ( 总体内容介绍 )
  • 二、实现 "记住我" 功能 ( 通过 "HttpSecurity类" 的 rememberMe( )方法来实现 "记住我" 功能 ) :
      • 2.1 基于 "简单加密 Token" 的方式 ( 实现 "记住我" 功能 ) - 存在 "安全隐患",不建议使用该方式
        • 基础项目文件准备
        • 实现 "自定义身份认证" ( UserDetailsService身份认证 )
          • ① service层中类 获取 "用户基本信息" 和 "用户权限信息"
          • ② "自定义类" 实现 "UserDetailsService接口" , 在该类中 封装 "用户身份认证信息"
          • ③ "SecurityConfig配置类" 中 实现 "自定义 身份认证"
        • 实现 "自定义用户访问"
          • ④ "SecurityConfig配置类" 中 实现 "自定义 用户访问控制"
          • ⑤ controller层中 实现 "路径访问跳转"
        • 实现 "自定义用户登录"
          • ⑥ 自定义 用户登录 "页面"
          • ⑦ 自定义 用户登录 "跳转"
          • ⑧ 自定义 用户登录 "控制"
        • 实现 "自定义用户退出"
          • ⑨ 添加自定义 "用户退出链接"
          • ⑩ 添加自定义 "用户退出控制"
        • 基于 "简单加密Token方式" 实现 "记住我"功能
          • ⑪ 前端页面中添加 “记住我” 框
          • ⑫ 定制 "记住我" 功能
      • 2.2 基于持久化 Token 的方式 ( 这种方式 比 "第一种" 方式更安全 )
        • 基础项目文件准备
        • 实现 "自定义身份认证" ( UserDetailsService身份认证 )
          • ① service层中类 获取 "用户基本信息" 和 "用户权限信息"
          • ② "自定义类" 实现 "UserDetailsService接口" , 在该类中 封装 "用户身份认证信息"
          • ③ "SecurityConfig配置类" 中 实现 "自定义 身份认证"
        • 实现 "自定义用户访问"
          • ④ "SecurityConfig配置类" 中 实现 "自定义 用户访问控制"
          • ⑤ controller层中 实现 "路径访问跳转"
        • 实现 "自定义用户登录"
          • ⑥ 自定义 用户登录 "页面"
          • ⑦ 自定义 用户登录 "跳转"
          • ⑧ 自定义 用户登录 "控制"
        • 实现 "自定义用户退出"
          • ⑨ 添加自定义 "用户退出链接"
          • ⑩ 添加自定义 "用户退出控制"
        • 基于 "持久化 Token方式" 实现 "记住我"功能
          • ⑪ 创建 "存储Token信息" 的 persistent_logins "数据库表"
          • ⑫ 定制 "记住我" 功能 ( ①注入DataSource数据库信息 + ②调用rememberMe( )方法 + 配置JdbcTokenRepositoryImpl对象并将其加入到IOC容器中 )
          • ⑬ 效果测试

在这里插入图片描述

作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!

该文章参考学习教材为:
《Spring Boot企业级开发教程》 黑马程序员 / 编著
文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章

文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!

(侵权可联系我,进行删除,如果雷同,纯属巧合)


一、SpringBoot 中 自定义 “用户授权管理” ( 总体内容介绍 )

  • 一个系统建立之后,通常需要适当地做一些权限控制,使得 不同用户具有不同的权限 操作系统
    例如一般的项目中都会做一些简单的登录控制,只有特定用户才能登录访问。接下来将 针对 Web 应用中常见自定义用户授权管理 进行 介绍

  • SpringBoot自定义 “用户授权管理”实现方式 :
    创建类 继承(extens) WebSecurityConfigurerAdapter类

    重写 WebSecurityConfigurerAdapter类 中的 configure( HttpSecurity http )方法

    通过 HttpSecurity类中的 Xxx方法 来 实现自定义 “用户授权管理”

    ( 通过 configure( HttpSecurity http ) 方法 中的 HttpSecurity 类 实现/进行 “用户授权管理” )

  • HttpSecurity类主要方法说明 ( 通过 该类中的方法 来实现 "用户授权管理"):

    方法描述
    authorizeRequests( ) :
    授权请求
    开启基于 “HttpServletRequest” 请求访问限制
    ps :
    用于实现 “自定义用户访问控制
    ( 通过configure( HttpSecurity http)方法 中的 HttpSecurity类authorizeRequests( )方法实现 “自定义用户访问控制”其他方法则是以此类推。)
    formLogin( )开启基于表单用户登录
    ps :
    用于实现 “自定义用户登录页面
    使用该方法就是 使用 security提供的"默认登录"页面进行"登录验证" ( 如果没有指定 "自定义的登录页面" 的话 )
    httpBasic( )开启基于 HTTP 请求Basic 认证登录
    logout( )开启退出登录支持
    sessionManagement( )开启 Session 管理配置
    rememberMe( )开启 记住我 功能
    csrf( )配置 “CSRF” 跨站请求伪造防护功能

    补充 :
    configure ( HttpSecurity http )方法参数类型HttpSecurity 类HttpSecurity 类提供了 Http请求的限制权限Session 管理配置CSRF跨站请求问题等方法

二、实现 “记住我” 功能 ( 通过 “HttpSecurity类” 的 rememberMe( )方法来实现 “记住我” 功能 ) :

  • 实际开发中,有些项目 为了用户登录方便还会提供记住我( Remember-Me )功能。如果用户登录时勾选了 “记住我” 选项,那么在一段有效时间内,会默认自动登录并允许访问相关页面,这就 免去了重复登录操作麻烦

  • 实现 "记住我 功能 " 的 具体实际操作为 : 通过 WebSecurityConfigurerAdapter 类configure( HttpSecurity http )方法 中的 HttpSecurity 类中的 rememberMe( )方法可以实现 "记住我 功能 " 。

  • rememberMe( )方法 中 的 主要方法说明下表所示 :

    方法 ( 实现 “记住我” 功能的 方法 )描述
    rememberMeParameter( String rememberMeParameter )方法指定登录时 “记住” 用户HTTP 参数 ( 即指定 "记住我"功能框 中的 name属性值 )
    默认值为 : remember-me , 此时 前端的 "记住我"这个name属性属性值必须为 : remember-me
    当然 前端可修改对应的属性值,但 后端rememberMeParameter( ) 方法处也要对应的进行修改
    key( String key )方法设置 记住我认证生成Token 令牌标识
    tokenValiditySeconds( int token aliditySeconds )方法设置 记住我 Token 令牌有效期单位s()。
    ps
    设置 记住我功能中的 token有效期
    tokenRepository( PersistentTokenRepository tokenRepository )方法指定使用PesistentTokenRepository,用来 配置持久化 Token令牌
    ps :
    调用该方法来将Cookie信息/Token信息存储” 到 数据库 中。
    alwaysRemember( boolean alwaysRemember )方法是否应该始终创建 “记住我 Cookie”默认false
    clearAuthentication( boolean clearAuthentication )方法是否设置 Cookie安全的,如果设置true,则 必须通过 HTTPS进行连接请求
  • 需要说明的是Spring Security 针对 “记住我” 功能 提供了 两种实现 : 一种“简单地” 使用加密来保证基于 CookieToken 的安全 ; 另一种 是通过 数据库其他持久化机制保存生成的Token

2.1 基于 “简单加密 Token” 的方式 ( 实现 “记住我” 功能 ) - 存在 “安全隐患”,不建议使用该方式

  • 基于 简单加密 Token 的方式实现 “记住我功能 非常简单当用户选择 "记住我"成功登录后Spring Security 将会 生成一个 Cookie发送客户端浏览器。其中,Cookie 值下列方式组合 加密而成

    base64(username + ":" + expirationTime + ":" + md5Hex(username) + ":" + expirationTime + ":" password + ":" + key))
    

    上述 Cookie 值生成方式中,userame 代表 登录的用户名password 代表 登录用户密码expirationTime 表示 记住我中的 Token 的失效目期,以毫秒单位key 表示 防止修改 Token标识


    基于简单加密 Token 的方式中的 Token指定的时间内有效,且 必须保证 Token 中所包含的 username、password 和 key 没有被改变。需要注意的是,这种 加密方式其实是 存在安全隐患的 ,任何人获取到记住我功能Token后,都可以在该 Token 过期之前进行自动登录只有当用户觉察到 Token 被盗用后才会对自己的登录密码进行修改来立即使其原有的记住我 Token 失效

基础项目文件准备
  • 创建项目 :

    在这里插入图片描述

  • 项目结构 :

    在这里插入图片描述

  • pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.itheima</groupId><artifactId>chapter07</artifactId><version>0.0.1-SNAPSHOT</version><name>chapter07</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!-- Security与Thymeleaf整合实现前端页面安全访问控制 --><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency><!-- JDBC数据库连接启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- MySQL数据连接驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- Redis缓存启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Data JPA操作数据库  --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- Spring Security提供的安全管理依赖启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><!--    <build>--><!--        <plugins>--><!--            <plugin>--><!--                <groupId>org.springframework.boot</groupId>--><!--                <artifactId>spring-boot-maven-plugin</artifactId>--><!--            </plugin>--><!--        </plugins>--><!--    </build>--></project>
    
    • 导入 Sql文件 ( 创建数据库表 ) :
      security.sql

    • 创建实体类 :

      Customer.java :

      import javax.persistence.*;@Entity(name = "t_customer")public class Customer {@Id@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增private Integer id;private String username;private String password;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "Customer{" +"id=" + id +", username='" + username + '\'' +", password=" + password +'}';}
      }
      

    Authority.java

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;@Entity(name = "t_authority ")
    public class Authority {@Id@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增private Integer id;private String authority ;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getAuthority() {return authority;}public void setAuthority(String authority) {this.authority = authority;}@Overridepublic String toString() {return "Authority{" +"id=" + id +", authority='" + authority + '\'' +'}';}
    }
    
  • 创建Repository接口文件 : ( 通过该接口的方法操作数据 ) :

    CustomerRepository.java :

    import org.springframework.data.jpa.repository.JpaRepository;public interface CustomerRepository extends JpaRepository<Customer,Integer> {//根据username查询Customer对象信息Customer findByUsername(String username);
    }
    

    AuthorityRepository.java ( 接口文件 ) :

    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;import java.util.List;public interface AuthorityRepository extends JpaRepository<Authority,Integer> {//根据 username 来查询"权限信息"@Query(value = "select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username =?1",nativeQuery = true)public List<Authority> findAuthoritiesByUsername(String username);}
    
  • 自定义序列化机制 :

    RedisConfig.java :

    package com.itheima.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@Configuration
    public class RedisConfig { //在该配置类中 自定义存储到Redis数据库的数据的 "序列化机制"/***  定制Redis API模板RedisTemplate* @param redisConnectionFactory* @return*/@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);// 使用JSON格式序列化对象,对缓存数据key和value进行转换Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);// 解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jacksonSeial.setObjectMapper(om);// 设置RedisTemplate模板API的序列化方式为JSONtemplate.setDefaultSerializer(jacksonSeial);return template;}
    }
    
  • application.properties :

    spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=rootspring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=123456spring.thymeleaf.cache=false
    
  • 创建 html资源文件 :
    index.html 页面是 项目首页页面commonvip文件夹中分别对应普通用户VIP用户可访问的页面

    index.html

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf模板引擎页面配置 -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>影视直播厅</title>
    </head>
    <body><!-- index.html页面是项目首页页面,common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 -->
    <h1 align="center">欢迎进入电影网站首页</h1>
    <hr>
    <h3>普通电影</h3>
    <ul><li><a th:href="@{/detail/common/1}">飞驰人生</a></li><li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
    </ul>
    <h3>VIP专享</h3>
    <ul><li><a th:href="@{/detail/vip/1}">速度与激情</a></li><li><a th:href="@{/detail/vip/2}">猩球崛起</a></li>
    </ul>
    </body>
    </html>
    

    1.html : ( 其他三个页面 以此类推 )

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>Title</title>
    </head>
    <body>
    <!-- th:href="@{/}" : 返回项目首页-->
    <a th:href="@{/}">返回</a>
    <h1>飞驰人生</h1>
    .....
    </body>
    </html>
    
实现 “自定义身份认证” ( UserDetailsService身份认证 )
① service层中类 获取 “用户基本信息” 和 “用户权限信息”
  • service层 类中来从 Redis获取 “缓存数据”,如没找到缓存数据,则从Mysql数据库查询数据 : ( 获取 ① “用户基本信息”② “用户权限信息” )

    CustomerService.java :

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;import java.util.List;/*** 该Service层类中实现的代码效果为:* 在Reids数据库中查找是否有指定"缓存数据",有则从其中获取,没有则在Mysql数据库中查找,同时还将查找到的数据也在Redis中进行"缓存"*/
    @Service //加入到IOC容器中
    public class CustomerService {@Autowiredprivate CustomerRepository customerRepository;@Autowiredprivate AuthorityRepository authorityRepository;@Autowiredprivate RedisTemplate redisTemplate;  //通过 Redis API 的方式来进行 "Redis缓存"/*** 业务控制 : 使用唯一用户名查询用户信息*/public Customer getCustomer(String username){Customer customer=null;//从Redis数据库中获取"缓存数据"Object o = redisTemplate.opsForValue().get("customer_"+username);//判断是否有该缓存数据if(o!=null){customer=(Customer)o;}else { //不存在该缓存数据,则从数据库中查询缓存数据customer = customerRepository.findByUsername(username); //根据username来在数据库中查询数据if(customer!=null){redisTemplate.opsForValue().set("customer_"+username,customer);}}return customer;}/*** 业务控制 : 使用"唯一用户名"查询用户权限*/public List<Authority> getCustomerAuthority(String username){List<Authority> authorities=null;//尝试从Redis数据库中获得缓存数据Object o = redisTemplate.opsForValue().get("authorities_"+username);if(o!=null){authorities=(List<Authority>)o;}else {//没找到缓存数据则从Mysql数据库中查询数据authorities=authorityRepository.findAuthoritiesByUsername(username);if(authorities.size()>0){redisTemplate.opsForValue().set("authorities_"+username,authorities);}}return authorities;}
    }
    
② “自定义类” 实现 “UserDetailsService接口” , 在该类中 封装 “用户身份认证信息”
  • 自定义一个 , 该 实现UserDetailsService接口 , 用该接口loadUserByUsername( )方法 来封装 "用户认证信息" , 该最终被用于 configure ( AuthenticationManagerBuilder auth ) 方法中,被最终用于 "UserDetailsService"身份认证

    UserDetailsServiceImpl.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.*;
    import org.springframework.stereotype.Service;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;/***   自定义一个类, 该类实现了UserDetailsService接口 , 用该接口的 loadUserByUsername()方法来封装"用户认证信息" ,*   该类最终被用于 configure(AuthenticationManagerBuilder auth)方法中,被最终用于 "UserDetailsService"身份认证。*/
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService { //实现 UserDetailsService接口@Autowiredprivate CustomerService customerService;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //辅助进行"用户身份认证"的方法//通过业务方法(业务层类)获取用户以及权限信息//根据username获得Customer对象信息Customer customer = customerService.getCustomer(s);List<Authority> authorities = customerService.getCustomerAuthority(s);/***  .stream() : 将"权限信息"集合 转换为一个流(Stream) , 以便进行后续的流式操作**  .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) :*     使用map操作 / map()函数 来转换流中的每个元素 (将流中的"每一个元素"转换为 "另一种形式")*     具体分析:*     authority -> 获得流中的每一个元素,将其转换为另一种形式*     authority.getAuthority() : 获得 authority 这个元素对象的"权限信息" ( 是一个"权限信息"的字符串 )*     new SimpleGrantedAuthority(authority.getAuthority())) : 使用这个 "权限信息字符串" 创建一个 SimpleGrantedAuthority对象,*     该对象 是 Spring Security框架中用于表示 "授权信息" 的类**   .collect(Collectors.toList()); : 是一个终端操作,它告诉流如何收集其元素以生成一个结果。*     具体分析:*     .collect(Collectors.toList()) : 收集转换后的 SimpleGrantedAuthority对象,并将其放入一个新的列表中*/// 对"用户权限信息"进行封装List<SimpleGrantedAuthority> list = authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());/*创建 UserDetails (用户详情) 对象,并将该对象进行返回*/if(customer!=null){//用 username 、password 、权限信息集合 作为参数创建 UserDetails对象 ( 用户详情对象 )UserDetails userDetails= new User(customer.getUsername(),customer.getPassword(),list);return userDetails;} else {//如果查询的用户不存在 (用户名不存在 ) , 必须抛出异常throw new UsernameNotFoundException("当前用户不存在!");}}
    }
    
③ “SecurityConfig配置类” 中 实现 “自定义 身份认证”
  • SecurityConfig配置类” 中 实现 “自定义身份认证 :

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}
    }    
    
实现 “自定义用户访问”
④ “SecurityConfig配置类” 中 实现 “自定义 用户访问控制”
  • SecurityConfig配置类” 中 实现 自定义 “用户访问控制” :

    SecurityConfig.java

    package com.itheima.config;import com.itheima.service.Impl.UserDetailsServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户访问控制 http.authorizeRequests().antMatchers("/").permitAll()  //对/请求进行放行.antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)}}
    
⑤ controller层中 实现 “路径访问跳转”
  • controller层中 实现 "路径访问跳转" :

    LoginController.java

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.ResponseBody;@Controller //加入IOC容器中
    public class LoginController {  //关于跳转到登录页面有关的controller类/*跳转到Detail文件夹下的"视图页面"*/@GetMapping("/detail/{type}/{path}") //其中的变量为"路径变量"// @PathVariable注解获得"路径变量"的值public String toDetail( @PathVariable("type") String type ,@PathVariable("path") String path ) {//返回值为String,可用于返回一个视图页面return "/detail/"+type+"/"+path;}}
    
实现 “自定义用户登录”
⑥ 自定义 用户登录 “页面”
  • 要实现 自定义用户登录功能,首先必须根据需要自定义一个用户登录页面。在项目的 resources/templates 目录下新创建一个名为 login文件夹( 专门处理用户登录 ),在该文件夹中创建一个 用户登录页面 : login.html

    login.html

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf"模板引擎"页面配置 -->
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>用户登录界面</title><link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet"><link th:href="@{/login/css/signin.css}" rel="stylesheet">
    </head>
    <body class="text-center">
    <!--  form表单请求跳转路径为 : /userLogin  -->
    <form class="form-signin" th:action="@{/userLogin}" th:method="post" ><!--  img标签--><img class="mb-4" th:src="@{/login/img/login.jpg}" width="72px" height="72px"><h1 class="h3 mb-3 font-weight-normal">请登录</h1><!-- 用户登录错误信息提示框 --><!--  th:if 根据条件的真假决定是否渲染 HTML 元素 ,真则渲染,假则不渲染 (有该参数值则渲染,没有该参数值则不渲染)  --><!--  有该参数值则渲染该div,否则就不渲染该div   --><div th:if="${param.error}"style="color: red;height: 40px;text-align: left;font-size: 1.1em"><!--  img标签  --><img th:src="@{/login/img/loginError.jpg}" width="20px">用户名或密码错误,请重新登录!</div><!--  用户名参数名为 : name  , 密码的参数名为: pws  --><input type="text" name="name" class="form-control"placeholder="用户名" required="" autofocus=""><input type="password" name="pwd" class="form-control"placeholder="密码" required=""><button class="btn btn-lg btn-primary btn-block" type="submit" >登录</button><p class="mt-5 mb-3 text-muted">Copyright© 2024-2025</p>
    </form>
    </body>
    </html>
    

    上面代码中引入了两个 CSS 样式文件和两个 img图片文件用来渲染用户登录页面,它们都存在于 /ogin/** 目录下,需要提前引入这些静态资源文件
    目录中。引入这些静态资源文件后结构如下图所示 :

    获得相应的css文件 和 图片文件

    在这里插入图片描述

⑦ 自定义 用户登录 “跳转”
  • 自定义 “用户登录跳转” :

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.ResponseBody;@Controller //加入IOC容器中
    public class LoginController {  //关于跳转到登录页面有关的controller类/*** 跳转到自定义的 "登录页面"*/@GetMapping("/userLogin")public String toLoginPage() {return "/login/login";}
    }
    

    Spring Security 默认采用 Get 方式“/ogin”请求 用于向 “登录页面” 跳转 ( 可自定义跳转"登录页面"的路径,来指定”登录页面“ ),使用 Post 方式的 “/ogin”请求用于对登录后的数据处理

⑧ 自定义 用户登录 “控制”
  • 完成上面的准备工作后,打开 SecurityConfig 类重写 configure( HttpSecurity http )方法,实现 用户登录控制
    示例代码如下 :

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制 ( 权限管理 )*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户授权管理 (自定义用户访问控制 )http.authorizeRequests() .antMatchers("/").permitAll()  //对 "/"请求 进行放行 (进入到项目首页)//对static文件夹下的静态资源进行统一放行 (如果没有对静态资源放行,未登录的用户访问项目首页时就无法加载页面关联的静态资源文件 ).antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)/*** 自定义"用户登录控制" :*   .loginPage() : 自定义登录页面的"跳转路径" , 跳转到自己定制的"登录页面"*   .permitAll() : 无条件对请求进行放行*   .usernameParameter("name").passwordParameter("pwd") : 设置登录用户的"用户名参数" 和 "密码参数" (接受登录时提交的"用户名"和"密码")*   .defaultSuccessUrl() : 指定用户登录成功后的默认跳转地址*   .failureUrl() : 指定用户登录失败后的"跳转地址",默认跳转到 /login?error*     ---error时一个错误标识,作用是在登录失败后在登录页面进行接收判断,例如login.html示例中的${param.error},这两者必须保持一直。*///自定义"用户登录控制"http.formLogin().loginPage("/userLogin").permitAll() //自定义登录页面的"跳转路径".usernameParameter("name").passwordParameter("pwd")  //设置登录用户的"用户名参数" 和 "密码参数".defaultSuccessUrl("/") //用户登录成功后默认跳转到"项目首页".failureUrl("/userLogin?error"); //用户登录失败后默认跳转到"项目首页"}
    }    
    
实现 “自定义用户退出”
⑨ 添加自定义 “用户退出链接”
  • 要实现 自定义用户退出功能,必须某个页面定义用户退出链接或者 按钮。为了简化操作我们在之前创建的项目首页 : index.html 上方新增一个用户退出链接修改后示范文件代码如下所示 :

    <!-- th:action : 用于指定“表单提交”时 “应发送的URL”-->
    <!-- 跳转到 /mylogout路径来进行 "用户退出" , 因为不是使用默认的 /logout路径来请求 "用户退出"  所以 后端的 .logoutUrl()方法中内容也修改为 /mylogout -->
    <!-- 同时 /mylogout 请求的方法为post类型的-->
    <form th:action="/mylogout" method="post"><input th:type="submit" th:value="注销">
    </form>
    

    index.html

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf模板引擎页面配置 -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>影视直播厅</title>
    </head>
    <body><!-- index.html页面是项目首页页面,common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 -->
    <h1 align="center">欢迎进入电影网站首页</h1>
    <!-- th:action : 用于指定“表单提交”时 “应发送的URL”-->
    <!-- 跳转到 /mylogout路径来进行 "用户退出" , 因为不是使用默认的 /logout路径来请求 "用户退出"  所以 后端的 .logoutUrl()方法中内容也修改为 /mylogout -->
    <!-- 同时 /mylogout 请求的方法为post类型的-->
    <form th:action="/mylogout" method="post"><input th:type="submit" th:value="注销">
    </form>
    <hr>
    <h3>普通电影</h3>
    <ul><li><a th:href="@{/detail/common/1}">飞驰人生</a></li><li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
    </ul>
    <h3>VIP专享</h3>
    <ul><li><a th:href="@{/detail/vip/1}">速度与激情</a></li><li><a th:href="@{/detail/vip/2}">猩球崛起</a></li>
    </ul>
    </body>
    </html>
    

    上面的代码中新增了一个 <form>标签进行注销控制 ( 进行 “用户退出控制”),且定义的退出表单 aciton 为“/mylogout ( 默认为“/logout” ),方法post


    需要说明的是,Spring Boot 项目中引入 Spring Security 框架后会 自动开启 : CSRF 防护功能 ( 跨站请求伪造防护),用户退出必须使用 POST请求 ; 如果关闭CSRF 防护功能,那么 可以使用任意方式HTTP 请求进行用户注销

⑩ 添加自定义 “用户退出控制”
  • SecurityConfig.java

    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制 ( 权限管理 )*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户授权管理 (自定义用户访问控制 )http.authorizeRequests().antMatchers("/").permitAll()  //对 "/"请求 进行放行 (进入到项目首页)//对static文件夹下的静态资源进行统一放行 (如果没有对静态资源放行,未登录的用户访问项目首页时就无法加载页面关联的静态资源文件 ).antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)/*** 自定义"用户登录控制" :*   .loginPage() : 自定义登录页面的"跳转路径" , 跳转到自己定制的"登录页面"*   .permitAll() : 无条件对请求进行放行*   .usernameParameter("name").passwordParameter("pwd") : 设置登录用户的"用户名参数" 和 "密码参数" (接受登录时提交的"用户名"和"密码")*   .defaultSuccessUrl() : 指定用户登录成功后的默认跳转地址*   .failureUrl() : 指定用户登录失败后的"跳转地址",默认跳转到 /login?error*     ---error时一个错误标识,作用是在登录失败后在登录页面进行接收判断,例如login.html示例中的${param.error},这两者必须保持一直。*///自定义"用户登录控制"http.formLogin().loginPage("/userLogin").permitAll() //自定义登录页面的"跳转路径".usernameParameter("name").passwordParameter("pwd")  //设置登录用户的"用户名参数" 和 "密码参数".defaultSuccessUrl("/") //用户登录成功后默认跳转到"项目首页".failureUrl("/userLogin?error"); //用户登录失败后默认跳转到"项目首页"/*** 自定义"用户退出控制"*/http.logout().logoutUrl("/mylogout") //指定用户退出的请求路径 (前端页面进行"用户退出时,要请求该路径,且方法为post").logoutSuccessUrl("/"); //退出成功后的重定向地址 (退出成功后跳转的地址)//用户退出后,用户会话信息是默认清除的,次情况下无需手动配置}}
    
基于 “简单加密Token方式” 实现 "记住我"功能
⑪ 前端页面中添加 “记住我” 框
  • 下面将结合前面介绍rememberMe()相关方法实现这种简单记住我功能。在已有的 login.html 文件新增一个 “记住我” 功能勾选框示例代码如下 :

       <!--  实现记住我功能  --><div><label><!--Security提供的记住我功能的name属性默认值为: remember-me , 下面的name属性值为 : rememberme ,因此后端的 		   	  rememberMeParameter()方法中也要进行对应的修改-->         <input th:type="checkbox" name="rememberme">记住我</label></div>
    

    login.html :

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf"模板引擎"页面配置 -->
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>用户登录界面</title><link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet"><link th:href="@{/login/css/signin.css}" rel="stylesheet">
    </head>
    <body class="text-center">
    <!--  form表单请求跳转路径为 : /userLogin  -->
    <form class="form-signin" th:action="@{/userLogin}" th:method="post" ><!--  img标签--><img class="mb-4" th:src="@{/login/img/login.jpg}" width="72px" height="72px"><h1 class="h3 mb-3 font-weight-normal">请登录</h1><!-- 用户登录错误信息提示框 --><!--  th:if 根据条件的真假决定是否渲染 HTML 元素 ,真则渲染,假则不渲染 (有该参数值则渲染,没有该参数值则不渲染)  --><!--  有该参数值则渲染该div,否则就不渲染该div   --><div th:if="${param.error}"style="color: red;height: 40px;text-align: left;font-size: 1.1em"><!--  img标签  --><img th:src="@{/login/img/loginError.jpg}" width="20px">用户名或密码错误,请重新登录!</div><!--  用户名参数名为 : name  , 密码的参数名为: pws  --><input type="text" name="name" class="form-control"placeholder="用户名" required="" autofocus=""><input type="password" name="pwd" class="form-control"placeholder="密码" required=""><!--  实现记住我功能  --><div><label><!--Security提供的记住我功能的name属性默认值为: remember-me , 下面的name属性值为 : rememberme ,因此后端的 rememberMeParameter()方法中也要进行对应的修改--><input th:type="checkbox" name="rememberme">记住我</label></div><button class="btn btn-lg btn-primary btn-block" type="submit" >登录</button><p class="mt-5 mb-3 text-muted">Copyright© 2024-2025</p>
    </form>
    </body>
    </html>
    
⑫ 定制 “记住我” 功能
  • 定制 “记住我” 功能 :

        /*** 定制记住我功能*/http.rememberMe()//指定"记住我"功能框中的name属性值,如果该框使用了默认的"remember-me",则该方法可以省略.rememberMeParameter("rememberme")//设置记住我功能中的token的有效期为200s.tokenValiditySeconds(200);
    }
    

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制 ( 权限管理 )*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户授权管理 (自定义用户访问控制 )http.authorizeRequests().antMatchers("/").permitAll()  //对 "/"请求 进行放行 (进入到项目首页)//对static文件夹下的静态资源进行统一放行 (如果没有对静态资源放行,未登录的用户访问项目首页时就无法加载页面关联的静态资源文件 ).antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)/*** 自定义"用户登录控制" :*   .loginPage() : 自定义登录页面的"跳转路径" , 跳转到自己定制的"登录页面"*   .permitAll() : 无条件对请求进行放行*   .usernameParameter("name").passwordParameter("pwd") : 设置登录用户的"用户名参数" 和 "密码参数" (接受登录时提交的"用户名"和"密码")*   .defaultSuccessUrl() : 指定用户登录成功后的默认跳转地址*   .failureUrl() : 指定用户登录失败后的"跳转地址",默认跳转到 /login?error*     ---error时一个错误标识,作用是在登录失败后在登录页面进行接收判断,例如login.html示例中的${param.error},这两者必须保持一直。*///自定义"用户登录控制"http.formLogin().loginPage("/userLogin").permitAll() //自定义登录页面的"跳转路径".usernameParameter("name").passwordParameter("pwd")  //设置登录用户的"用户名参数" 和 "密码参数".defaultSuccessUrl("/") //用户登录成功后默认跳转到"项目首页".failureUrl("/userLogin?error"); //用户登录失败后默认跳转到"项目首页"/*** 自定义"用户退出控制"*/http.logout().logoutUrl("/mylogout") //指定用户退出的请求路径 (前端页面进行"用户退出时,要请求该路径,且方法为post").logoutSuccessUrl("/"); //退出成功后的重定向地址 (退出成功后跳转的地址)//用户退出后,用户会话信息是默认清除的,次情况下无需手动配置/*** 定制记住我功能*/http.rememberMe()//指定"记住我"功能框中的name属性值,如果该框使用了默认的"remember-me",则该方法可以省略.rememberMeParameter("rememberme")//设置记住我功能中的token的有效期为200s.tokenValiditySeconds(200);}}
    

    初次登录时勾选了【记住我】选项后,在设置的 Token 有效期内 再次进行访问不需要重新登录认证如果 Token 失效后再次访问项目,则需要重新登录认证

2.2 基于持久化 Token 的方式 ( 这种方式 比 “第一种” 方式更安全 )

  • 持久化 Token方式简单加密Token方式在实现 Remember-Me (记住我) 功能大体相同 , 都是在 用户 选择【记住我】成功登录 后,将 生成的Token存入Cookie发送客户端浏览器 ,在下次 用户通过同一客户端访问系统时,系统直接客户端 Cookie读取 Token 进行认证

    ( 用户登录成功将生成的Token存入到 Cookie中,并将该Cookie发送给" 客户端浏览器" , 浏览器读取 Cookie 中的 Token信息 进行 认证,这 就实现了"记住我"功能 )

  • 基于 “简单加密 Token”方式基于持久化 Token方式区别 :

    1. 基于简单加密 Token 的方式,生成的Token 将在 客户端保存一段时间 , 如果 用户不退出登录,或者 不修改密码那么在 Cookie 失效之前任何人都可以无限制地使用该 Token 进行 自动登录 ;

    2. 基于持久化 Token方式采用 如下实现逻辑 :

    • (1) 用户 选择【记住我 功能 成功登录后Security 会把 username 随机产生的序列号 生成的Token 进行 持久化存储 ( 例如存储数据表中 ),同时 它们的组合生成一个 Cookie “发送给” 客户端浏览器
      ----( 将 记住我功能 相关的信息数据库存储一份同时 也将 其组合成 一个 CookieCookie 客户端浏览器发送一份 )
    • (2) 当用户 再次访问系统时,首先检查 客户端 携带的 Cookie信息,如果对应 Cookie信息包含username序列号Token 与数据库保存的一致,则 “验证通过” 并 “自动登录”,同时 系统重新生成一个新Token 替换数据库旧的 Token,同时也会组合一个 新的Cookie 并将其 发给 “客户端”
      ----( 当 用户再次访问系统时,将 客户端 中的 Cookie信息 和 与 数据库 中的Token信息进行 比对如果一致,则代表 验证通过并“ 自动登录” ,同时系统将生成一个新的Token替代数据库旧的Token ,同时还会组合一个 新的Cookie将其发给 “客户端” )
    • (3) 如果 Cookie 中的 Token 不匹配 ( 即如果 客户端中的 Token信息数据库中Token信息不一致 ),则很有可能用户的 Cookie 被盗用。由于盗用者使用初次生成Token 进行登录生成一个新的 Token,所以用户不知情再次登录就会出现 Token 不匹配的情况,这时就需要重新登录,并生成新的TokenCookie。同时 Spring Securiy 就可以发现 Cookie 可能被盗用的情况,它将 删除数据库 中与 当前用户相关所有Token 记录,这样 盗用者 使用 原有的Cookie不能再次登录
    • (4) 如果 用户"访问系统" 没有携带Cookie,或者包含的 username序列号数据库保存的不一致,那么将 引导用户登录页面

    以上实现逻辑可以看出持久化Token 的方式比 简单加密Token 的方式 相对更加安全。使用 简单加密 Token 的方式,一旦用户的 Cookie 被盗用,在 Token 有效期内盗用者可以无限制地自动登录进行恶意操作直到用户本人发现修改密码才会避免这种问题 ;
    而使用 持久化 Token 的方式 相对安全,用户 每登录一次都会生成新的TokenCookie,但也给盗用者留下了在用户进行第2次登录前进行恶意操作的机会,只有在用户进行第2次登录更新Token 和 Cookie 时才会避免这种问题。因此,总体来讲,对于 安全性要求很高应用不推荐使用Bemember-Me 功能

基础项目文件准备
  • 创建项目 :

    在这里插入图片描述

  • 项目结构 :

    在这里插入图片描述

  • pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.itheima</groupId><artifactId>chapter07</artifactId><version>0.0.1-SNAPSHOT</version><name>chapter07</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!-- Security与Thymeleaf整合实现前端页面安全访问控制 --><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency><!-- JDBC数据库连接启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- MySQL数据连接驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- Redis缓存启动器--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Data JPA操作数据库  --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- Spring Security提供的安全管理依赖启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><!--    <build>--><!--        <plugins>--><!--            <plugin>--><!--                <groupId>org.springframework.boot</groupId>--><!--                <artifactId>spring-boot-maven-plugin</artifactId>--><!--            </plugin>--><!--        </plugins>--><!--    </build>--></project>
    
    • 导入 Sql文件 ( 创建数据库表 ) :
      security.sql

    • 创建实体类 :

      Customer.java :

      import javax.persistence.*;@Entity(name = "t_customer")public class Customer {@Id@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增private Integer id;private String username;private String password;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "Customer{" +"id=" + id +", username='" + username + '\'' +", password=" + password +'}';}
      }
      

    Authority.java

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;@Entity(name = "t_authority ")
    public class Authority {@Id@GeneratedValue(strategy = GenerationType.IDENTITY) //主键自增private Integer id;private String authority ;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getAuthority() {return authority;}public void setAuthority(String authority) {this.authority = authority;}@Overridepublic String toString() {return "Authority{" +"id=" + id +", authority='" + authority + '\'' +'}';}
    }
    
  • 创建Repository接口文件 : ( 通过该接口的方法操作数据 ) :

    CustomerRepository.java :

    import org.springframework.data.jpa.repository.JpaRepository;public interface CustomerRepository extends JpaRepository<Customer,Integer> {//根据username查询Customer对象信息Customer findByUsername(String username);
    }
    

    AuthorityRepository.java ( 接口文件 ) :

    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;import java.util.List;public interface AuthorityRepository extends JpaRepository<Authority,Integer> {//根据 username 来查询"权限信息"@Query(value = "select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username =?1",nativeQuery = true)public List<Authority> findAuthoritiesByUsername(String username);}
    
  • 自定义序列化机制 :

    RedisConfig.java :

    package com.itheima.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@Configuration
    public class RedisConfig { //在该配置类中 自定义存储到Redis数据库的数据的 "序列化机制"/***  定制Redis API模板RedisTemplate* @param redisConnectionFactory* @return*/@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);// 使用JSON格式序列化对象,对缓存数据key和value进行转换Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);// 解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jacksonSeial.setObjectMapper(om);// 设置RedisTemplate模板API的序列化方式为JSONtemplate.setDefaultSerializer(jacksonSeial);return template;}
    }
    
  • application.properties :

    spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=rootspring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=123456spring.thymeleaf.cache=false
    
  • 创建 html资源文件 :
    index.html 页面是 项目首页页面commonvip文件夹中分别对应普通用户VIP用户可访问的页面

    index.html

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf模板引擎页面配置 -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>影视直播厅</title>
    </head>
    <body><!-- index.html页面是项目首页页面,common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 -->
    <h1 align="center">欢迎进入电影网站首页</h1>
    <hr>
    <h3>普通电影</h3>
    <ul><li><a th:href="@{/detail/common/1}">飞驰人生</a></li><li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
    </ul>
    <h3>VIP专享</h3>
    <ul><li><a th:href="@{/detail/vip/1}">速度与激情</a></li><li><a th:href="@{/detail/vip/2}">猩球崛起</a></li>
    </ul>
    </body>
    </html>
    

    1.html : ( 其他三个页面 以此类推 )

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>Title</title>
    </head>
    <body>
    <!-- th:href="@{/}" : 返回项目首页-->
    <a th:href="@{/}">返回</a>
    <h1>飞驰人生</h1>
    .....
    </body>
    </html>
    
实现 “自定义身份认证” ( UserDetailsService身份认证 )
① service层中类 获取 “用户基本信息” 和 “用户权限信息”
  • service层 类中来从 Redis获取 “缓存数据”,如没找到缓存数据,则从Mysql数据库查询数据 : ( 获取 ① “用户基本信息”② “用户权限信息” )

    CustomerService.java :

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;import java.util.List;/*** 该Service层类中实现的代码效果为:* 在Reids数据库中查找是否有指定"缓存数据",有则从其中获取,没有则在Mysql数据库中查找,同时还将查找到的数据也在Redis中进行"缓存"*/
    @Service //加入到IOC容器中
    public class CustomerService {@Autowiredprivate CustomerRepository customerRepository;@Autowiredprivate AuthorityRepository authorityRepository;@Autowiredprivate RedisTemplate redisTemplate;  //通过 Redis API 的方式来进行 "Redis缓存"/*** 业务控制 : 使用唯一用户名查询用户信息*/public Customer getCustomer(String username){Customer customer=null;//从Redis数据库中获取"缓存数据"Object o = redisTemplate.opsForValue().get("customer_"+username);//判断是否有该缓存数据if(o!=null){customer=(Customer)o;}else { //不存在该缓存数据,则从数据库中查询缓存数据customer = customerRepository.findByUsername(username); //根据username来在数据库中查询数据if(customer!=null){redisTemplate.opsForValue().set("customer_"+username,customer);}}return customer;}/*** 业务控制 : 使用"唯一用户名"查询用户权限*/public List<Authority> getCustomerAuthority(String username){List<Authority> authorities=null;//尝试从Redis数据库中获得缓存数据Object o = redisTemplate.opsForValue().get("authorities_"+username);if(o!=null){authorities=(List<Authority>)o;}else {//没找到缓存数据则从Mysql数据库中查询数据authorities=authorityRepository.findAuthoritiesByUsername(username);if(authorities.size()>0){redisTemplate.opsForValue().set("authorities_"+username,authorities);}}return authorities;}
    }
    
② “自定义类” 实现 “UserDetailsService接口” , 在该类中 封装 “用户身份认证信息”
  • 自定义一个 , 该 实现UserDetailsService接口 , 用该接口loadUserByUsername( )方法 来封装 "用户认证信息" , 该最终被用于 configure ( AuthenticationManagerBuilder auth ) 方法中,被最终用于 "UserDetailsService"身份认证

    UserDetailsServiceImpl.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.*;
    import org.springframework.stereotype.Service;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;/***   自定义一个类, 该类实现了UserDetailsService接口 , 用该接口的 loadUserByUsername()方法来封装"用户认证信息" ,*   该类最终被用于 configure(AuthenticationManagerBuilder auth)方法中,被最终用于 "UserDetailsService"身份认证。*/
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService { //实现 UserDetailsService接口@Autowiredprivate CustomerService customerService;@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //辅助进行"用户身份认证"的方法//通过业务方法(业务层类)获取用户以及权限信息//根据username获得Customer对象信息Customer customer = customerService.getCustomer(s);List<Authority> authorities = customerService.getCustomerAuthority(s);/***  .stream() : 将"权限信息"集合 转换为一个流(Stream) , 以便进行后续的流式操作**  .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) :*     使用map操作 / map()函数 来转换流中的每个元素 (将流中的"每一个元素"转换为 "另一种形式")*     具体分析:*     authority -> 获得流中的每一个元素,将其转换为另一种形式*     authority.getAuthority() : 获得 authority 这个元素对象的"权限信息" ( 是一个"权限信息"的字符串 )*     new SimpleGrantedAuthority(authority.getAuthority())) : 使用这个 "权限信息字符串" 创建一个 SimpleGrantedAuthority对象,*     该对象 是 Spring Security框架中用于表示 "授权信息" 的类**   .collect(Collectors.toList()); : 是一个终端操作,它告诉流如何收集其元素以生成一个结果。*     具体分析:*     .collect(Collectors.toList()) : 收集转换后的 SimpleGrantedAuthority对象,并将其放入一个新的列表中*/// 对"用户权限信息"进行封装List<SimpleGrantedAuthority> list = authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());/*创建 UserDetails (用户详情) 对象,并将该对象进行返回*/if(customer!=null){//用 username 、password 、权限信息集合 作为参数创建 UserDetails对象 ( 用户详情对象 )UserDetails userDetails= new User(customer.getUsername(),customer.getPassword(),list);return userDetails;} else {//如果查询的用户不存在 (用户名不存在 ) , 必须抛出异常throw new UsernameNotFoundException("当前用户不存在!");}}
    }
    
③ “SecurityConfig配置类” 中 实现 “自定义 身份认证”
  • SecurityConfig配置类” 中 实现 “自定义身份认证 :

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}
    }    
    
实现 “自定义用户访问”
④ “SecurityConfig配置类” 中 实现 “自定义 用户访问控制”
  • SecurityConfig配置类” 中 实现 自定义 “用户访问控制” :

    SecurityConfig.java

    package com.itheima.config;import com.itheima.service.Impl.UserDetailsServiceImpl;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户访问控制 http.authorizeRequests().antMatchers("/").permitAll()  //对/请求进行放行.antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)}}
    
⑤ controller层中 实现 “路径访问跳转”
  • controller层中 实现 "路径访问跳转" :

    LoginController.java

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.ResponseBody;@Controller //加入IOC容器中
    public class LoginController {  //关于跳转到登录页面有关的controller类/*跳转到Detail文件夹下的"视图页面"*/@GetMapping("/detail/{type}/{path}") //其中的变量为"路径变量"// @PathVariable注解获得"路径变量"的值public String toDetail( @PathVariable("type") String type ,@PathVariable("path") String path ) {//返回值为String,可用于返回一个视图页面return "/detail/"+type+"/"+path;}}
    
实现 “自定义用户登录”
⑥ 自定义 用户登录 “页面”
  • 要实现 自定义用户登录功能,首先必须根据需要自定义一个用户登录页面。在项目的 resources/templates 目录下新创建一个名为 login文件夹( 专门处理用户登录 ),在该文件夹中创建一个 用户登录页面 : login.html

    login.html

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf"模板引擎"页面配置 -->
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>用户登录界面</title><link th:href="@{/login/css/bootstrap.min.css}" rel="stylesheet"><link th:href="@{/login/css/signin.css}" rel="stylesheet">
    </head>
    <body class="text-center">
    <!--  form表单请求跳转路径为 : /userLogin  -->
    <form class="form-signin" th:action="@{/userLogin}" th:method="post" ><!--  img标签--><img class="mb-4" th:src="@{/login/img/login.jpg}" width="72px" height="72px"><h1 class="h3 mb-3 font-weight-normal">请登录</h1><!-- 用户登录错误信息提示框 --><!--  th:if 根据条件的真假决定是否渲染 HTML 元素 ,真则渲染,假则不渲染 (有该参数值则渲染,没有该参数值则不渲染)  --><!--  有该参数值则渲染该div,否则就不渲染该div   --><div th:if="${param.error}"style="color: red;height: 40px;text-align: left;font-size: 1.1em"><!--  img标签  --><img th:src="@{/login/img/loginError.jpg}" width="20px">用户名或密码错误,请重新登录!</div><!--  用户名参数名为 : name  , 密码的参数名为: pws  --><input type="text" name="name" class="form-control"placeholder="用户名" required="" autofocus=""><input type="password" name="pwd" class="form-control"placeholder="密码" required=""><button class="btn btn-lg btn-primary btn-block" type="submit" >登录</button><p class="mt-5 mb-3 text-muted">Copyright© 2024-2025</p>
    </form>
    </body>
    </html>
    

    上面代码中引入了两个 CSS 样式文件和两个 img图片文件用来渲染用户登录页面,它们都存在于 /ogin/** 目录下,需要提前引入这些静态资源文件
    目录中。引入这些静态资源文件后结构如下图所示 :

    获得相应的css文件 和 图片文件

    在这里插入图片描述

⑦ 自定义 用户登录 “跳转”
  • 自定义 “用户登录跳转” :

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.ResponseBody;@Controller //加入IOC容器中
    public class LoginController {  //关于跳转到登录页面有关的controller类/*** 跳转到自定义的 "登录页面"*/@GetMapping("/userLogin")public String toLoginPage() {return "/login/login";}
    }
    

    Spring Security 默认采用 Get 方式“/ogin”请求 用于向 “登录页面” 跳转 ( 可自定义跳转"登录页面"的路径,来指定”登录页面“ ),使用 Post 方式的 “/ogin”请求 用于 对登录后的数据处理

⑧ 自定义 用户登录 “控制”
  • 完成上面的准备工作后,打开 SecurityConfig 类重写 configure( HttpSecurity http )方法,实现 用户登录控制
    示例代码如下 :

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制 ( 权限管理 )*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户授权管理 (自定义用户访问控制 )http.authorizeRequests() .antMatchers("/").permitAll()  //对 "/"请求 进行放行 (进入到项目首页)//对static文件夹下的静态资源进行统一放行 (如果没有对静态资源放行,未登录的用户访问项目首页时就无法加载页面关联的静态资源文件 ).antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)/*** 自定义"用户登录控制" :*   .loginPage() : 自定义登录页面的"跳转路径" , 跳转到自己定制的"登录页面"*   .permitAll() : 无条件对请求进行放行*   .usernameParameter("name").passwordParameter("pwd") : 设置登录用户的"用户名参数" 和 "密码参数" (接受登录时提交的"用户名"和"密码")*   .defaultSuccessUrl() : 指定用户登录成功后的默认跳转地址*   .failureUrl() : 指定用户登录失败后的"跳转地址",默认跳转到 /login?error*     ---error时一个错误标识,作用是在登录失败后在登录页面进行接收判断,例如login.html示例中的${param.error},这两者必须保持一直。*///自定义"用户登录控制"http.formLogin().loginPage("/userLogin").permitAll() //自定义登录页面的"跳转路径".usernameParameter("name").passwordParameter("pwd")  //设置登录用户的"用户名参数" 和 "密码参数".defaultSuccessUrl("/") //用户登录成功后默认跳转到"项目首页".failureUrl("/userLogin?error"); //用户登录失败后默认跳转到"项目首页"}
    }    
    
实现 “自定义用户退出”
⑨ 添加自定义 “用户退出链接”
  • 要实现 自定义用户退出功能,必须某个页面定义用户退出链接或者 按钮。为了简化操作我们在之前创建的项目首页 : index.html 上方新增一个用户退出链接修改后示范文件代码如下所示 :

    <!-- th:action : 用于指定“表单提交”时 “应发送的URL”-->
    <!-- 跳转到 /mylogout路径来进行 "用户退出" , 因为不是使用默认的 /logout路径来请求 "用户退出"  所以 后端的 .logoutUrl()方法中内容也修改为 /mylogout -->
    <!-- 同时 /mylogout 请求的方法为post类型的-->
    <form th:action="/mylogout" method="post"><input th:type="submit" th:value="注销">
    </form>
    

    index.html

    <!DOCTYPE html>
    <!-- 配置开启thymeleaf模板引擎页面配置 -->
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head><meta charset="UTF-8"><title>影视直播厅</title>
    </head>
    <body><!-- index.html页面是项目首页页面,common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 -->
    <h1 align="center">欢迎进入电影网站首页</h1>
    <!-- th:action : 用于指定“表单提交”时 “应发送的URL”-->
    <!-- 跳转到 /mylogout路径来进行 "用户退出" , 因为不是使用默认的 /logout路径来请求 "用户退出"  所以 后端的 .logoutUrl()方法中内容也修改为 /mylogout -->
    <!-- 同时 /mylogout 请求的方法为post类型的-->
    <form th:action="/mylogout" method="post"><input th:type="submit" th:value="注销">
    </form>
    <hr>
    <h3>普通电影</h3>
    <ul><li><a th:href="@{/detail/common/1}">飞驰人生</a></li><li><a th:href="@{/detail/common/2}">夏洛特烦恼</a></li>
    </ul>
    <h3>VIP专享</h3>
    <ul><li><a th:href="@{/detail/vip/1}">速度与激情</a></li><li><a th:href="@{/detail/vip/2}">猩球崛起</a></li>
    </ul>
    </body>
    </html>
    

    上面的代码中新增了一个 <form>标签进行注销控制 ( 进行 “用户退出控制”),且定义的退出表单 aciton 为“/mylogout ( 默认为“/logout” ),方法post


    需要说明的是,Spring Boot 项目中引入 Spring Security 框架后会 自动开启 : CSRF 防护功能 ( 跨站请求伪造防护),用户退出必须使用 POST ; 如果关闭CSRF 防护功能,那么 可以使用任意方式HTTP 请求进行用户注销

⑩ 添加自定义 “用户退出控制”
  • SecurityConfig.java

    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制 ( 权限管理 )*/
    @Override
    protected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户授权管理 (自定义用户访问控制 )http.authorizeRequests().antMatchers("/").permitAll()  //对 "/"请求 进行放行 (进入到项目首页)//对static文件夹下的静态资源进行统一放行 (如果没有对静态资源放行,未登录的用户访问项目首页时就无法加载页面关联的静态资源文件 ).antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)/*** 自定义"用户登录控制" :*   .loginPage() : 自定义登录页面的"跳转路径" , 跳转到自己定制的"登录页面"*   .permitAll() : 无条件对请求进行放行*   .usernameParameter("name").passwordParameter("pwd") : 设置登录用户的"用户名参数" 和 "密码参数" (接受登录时提交的"用户名"和"密码")*   .defaultSuccessUrl() : 指定用户登录成功后的默认跳转地址*   .failureUrl() : 指定用户登录失败后的"跳转地址",默认跳转到 /login?error*     ---error时一个错误标识,作用是在登录失败后在登录页面进行接收判断,例如login.html示例中的${param.error},这两者必须保持一直。*///自定义"用户登录控制"http.formLogin().loginPage("/userLogin").permitAll() //自定义登录页面的"跳转路径".usernameParameter("name").passwordParameter("pwd")  //设置登录用户的"用户名参数" 和 "密码参数".defaultSuccessUrl("/") //用户登录成功后默认跳转到"项目首页".failureUrl("/userLogin?error"); //用户登录失败后默认跳转到"项目首页"/*** 自定义"用户退出控制"*/http.logout().logoutUrl("/mylogout") //指定用户退出的请求路径 (前端页面进行"用户退出时,要请求该路径,且方法为post").logoutSuccessUrl("/"); //退出成功后的重定向地址 (退出成功后跳转的地址)//用户退出后,用户会话信息是默认清除的,次情况下无需手动配置}}
    
基于 “持久化 Token方式” 实现 "记住我"功能
⑪ 创建 “存储Token信息” 的 persistent_logins “数据库表”
  • 数据库中创建一个存储 Cookie 信息的持续登录用户表 : persistent_logins ( 在之前springbootdata数据库中创建 )

    persistent_logins.sql

    在这里插入图片描述

    上述建表语句中创建了一个名为 persistent_logins数据表,其中 username 存储 用户名 series 存储 随机生成的序列号 token 存信 每次访问更新的 Token last_used 表示 最近登录日期

    需要说明的是,在 默认情况基于持久化 Token 的方式会使用上述 官方提供用户表 : persistent_logins 进行持久化 Token 的管理

⑫ 定制 “记住我” 功能 ( ①注入DataSource数据库信息 + ②调用rememberMe( )方法 + 配置JdbcTokenRepositoryImpl对象并将其加入到IOC容器中 )
  • 定制 “记住我” 功能 :

    SecurityConfig.java

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;import javax.sql.DataSource;@EnableWebSecurity  // 开启MVC security安全支持
    public class SecurityConfig extends WebSecurityConfigurerAdapter { //在该配置类中 配置①自定义用户认证(UserDetailsService) 和 ②用户授权管理自定义配置(自定义用户访问控制)/*该类为 配置好了 "UserDetailsService"身份认证信息 的类 ,使用该类来在 configure(AuthenticationManagerBuilder auth)方法中进行 "UserDetailsService"身份认证。*/@Autowiredprivate UserDetailsServiceImpl userDetailsService;/*** 用户身份认证自定义配置 ( 通过UserDetailsService的方式实现 )** 重写configure(AuthenticationManagerBuilder auth)方法 : 自定义用户认证*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码需要设置编码器 ( 添加"密码编辑器")BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();//使用UserDetailService进行"身份认证"auth.userDetailsService(userDetailsService)//设置"密码编辑器".passwordEncoder(encoder);}//注入数据库信息@Autowiredprivate DataSource dataSource;/*** 用户授权管理自定义配置 ( 自定义用户访问控制 )** 重写configure(HttpSecurity http)方法 : 自定义用户访问控制 ( 权限管理 )*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/***  .antMatchers : 开启Ant风格的路径匹配*  .permitAll() : 无条件对请求进行放行*  .antMatchers("/").permitAll() : //对/请求进行放行**  .hasRole() : 匹配用户是否是"某一个角色"*  .hasAnyRole() : 匹配用户是否是"某一个角色" 或 是 "另一个角色" ( 匹配"用户"是否有参数中的"任意角色" )*  .antMatchers("detail/common/**").hasAnyRole("common","vip") :  表示 detail/common/** 请求只有用户角色common或用户vip才允许访问/才能访问*    ---( 即ROLE_common权限 和 (即ROLE_vip权限才能访问该路径 )*  .antMatchers("/detail/vip/**").hasRole("vip") :  表示 detail/vip/** 请求只有用户vip才允许访问/才能访问**  .anyRequest() :    匹配任何请求*  .authenticated() : 匹配已经登陆认证的用户*  .and() //功能连接符*  .formLogin() : 启用Spring Security的基于"HTML表单"的 用户登录功能/用户登录页面, 同时通过该页面来进行"登录验证"*    ---简而言之 : 使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)**/// 自定义用户授权管理 (自定义用户访问控制 )http.authorizeRequests().antMatchers("/").permitAll()  //对 "/"请求 进行放行 (进入到项目首页)//对static文件夹下的静态资源进行统一放行 (如果没有对静态资源放行,未登录的用户访问项目首页时就无法加载页面关联的静态资源文件 ).antMatchers("/login/**").permitAll().antMatchers("/detail/common/**").hasAnyRole("common","vip") //普通电影,则是common和vip用户都能看.antMatchers("/detail/vip/**").hasRole("vip")  //vip电影则是vip用户才能看.anyRequest()  //匹配任何请求.authenticated()  //匹配已经登陆认证的用户.and() //功能连接符.formLogin(); //使用security提供的"默认登录"页面进行"登录验证" (如果没有指定"自定义的登录页面"的话)/*** 自定义"用户登录控制" :*   .loginPage() : 自定义登录页面的"跳转路径" , 跳转到自己定制的"登录页面"*   .permitAll() : 无条件对请求进行放行*   .usernameParameter("name").passwordParameter("pwd") : 设置登录用户的"用户名参数" 和 "密码参数" (接受登录时提交的"用户名"和"密码")*   .defaultSuccessUrl() : 指定用户登录成功后的默认跳转地址*   .failureUrl() : 指定用户登录失败后的"跳转地址",默认跳转到 /login?error*     ---error时一个错误标识,作用是在登录失败后在登录页面进行接收判断,例如login.html示例中的${param.error},这两者必须保持一直。*///自定义"用户登录控制"http.formLogin().loginPage("/userLogin").permitAll() //自定义登录页面的"跳转路径".usernameParameter("name").passwordParameter("pwd")  //设置登录用户的"用户名参数" 和 "密码参数".defaultSuccessUrl("/") //用户登录成功后默认跳转到"项目首页".failureUrl("/userLogin?error"); //用户登录失败后默认跳转到"项目首页"/*** 自定义"用户退出控制"*/http.logout().logoutUrl("/mylogout") //指定用户退出的请求路径 (前端页面进行"用户退出时,要请求该路径,且方法为post").logoutSuccessUrl("/userLogin"); //退出成功后的重定向地址 (退出成功后跳转的地址)//用户退出后,用户会话信息是默认清除的,次情况下无需手动配置/***  定制记住我功能 (基于持久化Token的方式)*/http.rememberMe()//指定"记住我"功能框中的name属性值,如果该框使用了默认的"remember-me",则该方法可以省略.rememberMeParameter("rememberme")//设置记住我功能中的token的有效期为200s.tokenValiditySeconds(200)//对Cookie信息进行"持久化管理".tokenRepository(tokenRepository());}/*** 对Cookie信息进行"持久化管理" ( 通过该方法来将Cookie信息存储到数据库中 )*/
    @Bean
    public JdbcTokenRepositoryImpl tokenRepository() {JdbcTokenRepositoryImpl jr = new JdbcTokenRepositoryImpl();jr.setDataSource(dataSource);return jr;}} 
    

    ​ 上述代码中,与 基于简单加密Token 方式 相比,在持久化 Token 方式rememberMe( )示例中加入了 tokenRepository( tokenRepository( ) )方法Cookie 信息进行持久化管理。其中的tokenRepository( )参数会返回一个设置 dataSource数据源JdbcTokenRepositorylmpl实现类对象,该对象包含操作 Token各种方法

⑬ 效果测试
  • 重启项目项目启动成功后 ,通过浏览器访问项目登录页,在 登录界面输入正确用户名密码信息,同时勾选记住我功能后跳转到项目首页 : index.html。此时查看数据库persistent logins 表数据信息效果如下图所示 :

    在这里插入图片描述


    在这里插入图片描述

    从上图可以看出项目启动后用户使用记住我功能登录时,会在 持久化数据表 : persistent_logins生成对应用户 Cookie 信息,包括 用户名 序列号 Token 最近登录时间

  • 使用浏览器 重新访问项目首页直接查看影片详情 (打开与之前登录用户权限对应的影片) 会发现 无须重新登录可以直接访问。此时,再次查看数据库persistent_ogins 表数据信息效果如下图所示

    在这里插入图片描述

    将上面 两个有Token信息 进行对比可以看出 ,在 Token 有效期内 再次自动登录时,数据库表中的 token会更新其他数据不变如果 启用浏览器 Debug 模式还会发现,第 2次登录返回Cookie 值也会随之更新,这与之前分析持久化Token 方法实现逻辑一致 的。

  • 返回 浏览器首页 首页上方的 用户“注销”链接,在 Token 有效期内进行用户手动注销。此时,再次查看数据库persistent_logins 表数据信息,效果如下图所示 :

    在这里插入图片描述

    登录用户手动实现用户退出后,数据库中 persistent_logins 表持久化用户信息也会随之删除
    如果用户是在
    Token 有效期后自动退出的,那么数据库
    persistent_logins 表持久化用户信息 不会随之删除,当用户 再次进行访问登录 时,则是 在表中新增一条持久化用户信息

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

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

相关文章

av_dump_format经验分析,FFmpeg获取媒体文件总时长(FLV获取总时长的误区)

播放器有个功能&#xff0c;当用户打开视频时&#xff0c;需要读取媒体文件的总时长等信息&#xff0c;不巧的时&#xff0c;获取FLV时总失败&#xff0c;下面来具体分析下FLV和MP4获取总时长的原因和区别&#xff1a; 播放器有个获取MediaInfo的接口&#xff0c;功能如下&am…

echarts全局设置饼图的颜色

&#x1f337;第一步 在js文件中写入你需要的颜色 这里的颜色也可以写渐变的 &#x1f337;下一步 在main.is中引用全局挂载 &#x1f337;最后一步 在初始化的时候加一个macarons即可 &#x1f337;第一步 在js文件中写入你需要的颜色 这里的颜色也可以写渐变的 (functi…

探索k8s集群中kubectl的陈述式资源管理

一、k8s集群资源管理方式分类 1.1陈述式资源管理方式&#xff1a;增删查比较方便&#xff0c;但是改非常不方便 使用一条kubectl命令和参数选项来实现资源对象管理操作 即通过命令的方式来实 1.2声明式资源管理方式&#xff1a;yaml文件管理 使用yaml配置文件或者json配置文…

【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法

文章目录 前言 Timer中断调度 Event中断调度 StateFlow调度 分析和应用 总结 参考资料 前言 近期在一些嵌入式系统开发项目中&#xff0c;在使用嵌入式处理器时&#xff0c;遇到了挺多费时费力的事情。所以利用晚上和周末时间&#xff0c;在这些方面深入研究了一下&…

Joomla 3.7.0 (CVE-2017-8917) SQL注入漏洞环境

1 漏洞概述 Joomla是一个基于PHP的内容管理系统&#xff08;CMS&#xff09;&#xff0c;广泛应用于各类网站。2017年&#xff0c;Joomla 3.7.0版本被发现存在SQL注入漏洞&#xff08;CVE-2017-8917&#xff09;&#xff0c;攻击者可以利用该漏洞对数据库进行未授权查询或操作…

The Sandbox 和 Bitkub 联手增强东南亚元宇宙中心

作为去中心化游戏虚拟世界和区块链平台的先驱&#xff0c;The Sandbox 正与泰国领先的区块链网络 Bitkub Blockchain Technology Co., Ltd. 展开创新合作。双方合作的目的是将Bitkub元宇宙的影响力扩展到The Sandbox&#xff0c;建立一个元宇宙中心&#xff0c;向用户承诺从 Bi…

C# GetManifestResourceStream 获取项目资源为null解决方案(亲测)

GetManifestResourceStream 获取项目资源为null 使用Stream s assembly.GetManifestResourceStream(Assembly.GetExecutingAssembly().GetName().Name resourceName) 获取资源文件&#xff0c;返回流为null&#xff0c;如图所示&#xff1a; 解决方案 设置资源文件的 属性&…

<MySQL> 【数据类型】

目录 一、数据类型一览 二、整型 &#xff08;一&#xff09;INT &#xff08;二&#xff09;BIT 三、浮点数 &#xff08;一&#xff09;FLOAT &#xff08;二&#xff09;DECIMAL &#xff08;三&#xff09;如何选择 四、字符 &#xff08;一&#xff09;CHAR &a…

超简单白话文机器学习 - 支持向量机SVM(含算法讲解,公式全解,手写代码实现,调包实现)

1. 支持向量机SVM 支持向量机&#xff0c;因其英文名为support vector machine&#xff0c;故一般简称SVM&#xff0c;通俗来讲&#xff0c;它是一种二类分类模型&#xff0c;其基本模型定义为特征空间上的间隔最大的线性分类器&#xff0c;其学习策略便是间隔最大化&#xff…

优雅草便民工具v2.0.4更新

优雅草便民工具v2.0.4更新 优雅草便民工具v2.0.4更新 2024年5月20日v2.0.4更新优雅草便民工具youyacao-tools-增加淘宝联想词功能和ai绘画功能apk下载 https://fenfacun.youyacao.com/tools204.apk 介绍 优雅草便民工具是一款由成都市一颗优雅草科技有限公司打造的便民查询公益…

JVM(8):虚拟机性能分析和故障解决工具之jinfo工具

1 jinfo(Configuration Info forJava)作用 实时地查看和调整虚拟机各项参数 2 命令格式 jinfo [options] 参数解释&#xff1a; 第一个参数&#xff1a;options no option&#xff1a;输出全部的参数和系统属性-flag name&#xff1a;输出对应名称的参数-flag [|-]name&am…

6. Spring面试题汇总

Java全栈面试题汇总目录-CSDN博客 1. 什么是Spring框架? Spring是一种轻量级开发框架&#xff0c;旨在提高开发人员的开发效率以及系统的可维护性。 我们一般说Spring框架指的都是Spring Framework&#xff0c;它是很多模块的集合&#xff0c;使用这些模块可以很方便地协助…

如何轻松访问 Android 手机和平板电脑上的内部存储

概括 在数字设备领域&#xff0c;我们的智能手机充当虚拟金库&#xff0c;在其范围内存储个人数据、珍贵记忆和重要信息的宝库。因此&#xff0c;我们将指导您如何访问 Android 上的内部存储&#xff0c;确保您可以安全、轻松地检查内部文件系统并管理文件。同时&#xff0c;您…

无人机监测系统:天空之眼,精准掌握地球脉动

在当今信息化快速发展的时代&#xff0c;无人机技术以其独特的优势&#xff0c;正在成为资源调查、环境监测和规划支持的重要工具。无人机监测系统通过搭载多种传感器和设备&#xff0c;能够快速、高效地获取地表信息&#xff0c;为决策提供科学依据。 项目背景 随着全球环境…

Llama 3 CPU推理优化指南

备受期待的 Meta 第三代 Llama 现已发布&#xff0c;我想确保你知道如何以最佳方式部署这种最先进的 (SoTA) LLM。在本教程中&#xff0c;我们将重点介绍如何执行仅权重量化 (WOQ) 来压缩 8B 参数模型并改善推理延迟&#xff0c;但首先&#xff0c;让我们讨论一下 Meta Llama 3…

CSS基础(第五天)

目录 定位 为什么需要定位 定位组成 边偏移 静态定位 static&#xff08;了解&#xff09; 相对定位 relative 绝对定位 absolute&#xff08;重要&#xff09; 子绝父相的由来 固定定位 fixed &#xff08;重要&#xff09; 粘性定位 sticky&#xff08;了解&#xff…

常见算法(2)

1.冒泡排序 定义&#xff1a;相邻的数据两两比较&#xff0c;小的放前面&#xff0c;大的放后面。 public class test {public static void main(String [] arg) {int [] arr {2,4,5,3,6,1};//冒泡排序&#xff0c;排序次数arr.length-1for(int i0;i<arr.length-1;i) {f…

二叉树(详解)

在了解二叉树之前呢我们先来了解一下树形结构&#xff0c;因为二叉树就是树形结构的一种特殊情况&#xff0c;它有这非常好的性质&#xff0c;是很常用的一种结构。 目录 一.什么是树形结构&#xff1f; 二.树形结构常见的名词 三.树的存储 四.二叉树 1.二叉树的概念 2.…

私有云和多云管理平台 | Cloudpods v3.10.15 正式发布

功能优化 【主机】裸金属详情页增加部分属性信息【监控】优化告警策略&#xff0c;支持同时设置多监控指标【主机】支持透传设备自动探测【主机】LVM 块存储支持快照【监控】简化 Telegraf 容器的挂载点【主机】新建 VMware 支持同时填写备注信息【存储】KVM 支持对接 LVM 存储…

ubuntu2404 AMD64 编译并安装virtualbox7.0.18

ubuntu2404 AMD64 编译并安装virtualbox7.0.18 0、官方参考文档&#xff1a; https://www.virtualbox.org/wiki/Linux%20build%20instructions 1、下载源码&#xff1a; $ wget https://download.virtualbox.org/virtualbox/7.0.18/VirtualBox-7.0.18.tar.bz2 2、安装库&…