Spring Boot | SpringBoot 中 自定义 “用户授权管理“ : 自定义“用户访问控制“、自定义“用户登录控制“

目录:

    • 一、SpringBoot 中 自定义 "用户授权管理" ( 总体内容介绍 ) :
    • 二、 自定义 "用户访问控制" ( 通过 "HttpSecurity类" 的 authorizeRequests( )方法来实现 "自定义用户访问控制" ) :
      • 1.基础项目文件准备
      • 2.实现 "自定义身份认证" ( UserDetailsService身份认证 )
        • ① service层中类 获取 "用户基本信息" 和 "用户权限信息"
        • ② "自定义类" 实现 "UserDetailsService接口" , 在该类中 封装 "用户身份认证信息"
        • ③ "SecurityConfig配置类" 中 实现 "自定义 身份认证"
      • 3.实现 "自定义用户访问"
        • ④ "SecurityConfig配置类" 中 实现 "自定义 用户访问控制"
        • ⑤ controller层中 实现 "路径访问跳转"
        • ⑥ 效果测试
    • 三、 自定义 "用户登录控制" ( 通过 "HttpSecurity类" 的 formLogin( )方法来实现 "自定义用户用户登录控制" ) :
      • 1.基础项目文件准备
      • 2.实现 "自定义身份认证" ( UserDetailsService身份认证 )
        • ① service层中类 获取 "用户基本信息" 和 "用户权限信息"
        • ② "自定义类" 实现 "UserDetailsService接口" , 在该类中 封装 "用户身份认证信息"
        • ③ "SecurityConfig配置类" 中 实现 "自定义 身份认证"
      • 3.实现 "自定义用户访问"
        • ④ "SecurityConfig配置类" 中 实现 "自定义 用户访问控制"
        • ⑤ controller层中 实现 "路径访问跳转"
      • 4.实现 "自定义用户登录"
        • ⑥ 自定义 用户登录 "页面"
        • ⑦ 自定义 用户登录 "跳转"
        • ⑧ 自定义 用户登录 "控制"
        • ⑨ 效果测试

在这里插入图片描述

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

该文章参考学习教材为:
《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类” 的 authorizeRequests( )方法来实现 “自定义用户访问控制” ) :

  • 实际生产中网站访问多基于 HTTP 请求通过 WebSecurityConfigurerAdapter 类configure( HttpSecurity http )方法 中的 HttpSecurity 类中的 authorizeRequests( )方法可以实现 "自定义用户访问控制" 。

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

    方法 ( 实现 “用户访问控制” 的效果的 方法 )描述
    antMatchers ( java.lang.StringantPatterns ) 方法开启 Ant风格路径匹配
    mvcMatchers ( java.lang.Stringpatterns )方法开启 MVC风格路径匹配。( 与Ant风格类似 )
    regexMatchers ( java.lang.StringregexPatterns )方法开启 “正则表达式” 风格路径匹配
    and( ) 方法功能连接符
    anyRequest( ) 方法匹配任何请求
    rememberMe( ) 方法开启 记住我功能
    access( String attribute )方法匹配 给定的 SpEL 表达式计算结果是否为 true
    hasRole( String role )方法匹配 “用户” 是否有 “某一个角色”
    hasAnyRole( String … roles )方法匹配 “用户” 是否有参数中的 “任意角色”
    更多操作方法描述
    hasAuthority( String authority )方法匹配 “用户” 是否有 “某一个权限”
    hasAnyAuthority( String … authorities )方法匹配 “用户” 是否有参数中的 “任意权限”
    authenticated( ) 方法匹配 已经登录认证用户
    fullyAuthenticated( ) 方法匹配 完整登录认证用户 ( 非rememberMe 登录用户 )。
    haslpAddress( String ipaddressExpression )方法匹配 某IP 地址访问请求
    permitAll( ) 方法无条件 对请求 进行 放行
  • 自定义 "用户访问控制" - 案例 代码如下所示

1.基础项目文件准备

  • 创建项目 :

    在这里插入图片描述

  • 项目结构 :

    在这里插入图片描述

  • 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>
    

2.实现 “自定义身份认证” ( 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);}
    }    
    

3.实现 “自定义用户访问”

④ “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;}}
    
⑥ 效果测试
  • 项目启动成功后,通过浏览器访问 http://localhost:8080/ 项目首页,效果如下图所示 :

    在这里插入图片描述

    上图可以看出直接访问http://localhost:8080/”可以进入 项目首页 ,这是因为 自定义用户访问控制中 ,对 "/" 的请求是 直接放行的,此图也说明了 "自定义的用户访问控制 生效 了。

  • 项目首页 单击普通电影 或者 VIP 专享电影 名称 査询电影详情,效果如下图所示 :

    在这里插入图片描述

    上图可以看出,在项目首页访 问影片详情 (实质是请求 URL 跳转如“/detai/common/1”),会直接被 自定义的访问控制拦截跳转默认用户登录界面 ( 没有没对该请求进行放行,所以肯定是要进入登录页面验证登录信息的 )。在此登录界面输入正确的用户名和密码信息 (如果访问的是普通电影,可以输入用户名shitou,密码 123456),效果如下图所示 :

    在这里插入图片描述

    shitou,123456 ,能访问到 飞驰人生这个电影,说明其有 common用户权限,此时点击左上角的“返回”链接,会再次回到项目首页。此时,之前登录的普通用户 shitou 还处于登录状态,再次单击 VIP 专享电影名称查看影片详情效果如下图所示 :

    在这里插入图片描述

    上图可以看出,登录后的 普通用户 shitou,在查看 VIP 电影 详情时,页面会出现 403 Forbidden(禁止访问)错误信息 ( 因为账户权限不足 ),而控制台不会报错。

    上述演示结果,说明了示例中配置的用户访问控制不同的请求拦截也生效了。另外,当前示例没有配置完善的用户注销功能,所以登录一个用户后要切换其他用户的话将浏览器重启再次使用新账号登录即可。

三、 自定义 “用户登录控制” ( 通过 “HttpSecurity类” 的 formLogin( )方法来实现 “自定义用户用户登录控制” ) :

  • 通过前面几个示例演示可以发现项目中没有配置 用户登录页面登录处理方法 ( Security框架中 并没有对用户登录控制” 进行 自定义 ),但是 演示过程 中却 提供了一个默认用户登录页面,并且进行了自动登录处理,这就是 Spring Security 提供的 默认登录处理机制 ( 默认的 “用户登录” 机制 )。

  • 实际开发中,通常要求 定制更美观用户登录页面,并 配置有更好错误提示信息,此时需要 自定义 “用户登录控制”

  • 自定义 “用户登录控制”具体实际操作为 : 通过 WebSecurityConfigurerAdapter 类configure( HttpSecurity http )方法 中的 HttpSecurity 类中的 formLogin( )方法可以实现 "自定义用户访问控制" 。

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

    方法 ( 实现 “用户登录控制” 的效果的 方法 )描述
    loginPage( String loginPage )方法设置 用户登录页面跳转路径默认get请求/ogin
    ( 可
    自定义 “设置”
    跳转路径” , 来 跳转自定义的"登录界面" )
    successForwardUrl( String forwardUrl )方法用户登录成功 后的 重定向地址
    successHandler( AuthenticationSuccessHandler successHandler )方法设置 用户登录成功后的处理
    defaultSuccessUrl( String defaultSuccessUrl )方法设置 用户登录成功默认跳转地址
    : .defaultSuccessUrl(“/”) :指定
    了用户登录成功
    默认跳转到 “项目首页”
    failureForwardUrl( String forwardUrl )方法设置 用户登录失败 后的 重定向地址
    failureUrl( String authenticationFailureUrl )方法设置 用户登录失败 后的 跳转地址默认
    /ogin?error
    failureHandler( AuthenticationFailureHandler authenticationFailureHandler )方法设置 用户登录失败 后的 错误处理
    usernameParameter( String usernameParameter )方法设置 登录用户用户名参数默认username
    passwordParameter( String passwordParameter )方法设置 登录用户密码参数默认password
    更多方法
    loginProcessingUrl( String loginProcessingUrl )方法设置 登录表单提交路径默认post 请求的 /login
    permitAll( )方法设置 无条件对请求进行放行

1.基础项目文件准备

  • 创建项目 :

    在这里插入图片描述

  • 项目结构 :

    在这里插入图片描述

  • 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>
    

2.实现 “自定义身份认证” ( 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);}
    }    
    

3.实现 “自定义用户访问”

④ “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;}}
    

4.实现 “自定义用户登录”

⑥ 自定义 用户登录 “页面”
  • 要实现 自定义用户登录功能,首先必须根据需要自定义一个用户登录页面。在项目的 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"); //用户登录失败后默认跳转到"项目首页"}
    }    
    
⑨ 效果测试
  • 重启项目,项目 启动成功后,通过浏览器访问“http://localhost8080/”,会直接进入项目首页。在项目首页,单击普通电影或者 VIP 专享电影名称查询电影详情,效果如下图所示 :

    在这里插入图片描述

    此时输入错误的用户名和,会返回到当前登录页面,此时的请求路径上已经携带了 error 错误标识,并且 登录页面也有 错误信息提示,说明自定义登录失败设置成功

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

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

相关文章

4. 分布式链路追踪客户端工具包Starter设计

前言 本文将从零搭建分布式链路追踪客户端工具包的Starter&#xff0c;并将在后续文章中逐步丰富支持的场景。这里首先将搭建一个最基础的Starter&#xff0c;能提供的功能和1. 看完这篇文章我奶奶都懂Opentracing了一文中的示例demo类似。 相关版本依赖如下。 opentracing-…

【SQL】SQL常见面试题总结(4)

目录 1、空值处理1.1、统计有未完成状态的试卷的未完成数和未完成率1.2、0 级用户高难度试卷的平均用时和平均得分 2、高级条件语句2.1、筛选限定昵称成就值活跃日期的用户&#xff08;较难&#xff09;2.2、筛选昵称规则和试卷规则的作答记录&#xff08;较难&#xff09;2.3、…

SmartEDA助力电工基础实验:打造高效、智能的学习新体验

在电工基础实验的教学与学习中&#xff0c;传统的实验设备往往存在着操作复杂、数据处理繁琐等问题&#xff0c;给学生的学习带来了不小的挑战。然而&#xff0c;随着科技的不断发展&#xff0c;一种名为SmartEDA的智能电工实验辅助设备正逐渐走入课堂&#xff0c;以其高效、智…

Es6-对象新增了哪些扩展?

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Javascript篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Javascript篇专栏内容:Es6-对象新增了哪些扩展&#xff1f; 目录 一、参数 二、属性 函数的length属性 …

数据结构-栈(带图)

目录 栈的概念 画图理解栈 栈的实现 fun.h fun.c main.c 栈的概念 栈&#xff08;Stack&#xff09;是一种基本的数据结构&#xff0c;其特点是只允许在同一端进行插入和删除操作&#xff0c;这一端被称为栈顶。遵循后进先出&#xff08;Last In, First Out, LIFO&#…

【论文粗读|arXiv】GaSpCT: Gaussian Splatting for Novel CT Projection View Synthesis

Abstract 本文提出了一种新颖的视图合成和3D场景表示方法&#xff0c;用于为计算机断层扫描&#xff08;CT&#xff09;生成新的投影视图。 方法采用了Gaussian Splatting 框架&#xff0c;基于有限的2D图像投影集&#xff0c;无需运动结构&#xff08;SfM&#xff09;方法&am…

Swift 5.9 中 if 与 switch 语句简洁新语法让撸码更带劲

概览 在实际代码开发中&#xff0c;可能初学 Swift 语言的小伙伴们在撸码时最常用的得数 if 和 switch…case 条件选择语句了。不过在某些场景下它们显得略有那么一丢丢“矫揉造作”&#xff0c;还好从 Swift 5.9 开始苹果知趣的为其简化了语法且增强了它们的表现力。 在本篇…

Vitis HLS 学习笔记--优化本地存储器访问瓶颈

目录 1. 简介 2. 代码解析 2.1 原始代码 2.2 优化后 2.3 分析优化措施 3. 总结 1. 简介 在Vitis HLS中&#xff0c;实现II&#xff08;迭代间隔&#xff09; 1是提高循环执行效率的关键。II1意味着每个时钟周期都可以开始一个新的迭代&#xff0c;这是最理想的情况&…

HNCTF ——baby_python

H&NCTF 2024 官方WP (qq.com) OpCodes Pickle.jl (juliahub.com) nc之后 PS D:\ForCode\pythoncode\.idea> nc hnctf.yuanshen.life 33267 # Python 3.10.12 from pickle import loads main b"\x80\x04ctypes\nFunctionType\n(ctypes\nCodeType\n(I1\nI0\nI0\n…

【Vim】

一、什么是Vim&#xff1f; Vim 是一个历史悠久的文本编辑器&#xff0c;可以追溯到 qed。 Bram Moolenaar 于 1991 年发布初始版本。Vim 有着悠久的历史;它起源于 Vi 编辑器&#xff08;1976 年&#xff09;&#xff0c;至今仍在开发中。(Vim has a rich history; it origina…

css+html 爱心❤

效果 代码实现 html <div class"main"><div class"aixin"></div></div>css .main {transform: rotate(-45deg);}.aixin {height: 100px;width: 100px;background-color: red;margin: auto;margin-top: 200px;position: relativ…

MySQL第一次作业(基本操作)

目录 一、登陆数据库 二、创建数据库zoo 三、修改数据库zoo字符集为gbk 四、选择当前数据库为zoo 五、查看创建数据库zoo信息 六、删除数据库zoo 一、登陆数据库 指令&#xff1a; mysql -u root -p 二、创建数据库zoo 指令&#xff1a; create database zoo; 三、修改数…

基于PHP+MySQL组合开发的多用户自定义商城系统源码 附带源代码包以及搭建教程

系统概述 互联网技术的飞速发展&#xff0c;电子商务已成为人们日常生活中不可或缺的一部分。商城系统作为电子商务的核心&#xff0c;其开发技术和用户体验直接影响着电商平台的竞争力和用户满意度。本文旨在介绍一个基于PHPMySQL组合开发的多用户自定义商城系统&#xff0c;…

C++学习~~string类

1.STL简单介绍 &#xff08;1&#xff09;标准模版库&#xff0c;是C里面的标准库的一部分&#xff0c;C标准库里面还有其他的东西&#xff0c;但是我们不经常使用&#xff0c;我们经常使用的还是STL这个标准库部分。 &#xff08;2&#xff09;六大件&#xff1a;仿函数&…

C# WinForm —— 16 MonthCalendar 介绍

1. 简介 可以选择单个日期&#xff0c;也可以选择一段日期&#xff0c;在选择时间范围上 比较适用&#xff0c;但不能跨月份选择日期范围 在直观上&#xff0c;可以快速查看、选择日期/日期范围 2. 常用属性 属性解释(Name)控件ID&#xff0c;在代码里引用的时候会用到,一般…

Uni-app基础知识

uni-app组成和跨端原理 | uni-app官网uni-app,uniCloud,serverless,uni-app组成和跨端原理,基本语言和开发规范,编译器,运行时&#xff08;runtime&#xff09;,逻辑层和渲染层分离https://uniapp.dcloud.net.cn/tutorial/1.adb连接模拟器 找到adb所在位置&#xff08;一般在hb…

C++ 程序员常用的VScode的插件

vscode中好用的插件 Better CommentsBookmarksC/C ThemeChinese (Simplified) (简体中文) Language Pack for Visual Studio CodeclangdClang-FormatCodeLLDBCMakeCMake ToolsCode RunnerCode Spell CheckerCodeSnapColor Highlightvscode-mindmapDraw.io IntegrationError Len…

对称加密算法的应用场景

随着信息技术的飞速发展&#xff0c;数据安全成为了至关重要的议题。在保护数据传输和存储的过程中&#xff0c;加密算法扮演着不可或缺的角色。其中&#xff0c;对称加密算法&#xff0c;由于其高效性和易用性&#xff0c;被广泛应用于各种场景中。本文将探讨对称加密算法的主…

Kubernets多master集群构建负载均衡

前言 在构建 Kubernetes 多 Master 集群时&#xff0c;实现负载均衡是至关重要的一环。通过多台 Master 节点配合使用 Nginx 和 Keepalived 等工具&#xff0c;可以有效提高集群的可靠性和稳定性&#xff0c;确保系统能够高效运行并有效应对故障。接下来将介绍如何配置这些组件…

物业水电抄表系统的全面解析

1.系统概述 物业水电抄表系统是现代物业管理中的重要组成部分&#xff0c;它通过自动化的方式&#xff0c;实时监控和记录居民或企业的水电使用情况&#xff0c;极大地提高了工作效率&#xff0c;降低了人工抄表的错误率。该系统通常包括数据采集、数据传输、数据分析和数据展…