从Redis反序列化UserDetails对象异常后发现FastJson序列化的一些问题

        最近在使用SpringSecurity+JWT实现认证授权的时候,出现Redis在反序列化userDetails的异常。通过实践发现,使用不同的序列化方法和不同的fastJson版本,异常信息各不相同。所以特地记录了下来。

一、项目代码

        先来看看我项目中redis相关配置信息。

1.自定义的redis序列化器

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;/*** Redis使用FastJson序列化** @author mosul*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>
{public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");private Class<T> clazz;static{ParserConfig.getGlobalInstance().setAutoTypeSupport(true);}public FastJsonRedisSerializer(Class<T> clazz){super();this.clazz = clazz;}@Overridepublic byte[] serialize(T t) throws SerializationException{if (t == null){return new byte[0];}return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}@Overridepublic T deserialize(byte[] bytes) throws SerializationException{if (bytes == null || bytes.length <= 0){return null;}String str = new String(bytes, DEFAULT_CHARSET);return JSON.parseObject(str, clazz);}protected JavaType getJavaType(Class<?> clazz){return TypeFactory.defaultInstance().constructType(clazz);}
}

2.redis配置类

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {/*** 指定特定的连接工厂* @return*//*@Beanpublic RedisConnectionFactory redisConnectionFactory() {return new LettuceConnectionFactory();}*/@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}
}

3.redis工具类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;import java.util.*;
import java.util.concurrent.TimeUnit;/*** Redis帮助类** @author mosul*/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisHelper
{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public long deleteObject(final Collection collection){return redisTemplate.delete(collection);}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 删除Hash中的数据** @param key* @param hkey*/public void delCacheMapValue(final String key, final String hkey){HashOperations hashOperations = redisTemplate.opsForHash();hashOperations.delete(key, hkey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}

4.自己系统中的UserDetails

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;// 系统用户private SysUser user;// 用户权限列表private List<SysPermission> permissionList;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return permissionList.stream().filter(permission -> permission.getPermission() != null).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

5.登录设置


@Overridepublic String login(SysUser sysUser) {String token = null;//密码需要客户端加密后传递try {UserDetails userDetails = sysUserService.loadUserByUsername(sysUser.getUsername());if(!passwordEncoder.matches(sysUser.getPassword(),userDetails.getPassword())){throw new BadCredentialsException("密码不正确");}UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);token = jwtTokenUtil.generateToken(userDetails);String key = "login:" + sysUser.getUsername();//设置redisredisHelper.setCacheObject(key,userDetails);//insertLoginLog(username);} catch (AuthenticationException e) {LOGGER.warn("登录异常:{}", e.getMessage());}return token;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser sysUser = sysUserMapper.selectOne(new QueryWrapper<SysUser>().eq("username", username));List<SysPermission> permissionsByUser = sysUserRoleMapper.findPermissionsByUser(sysUser.getUserId());sysUser.setPassword(new BCryptPasswordEncoder().encode(sysUser.getPassword()));// 将系统的用户信息和权限信息封装成UserDetailsUserDetails userDetail = new LoginUser(sysUser, permissionsByUser);return userDetail;}

6.JWT校验

 @Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String authHeader = request.getHeader(this.tokenHeader);if (authHeader != null && authHeader.startsWith(this.tokenHead)) {String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "String username = jwtTokenUtil.getUserNameFromToken(authToken.trim());LOGGER.info("checking username:{}", username);if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {//从redis中获取userDetailsString redisKey = "login:" + username;UserDetails userDetails = redisHelper.getCacheObject(redisKey);if(Objects.isNull(userDetails)){throw new RuntimeException("用户未登录");}if (jwtTokenUtil.validateToken(authToken, userDetails)) {//存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));LOGGER.info("authenticated user:{}", username);SecurityContextHolder.getContext().setAuthentication(authentication);}}}//放行filterChain.doFilter(request, response);}

7.fastjson版本

        <!--fastjson依赖--><!--第一个版本--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version></dependency><!--第二个版本--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.22</version></dependency>

        上面的代码中,先根据用户名获取用户对应的用户信息和权限信息,然后构建SpringSecurity的UserDetails对象,用户登录的时候将这个UserDetails对象放入redis中,后续校验请求携带的token与redis中的信息是否一致。

二、异常信息

1.版本一报错信息

        需要说明的是,在redis系列化时,是正常的,对应的值也成功设置近缓存了,但是在JWT校验阶段,执行UserDetails userDetails = redisHelper.getCacheObject(redisKey);时出现异常,反序列化失败。

        针对这个问题,首先上面的代码逻辑是没有问题的,但是与fastjson反序列化不兼容导致的问题。

        根据异常信息提示,设置属性authorities错误,猜想下是因为LoginUser中没有authorities属性,但也说不过去,同样没有属性username和password怎么不会报错?

        带着这个疑问,我们先给将LoginUser代码改为下面这种形式。

@Data
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;private SysUser user;private List<SysPermission> permissionList;private List<GrantedAuthority> authorities;public LoginUser() {}public LoginUser(SysUser user, List<SysPermission> permissionList) {this(user,permissionList,null);}/*** 针对fastJson中redis反序列化报错的改进* org.springframework.data.redis.serializer.SerializationException:* Could not deserialize: set authorities error; nested exception is com.alibaba.fastjson.JSONException: set authorities error** @param user* @param permissionList* @param authorities*/public LoginUser(SysUser user, List<SysPermission> permissionList, List<GrantedAuthority> authorities) {//返回当前用户的权限List<GrantedAuthority> authoritieList = permissionList.stream().filter(permission -> permission.getPermission() != null).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());this.user = user;this.permissionList = permissionList;this.authorities = authoritieList;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

        发现这里改完之后,是可以正常运行的。 而且发现,当我们给getAuthorities()赋简单的值的时候,不会出现这个问题。

@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> authoritieList = new ArrayList<>();authoritieList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));return authoritieList;}

        而且我们在构造函数中提前将权限列表加载出来,赋值给一个属性,属性不一定非得名为authorities,如属性名为authoritiesList。然后返回这个属性也不会报错。

@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authoritieList;}

        通过上面两个测试可以猜测下,如果构造函数中提前将比较复杂的实现暴露了,反系列化也不会报错。可能出现异常的原因fastjson是对不确定结果无法反系列化,如果是简单的结果或在构造器中就已经知道了确定结果,就不会出现反序列化的异常。 

2.版本二报错信息

         在使用fastJson 2.x版本的时候,同时需要对redis配置类做如下修改。

@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);/*java.lang.ClassCastException:* com.alibaba.fastjson.JSONObject cannot be cast to org.springframework.security.core.userdetails.UserDetails* */String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}

        修改完成之后,还是会出现设置属性authorities错误,同样需要对LoginUser做上述修改。

3.发现FastJson反系列的一般问题

        正如上面所说的,同样没有属性username和password怎么不会报错?于是做了一系列测试。发现了在低版本的fastJson中,对应集合类型接口方法中包含较复杂的实现(不是直接显示赋值),反序化可能要求必须有对应的属性。

        定义了一个有不同返回值类型的几种方法来测试。

public interface CrazyDetails {List<String> getApps();User getUser();String getName();String[] getNodes();Collection<String> getTests();List<User> getUsers();
}

        定义一个实现类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MosulApp implements CrazyDetails{@Overridepublic User getUser() {return new User(this.appInfo.name);}private AppInfo appInfo;@Overridepublic List<User> getUsers() {List<User> userList = new ArrayList<>();for(int i = 0; i < this.appInfo.name.length(); i ++) {User user1 = new User("" + i);userList.add(user1);}return userList;}@Overridepublic String[] getNodes() {String[] strings = new String[2];strings = new String[]{this.appInfo.name,this.appInfo.details};return strings;}/*private List<String> apps;*///报错,添加需要private List<String> tests@Overridepublic Collection<String> getTests() {List<String> list = Arrays.asList(appInfo.name);return list;}@Overridepublic List<String> getApps() {List<String> objects = new ArrayList<>();for(int i = 0; i < this.appInfo.name.length(); i ++) {objects.add("tt" + i);}return objects;}@Overridepublic String getName() {return this.appInfo.name;}}

       

        在测试发现fastJson 1.x版本对于Arrays.asList(appInfo.name);反序列化失败,fastJson 2.x版本则可以反序列化成功,但对于UserDetails中Collection<? extends GrantedAuthority> getAuthorities()中如果有比较复杂的实现,fastJson 2.x版本反序列化还是会失败。所以为了保险起见,最后在自定义的UserDetails中添加authorities属性,除了这种方法能外,应该也跟自定义的序列化器相关设置有关,需要进行探索。

三、Redis和SpringSecutiry相关配置

        基于上述测试,最终fastJson选用2.0.22版本,最后将redis配置类和SpringSecutiry中UserDetails实现类修改为如下所示。

1.redis配置类

@Configuration
public class RedisConfig {/*** 指定特定的连接工厂* @return*//*@Beanpublic RedisConnectionFactory redisConnectionFactory() {return new LettuceConnectionFactory();}*/@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);/* FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);*//*解决java.lang.ClassCastException:* com.alibaba.fastjson.JSONObject cannot be cast to org.springframework.security.core.userdetails.UserDetails* */String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(serializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return template;}
}

2.LoginUser类

@Data
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;// 用户信息private SysUser user;// 用户权限列表private List<SysPermission> permissionList;// SpringSecurity对应的权限信息private List<GrantedAuthority> authorities;public LoginUser() {}public LoginUser(SysUser user, List<SysPermission> permissionList) {this(user,permissionList,null);}/*** 针对fastJson中redis反序列化报错的改进* org.springframework.data.redis.serializer.SerializationException:* Could not deserialize: set authorities error; nested exception is com.alibaba.fastjson.JSONException: set authorities error** @param user* @param permissionList* @param authorities*/public LoginUser(SysUser user, List<SysPermission> permissionList, List<GrantedAuthority> authorities) {//返回当前用户的权限List<GrantedAuthority> authoritieList = permissionList.stream().filter(permission -> permission.getPermission() != null).map(permission -> new SimpleGrantedAuthority(permission.getPermission())).collect(Collectors.toList());this.user = user;this.permissionList = permissionList;this.authorities = authoritieList;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

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

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

相关文章

表单考勤签到作业周期打卡打分评价评分小程序开源版开发

表单考勤签到作业周期打卡打分评价评分小程序开源版开发 表单打卡评分 表单签到功能&#xff1a;学生可以通过扫描二维码或输入签到码进行签到&#xff0c;方便教师进行考勤管理。 考勤功能&#xff1a;可以记录学生的出勤情况&#xff0c;并自动生成出勤率和缺勤次数等统计数…

嵌入式单片机方向和Linux驱动开发方向哪个发展前景好?

嵌入式单片机方向和Linux驱动开发方向哪个发展前景好&#xff1f; 在某些平台上看到很多人鼓吹嵌入式Linux开发比单片机开发要好&#xff0c;让所有人都去做嵌入式Linux开发。说这种话的人大多数是嵌入式Linux的培训机构&#xff0c;或者是一开始就以嵌入式Linux入门的那一批人…

经典的回溯算法题leetcode全排列问题思路代码详解

目录 全排列问题 leetcode46题.全排列 leetcode47题.全排列II 对回溯算法感兴趣的朋友也可以多多支持一下我的其他文章。 回溯算法详解-CSDN博客 经典的回溯算法题leetcode组合问题整理及思路代码详解-CSDN博客 经典的回溯算法题leetcode子集问题思路代码详解-CSDN博客 …

API 设计:使用 Node.js 和 Express.js 的综合教程

API&#xff08;应用程序编程接口&#xff09;设计涉及创建一个高效而强大的接口&#xff0c;允许不同的软件应用程序相互交互。 说明 本教程将指导您使用 Node.js 和 Express.js 作为核心技术来规划、设计和构建 API。但是&#xff0c;这些原则可以应用于任何语言或框架。我们…

工业以太网交换机的特点

交换机的使用范围非常广泛&#xff0c;可以说只有需要进行网络连接的地方&#xff0c;基本上都会与交换机有关。但是工业以太网交换机的适用范围相对较小&#xff0c;主要用于工业控制领域的以太网交换设备。工业以太网交换机拥有电信级的性能特征&#xff0c;能够适应苛刻的工…

Hadoop实践指南:揭秘HDFS元数据并解析案例

1.什么是元数据 元数据&#xff08;Metadata&#xff09;&#xff0c;描述数据的数据&#xff08;data about data&#xff09;。 1.1 HDFS元数据 元数据&#xff1a;关于文件或目录的描述信息&#xff0c;如文件所在路径、文件名称、文件类型等等&#xff0c;这些信息称为文…

华为ospf路由协议防环和次优路径中一些难点问题分析

第一种情况是ar3的/0/0/2口和ar4的0/0/2口发布在区域1时&#xff0c;当ar1连接ar2的线断了以后&#xff0c;骨干区域就断了&#xff0c;1.1.1.1到2.2.2.2就断了&#xff0c;ping不通了。但ar5和ar6可以ping通2.2.2.2和1.1.1.1&#xff0c;ar3和ar4不可以ping通2.2.2.2和1.1.1.1…

【采坑分享】导出文件流responseType:“blob“如何提示报错信息

目录 前言&#xff1a; 采坑之路 总结&#xff1a; 前言&#xff1a; 近日&#xff0c;项目中踩了一个坑分享一下经验&#xff0c;也避免下次遇到方便解决。项目基于vue2axioselement-ui&#xff0c;业务中导出按钮需要直接下载接口中的文件流。正常是没有问题&#xff0c;但…

迈巴赫S480升级主动式氛围灯 浪漫婉转的气氛

主动式氛围灯有263个可多色渐变的LED光源&#xff0c;营造出全情沉浸的动态光影氛围。结合智能驾驶辅助系统&#xff0c;可在转向或检测到危险时&#xff0c;予以红色环境光提示&#xff0c;令光影艺术彰显智能魅力。配件有6个氛围灯&#xff0c;1个电脑模块。 1、气候&#xf…

项目实战详细讲解带有条件响应的 SQL 盲注、MFA绕过技术、MFA绕过技术、2FA绕过和技巧、CSRF绕过、如何寻找NFT市场中的XSS漏洞

项目实战详细讲解带有条件响应的 SQL 盲注、MFA绕过技术、MFA绕过技术、2FA绕过和技巧、CSRF绕过、如何寻找NFT市场中的XSS漏洞。 带有条件响应的 SQL 盲注 这篇文章的核心要点如下: 漏洞发现:作者在Portswigger提供的实验室中发现了一个盲SQL注入漏洞。这个漏洞存在于一个应…

【开源】基于JAVA的车险自助理赔系统

项目编号&#xff1a; S 018 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S018&#xff0c;文末获取源码。} 项目编号&#xff1a;S018&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 角色管理模块2.3 车…

EXIT外部中断 HAL库+cubeMX

一.cubeMX外部中断配置 1.系统内核 2.中断管理 3.选择抢占优先级和响应优先级&#xff0c;共有5个等级&#xff0c;在这里就使用库函数编写代码时最常用的2位抢占优先级2位响应优先级。 4.勾选使能选项&#xff0c;后面的两个零&#xff0c;第一个代表抢占优先级的等级&#xf…