网站做点击广告是怎么回事,高安网站找工作做面点事,单页设计图片,网站集群建设相关的招标目录: 一、SpringBoot 中 自定义 用户授权管理 ( 总体内容介绍 )二、实现 记住我 功能 ( 通过 HttpSecurity类 的 rememberMe( )方法来实现 记住我 功能 ) :2.1 基于 简单加密 Token 的方式 ( 实现 记住我用户授权管理 ( 总体内容介绍 )二、实现 记住我 功能 ( 通过 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 针对 “记住我” 功能 提供了 两种实现 : 一种 是 “简单地” 使用加密来保证基于 Cookie 中 Token 的安全 ; 另一种 是通过 数据库 或 其他持久化机制来保存生成的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 version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentgroupIdcom.itheima/groupIdartifactIdchapter07/artifactIdversion0.0.1-SNAPSHOT/versionnamechapter07/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependencies!-- Security与Thymeleaf整合实现前端页面安全访问控制 --dependencygroupIdorg.thymeleaf.extras/groupIdartifactIdthymeleaf-extras-springsecurity5/artifactId/dependency!-- JDBC数据库连接启动器 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependency!-- MySQL数据连接驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependency!-- Redis缓存启动器--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- Spring Data JPA操作数据库 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependency!-- Spring Security提供的安全管理依赖启动器 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies!-- build--!-- plugins--!-- plugin--!-- groupIdorg.springframework.boot/groupId--!-- artifactIdspring-boot-maven-plugin/artifactId--!-- /plugin--!-- /plugins--!-- /build--/project导入 Sql文件 ( 创建数据库表 ) security.sql 创建实体类 : Customer.java : import javax.persistence.*;Entity(name t_customer)public class Customer {IdGeneratedValue(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 {IdGeneratedValue(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 JpaRepositoryCustomer,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 JpaRepositoryAuthority,Integer {//根据 username 来查询权限信息Query(value select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_idc.id and ca.authority_ida.id and c.username ?1,nativeQuery true)public ListAuthority 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 RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplateObject, 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.urljdbc:mysql://localhost:3306/springbootdata?serverTimezoneUTC
spring.datasource.usernameroot
spring.datasource.passwordrootspring.redis.host127.0.0.1
spring.redis.port6379
spring.redis.password123456spring.thymeleaf.cachefalse创建 html资源文件 : index.html 页面是 项目首页页面common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面。 index.html !DOCTYPE html
!-- 配置开启thymeleaf模板引擎页面配置 --
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8title影视直播厅/title
/head
body!-- index.html页面是项目首页页面common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 --
h1 aligncenter欢迎进入电影网站首页/h1
hr
h3普通电影/h3
ullia th:href{/detail/common/1}飞驰人生/a/lilia th:href{/detail/common/2}夏洛特烦恼/a/li
/ul
h3VIP专享/h3
ullia th:href{/detail/vip/1}速度与激情/a/lilia th:href{/detail/vip/2}猩球崛起/a/li
/ul
/body
/html1.html : ( 其他三个页面 以此类推 ) !DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/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 customernull;//从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 ListAuthority getCustomerAuthority(String username){ListAuthority authoritiesnull;//尝试从Redis数据库中获得缓存数据Object o redisTemplate.opsForValue().get(authorities_username);if(o!null){authorities(ListAuthority)o;}else {//没找到缓存数据则从Mysql数据库中查询数据authoritiesauthorityRepository.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);ListAuthority 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对象并将其放入一个新的列表中*/// 对用户权限信息进行封装ListSimpleGrantedAuthority 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 xmlnshttp://www.w3.org/1999/xhtml xmlns:thhttp://www.thymeleaf.org
headmeta http-equivContent-Type contenttext/html; charsetUTF-8title用户登录界面/titlelink th:href{/login/css/bootstrap.min.css} relstylesheetlink th:href{/login/css/signin.css} relstylesheet
/head
body classtext-center
!-- form表单请求跳转路径为 : /userLogin --
form classform-signin th:action{/userLogin} th:methodpost !-- img标签--img classmb-4 th:src{/login/img/login.jpg} width72px height72pxh1 classh3 mb-3 font-weight-normal请登录/h1!-- 用户登录错误信息提示框 --!-- th:if 根据条件的真假决定是否渲染 HTML 元素 真则渲染假则不渲染 (有该参数值则渲染,没有该参数值则不渲染) --!-- 有该参数值则渲染该div否则就不渲染该div --div th:if${param.error}stylecolor: red;height: 40px;text-align: left;font-size: 1.1em!-- img标签 --img th:src{/login/img/loginError.jpg} width20px用户名或密码错误请重新登录/div!-- 用户名参数名为 : name , 密码的参数名为: pws --input typetext namename classform-controlplaceholder用户名 required autofocusinput typepassword namepwd classform-controlplaceholder密码 requiredbutton classbtn btn-lg btn-primary btn-block typesubmit 登录/buttonp classmt-5 mb-3 text-mutedCopyright© 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 methodpostinput th:typesubmit th:value注销
/formindex.html !DOCTYPE html
!-- 配置开启thymeleaf模板引擎页面配置 --
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8title影视直播厅/title
/head
body!-- index.html页面是项目首页页面common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 --
h1 aligncenter欢迎进入电影网站首页/h1
!-- th:action : 用于指定“表单提交”时 “应发送的URL”--
!-- 跳转到 /mylogout路径来进行 用户退出 , 因为不是使用默认的 /logout路径来请求 用户退出 所以 后端的 .logoutUrl()方法中内容也修改为 /mylogout --
!-- 同时 /mylogout 请求的方法为post类型的--
form th:action/mylogout methodpostinput th:typesubmit th:value注销
/form
hr
h3普通电影/h3
ullia th:href{/detail/common/1}飞驰人生/a/lilia th:href{/detail/common/2}夏洛特烦恼/a/li
/ul
h3VIP专享/h3
ullia th:href{/detail/vip/1}速度与激情/a/lilia 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 文件中 新增一个 “记住我” 功能勾选框示例代码如下 : !-- 实现记住我功能 --divlabel!--Security提供的记住我功能的name属性默认值为: remember-me , 下面的name属性值为 : rememberme ,因此后端的 rememberMeParameter()方法中也要进行对应的修改-- input th:typecheckbox namerememberme记住我/label/divlogin.html : !DOCTYPE html
!-- 配置开启thymeleaf模板引擎页面配置 --
html xmlnshttp://www.w3.org/1999/xhtml xmlns:thhttp://www.thymeleaf.org
headmeta http-equivContent-Type contenttext/html; charsetUTF-8title用户登录界面/titlelink th:href{/login/css/bootstrap.min.css} relstylesheetlink th:href{/login/css/signin.css} relstylesheet
/head
body classtext-center
!-- form表单请求跳转路径为 : /userLogin --
form classform-signin th:action{/userLogin} th:methodpost !-- img标签--img classmb-4 th:src{/login/img/login.jpg} width72px height72pxh1 classh3 mb-3 font-weight-normal请登录/h1!-- 用户登录错误信息提示框 --!-- th:if 根据条件的真假决定是否渲染 HTML 元素 真则渲染假则不渲染 (有该参数值则渲染,没有该参数值则不渲染) --!-- 有该参数值则渲染该div否则就不渲染该div --div th:if${param.error}stylecolor: red;height: 40px;text-align: left;font-size: 1.1em!-- img标签 --img th:src{/login/img/loginError.jpg} width20px用户名或密码错误请重新登录/div!-- 用户名参数名为 : name , 密码的参数名为: pws --input typetext namename classform-controlplaceholder用户名 required autofocusinput typepassword namepwd classform-controlplaceholder密码 required!-- 实现记住我功能 --divlabel!--Security提供的记住我功能的name属性默认值为: remember-me , 下面的name属性值为 : rememberme ,因此后端的 rememberMeParameter()方法中也要进行对应的修改--input th:typecheckbox namerememberme记住我/label/divbutton classbtn btn-lg btn-primary btn-block typesubmit 登录/buttonp classmt-5 mb-3 text-mutedCopyright© 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 “发送给” 客户端浏览器。 ----( 将 记住我功能 相关的信息 在 数据库存储一份同时 也将 其组合成 一个 Cookie将 该 Cookie 向客户端浏览器发送一份 )(2) 当用户 再次访问系统时首先检查 客户端 携带的 Cookie信息如果对应 Cookie信息 中 包含 的 username、序列号和 Token 与数据库 中保存的一致则 “验证通过” 并 “自动登录”同时 系统 将 重新生成一个新的 Token 替换数据库中旧的 Token同时也会组合一个 新的Cookie 并将其 发给 “客户端”。 ----( 当 用户再次访问系统时将 客户端 中的 Cookie信息 和 与 数据库 中的Token信息进行 比对如果一致则代表 验证通过并“ 自动登录” 同时系统将生成一个新的Token “替代” 数据库中 旧的Token 同时还会组合一个 新的Cookie 并 将其发给 “客户端” )(3) 如果 Cookie 中的 Token 不匹配 即如果 客户端中的 Token信息与 数据库中的 Token信息不一致 则很有可能是用户的 Cookie 被盗用了。由于盗用者使用初次生成的 Token 进行登录时会 生成一个新的 Token所以当 用户在 不知情时 再次登录就会出现 Token 不匹配的情况这时就需要重新登录并生成新的Token 和 Cookie。同时 Spring Securiy 就可以发现 Cookie 可能被盗用的情况它将 删除数据库 中与 当前用户相关 的 所有Token 记录这样 盗用者 使用 原有的Cookie 将 不能再次登录。(4) 如果 用户访问系统 时 没有携带Cookie或者包含的 username 和 序列号 与 数据库中 保存的不一致那么将 会 引导用户到 登录页面。 从以上实现逻辑可以看出持久化Token 的方式比 简单加密Token 的方式 相对更加安全。使用 简单加密 Token 的方式一旦用户的 Cookie 被盗用在 Token 有效期内盗用者可以无限制地自动登录进行恶意操作直到用户本人发现并修改密码才会避免这种问题 ; 而使用 持久化 Token 的方式 相对安全用户 每登录一次都会生成新的Token和 Cookie但也给盗用者留下了在用户进行第2次登录前进行恶意操作的机会只有在用户进行第2次登录并更新Token 和 Cookie 时才会避免这种问题。因此总体来讲对于 安全性要求很高的应用不推荐使用Bemember-Me 功能。 基础项目文件准备 创建项目 : 项目结构 : pom.xml ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.1.3.RELEASE/versionrelativePath/ !-- lookup parent from repository --/parentgroupIdcom.itheima/groupIdartifactIdchapter07/artifactIdversion0.0.1-SNAPSHOT/versionnamechapter07/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependencies!-- Security与Thymeleaf整合实现前端页面安全访问控制 --dependencygroupIdorg.thymeleaf.extras/groupIdartifactIdthymeleaf-extras-springsecurity5/artifactId/dependency!-- JDBC数据库连接启动器 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependency!-- MySQL数据连接驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependency!-- Redis缓存启动器--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- Spring Data JPA操作数据库 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependency!-- Spring Security提供的安全管理依赖启动器 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependencies!-- build--!-- plugins--!-- plugin--!-- groupIdorg.springframework.boot/groupId--!-- artifactIdspring-boot-maven-plugin/artifactId--!-- /plugin--!-- /plugins--!-- /build--/project导入 Sql文件 ( 创建数据库表 ) security.sql 创建实体类 : Customer.java : import javax.persistence.*;Entity(name t_customer)public class Customer {IdGeneratedValue(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 {IdGeneratedValue(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 JpaRepositoryCustomer,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 JpaRepositoryAuthority,Integer {//根据 username 来查询权限信息Query(value select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_idc.id and ca.authority_ida.id and c.username ?1,nativeQuery true)public ListAuthority 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 RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplateObject, 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.urljdbc:mysql://localhost:3306/springbootdata?serverTimezoneUTC
spring.datasource.usernameroot
spring.datasource.passwordrootspring.redis.host127.0.0.1
spring.redis.port6379
spring.redis.password123456spring.thymeleaf.cachefalse创建 html资源文件 : index.html 页面是 项目首页页面common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面。 index.html !DOCTYPE html
!-- 配置开启thymeleaf模板引擎页面配置 --
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8title影视直播厅/title
/head
body!-- index.html页面是项目首页页面common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 --
h1 aligncenter欢迎进入电影网站首页/h1
hr
h3普通电影/h3
ullia th:href{/detail/common/1}飞驰人生/a/lilia th:href{/detail/common/2}夏洛特烦恼/a/li
/ul
h3VIP专享/h3
ullia th:href{/detail/vip/1}速度与激情/a/lilia th:href{/detail/vip/2}猩球崛起/a/li
/ul
/body
/html1.html : ( 其他三个页面 以此类推 ) !DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/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 customernull;//从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 ListAuthority getCustomerAuthority(String username){ListAuthority authoritiesnull;//尝试从Redis数据库中获得缓存数据Object o redisTemplate.opsForValue().get(authorities_username);if(o!null){authorities(ListAuthority)o;}else {//没找到缓存数据则从Mysql数据库中查询数据authoritiesauthorityRepository.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);ListAuthority 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对象并将其放入一个新的列表中*/// 对用户权限信息进行封装ListSimpleGrantedAuthority 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 xmlnshttp://www.w3.org/1999/xhtml xmlns:thhttp://www.thymeleaf.org
headmeta http-equivContent-Type contenttext/html; charsetUTF-8title用户登录界面/titlelink th:href{/login/css/bootstrap.min.css} relstylesheetlink th:href{/login/css/signin.css} relstylesheet
/head
body classtext-center
!-- form表单请求跳转路径为 : /userLogin --
form classform-signin th:action{/userLogin} th:methodpost !-- img标签--img classmb-4 th:src{/login/img/login.jpg} width72px height72pxh1 classh3 mb-3 font-weight-normal请登录/h1!-- 用户登录错误信息提示框 --!-- th:if 根据条件的真假决定是否渲染 HTML 元素 真则渲染假则不渲染 (有该参数值则渲染,没有该参数值则不渲染) --!-- 有该参数值则渲染该div否则就不渲染该div --div th:if${param.error}stylecolor: red;height: 40px;text-align: left;font-size: 1.1em!-- img标签 --img th:src{/login/img/loginError.jpg} width20px用户名或密码错误请重新登录/div!-- 用户名参数名为 : name , 密码的参数名为: pws --input typetext namename classform-controlplaceholder用户名 required autofocusinput typepassword namepwd classform-controlplaceholder密码 requiredbutton classbtn btn-lg btn-primary btn-block typesubmit 登录/buttonp classmt-5 mb-3 text-mutedCopyright© 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 methodpostinput th:typesubmit th:value注销
/formindex.html !DOCTYPE html
!-- 配置开启thymeleaf模板引擎页面配置 --
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8title影视直播厅/title
/head
body!-- index.html页面是项目首页页面common和vip文件夹中分别对应的普通用户 和 VIP用户可访问的页面 --
h1 aligncenter欢迎进入电影网站首页/h1
!-- th:action : 用于指定“表单提交”时 “应发送的URL”--
!-- 跳转到 /mylogout路径来进行 用户退出 , 因为不是使用默认的 /logout路径来请求 用户退出 所以 后端的 .logoutUrl()方法中内容也修改为 /mylogout --
!-- 同时 /mylogout 请求的方法为post类型的--
form th:action/mylogout methodpostinput th:typesubmit th:value注销
/form
hr
h3普通电影/h3
ullia th:href{/detail/common/1}飞驰人生/a/lilia th:href{/detail/common/2}夏洛特烦恼/a/li
/ul
h3VIP专享/h3
ullia th:href{/detail/vip/1}速度与激情/a/lilia 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 表 的 持久化用户信息 不会随之删除当用户 再次进行访问登录 时则是 在表中新增一条持久化用户信息。