从0开始搭建一个生产级SpringBoot2.0.X项目(十二)SpringBoot接口SpringSecurity JWT鉴权

前言

最近有个想法想整理一个内容比较完整springboot项目初始化Demo。

SpringBoot接口权限控制 SpringSecurity 接口使用 Bearer token类型  JWT 鉴权

一、pom文件新增依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.12.6</version>
</dependency>

application.yaml 增加参数 用于模拟后续生成TOKEN和验证使用

#JTW 加密的密码
jwt:# secret 必须足够长 40bits之上# 否则 报错 The specified key byte array is 40 bits which is not secure enough            #for any JWT HMAC-SHA algorithmsecret: 12345678123456781234567812345678
#认证系统编号 clientid: clientid

二、创建 UserInfo 类 模拟用户信息

import lombok.Data;/*** 用户信息*/
@Data
public class UserInfo {private String clientid;//用户授权idprivate String username;//用户名private String password;//用户名
}

三、创建 JwtTokenUtil 类,用于生成和验证JWT令牌。

package com.murg.bootdemo.util;import com.murg.bootdemo.business.entity.UserInfo;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.io.Serializable;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;@Component
public class JwtTokenUtil implements Serializable {/*** 过期时间(单位:秒)*/public static final Integer REFRESH_TOKEN_EXPIRE = 72000;/*** 加密算法*/private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;/*** 私钥*/private static String SECRET;/*** 秘钥实例*/public static SecretKey KEY;/*** jwt签发者*/private final static String JWT_ISS = "Sunreal";/*** jwt主体*/private final static String SUBJECT = "dwd";@Value("${jwt.secret}")public void setSecret(String secret) {JwtTokenUtil.SECRET = secret;JwtTokenUtil.KEY=Keys.hmacShaKeyFor(SECRET.getBytes());}/*** 获取令牌用户数据* @param token* @return*/public static UserInfo getUserInfoByToken(String token) {final Claims claims = parsePayload(token);UserInfo userInfo = null;if (null != claims) {userInfo = new UserInfo();userInfo.setClientid(claims.get("clientid").toString());userInfo.setUsername(claims.get("username").toString());}return userInfo;}/*** 获取令牌用户数据* @param token* @return*/public static String getUsernameFromToken(String token) {final Claims claims = parsePayload(token);UserInfo userInfo = null;if (null != claims) {userInfo = new UserInfo();userInfo.setClientid(claims.get("clientid").toString());userInfo.setUsername(claims.get("username").toString());}return userInfo.getUsername();}/*** 令牌过期校验 true-过期 false-未过期* @param token* @return*/public static Boolean isExpired(String token) {Boolean result = true;final Claims claims = parsePayload(token);if (null != claims) {String exp = claims.get("exp").toString();long diff = Long.parseLong(exp) - System.currentTimeMillis() / 1000;if(diff > 0) {result = false;}}return result;}public Boolean validateToken(String token, UserDetails userDetails) {final String username = getUsernameFromToken(token);return (username.equals(userDetails.getUsername()) && !isExpired(token));}/*** 获取令牌的过期时间*/public static Long getTokenExpireIn(String token) {Long expireIn = System.currentTimeMillis() / 1000;final Claims claims = parsePayload(token);if (null != claims) {String exp = claims.get("exp").toString();return Long.parseLong(exp);}return expireIn;}public String generateToken(UserDetails userDetails,String clientid) {Map<String, Object> claims = new HashMap<>();claims.put("username",userDetails.getUsername());// claims.put("password",userDetails.getPassword());claims.put("clientid",clientid);return generateToken(claims);}/*** 生成令牌*/public static String generateToken(Map<String, Object> payload) {Date expireDate = Date.from(Instant.now().plusSeconds(REFRESH_TOKEN_EXPIRE));return Jwts.builder().header().add("typ", "JWT").add("alg", "HS256").and().claims(payload).id(UUID.randomUUID().toString()).expiration(expireDate).issuedAt(new Date()).subject(SUBJECT).issuer(JWT_ISS).signWith(KEY, ALGORITHM).compact();}/*** 解析令牌claims*/public static Jws<Claims> parseClaim(String token) {Jws<Claims> claimsJws = null;claimsJws = Jwts.parser() .verifyWith(KEY) .build().parseSignedClaims(token);return claimsJws;}/*** 解析令牌header*/private static JwsHeader parseHeader(String token) {Jws<Claims> claimsJws = parseClaim(token);if(null == claimsJws) {return null;}return claimsJws.getHeader();}/*** 解析令牌payload*/private static Claims parsePayload(String token) {Jws<Claims> claimsJws = parseClaim(token);if(null == claimsJws) {return null;}return claimsJws.getPayload();}
}

四、创建 JwtAuthenticationEntryPoint 类,用于处理未经授权的请求

package com.murg.bootdemo.filter;import com.murg.bootdemo.common.WebResult;
import com.murg.bootdemo.util.JsonUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;/**此类继承Spring Security的AuthenticationEntryPoint类,并重写其commence。它拒绝每个未经身份验证的请求并发送错误代码401。*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {private static final long serialVersionUID = -7858869558953243875L;@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response,AuthenticationException authException) throws IOException, ServletException {//返回统一封装的格式WebResult<Object> webResult = WebResult.ok();webResult.isWrong("您可能未登录或登录超时,请重新登陆!");response.setContentType("application/json");response.setStatus(401);try {JsonUtil.write2Stream(response.getOutputStream(), webResult);} catch (Exception var6) {throw new ServletException();}}
}

五、创建JWTUserDetailsService以便使用用户名从数据库中获取用户详细信息。

package com.murg.bootdemo.interceptor;import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.ArrayList;/*** JWTUserDetailsService* JWTUserDetailsService实现了Spring Security UserDetailsService接口。* 它会覆盖loadUserByUsername,以便使用用户名从数据库中获取用户详细信息。* 当对用户提供的用户详细信息进行身份验证时,Spring Security Authentication Manager调用此方法从数据库中获取用户详细信息* 在这里,我们从硬编码的用户列表中获取用户详细信息。* 以后实际会从数据库中获取用户详细信息的DAO实现。* 用户密码也使用BCrypt以加密格式存储。在这里,您可以使用在线Bcrypt生成器为密码生成Bcrypt。*/
@Service
public class JwtUserDetailsService implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {if ("admin".equals(username)) {return new User("admin", "$2a$10$XRemBdMT1hrf4EdfsDWHCeSQFczKq0STVDqVRXT8YnbPoQcN21KAi",new ArrayList<>());} else {throw new UsernameNotFoundException("User not found with username: " + username);}}/*** 测试生成BCrypt加密格式* @param args*/public static void main(String[] args) {String pass = "123456";BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();final String passHash = encoder.encode(pass);System.out.println(passHash);final boolean matches = encoder.matches(pass, passHash);System.out.println(matches);}
}

六、创建 JwtRequestFilter 类,用于解析和验证JWT 

package com.murg.bootdemo.filter;import com.murg.bootdemo.interceptor.JwtUserDetailsService;
import com.murg.bootdemo.util.JwtTokenUtil;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/**JwtRequestFilter继承了Spring Web的OncePerRequestFilter类。对于任何传入请求,都会执行此Filter类。它检查请求是否具有有效的JWT令牌。如果它具有有效的JWT令牌,则它将在上下文中设置Authentication,以指定当前用户已通过身份验证。*/
@Slf4j
@Component
public class JwtRequestFilter extends OncePerRequestFilter {private  final JwtUserDetailsService jwtUserDetailsService;private final JwtTokenUtil jwtTokenUtil;public JwtRequestFilter(JwtUserDetailsService jwtUserDetailsService, JwtTokenUtil jwtTokenUtil) {this.jwtTokenUtil = jwtTokenUtil;this.jwtUserDetailsService = jwtUserDetailsService;}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {final String requestTokenHeader = request.getHeader("Authorization");String username = null;String jwtToken = null;// JWT Token is in the form "Bearer token". Remove Bearer word and get only the Tokenif (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {jwtToken = requestTokenHeader.substring(7);try {username = jwtTokenUtil.getUserInfoByToken(jwtToken).getUsername();} catch (IllegalArgumentException e) {log.error("Unable to get JWT Token");} catch (ExpiredJwtException e) {log.error("JWT Token has expired"); //token 过期抛出异常 用于统一异常处理ExceptionConfig捕获  throw e;}} else {logger.warn("JWT Token does not begin with Bearer String");}//Once we get the token validate it.if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {//根据用户名获取库里的用户信息UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);// if token is valid configure Spring Security to manually set authenticationif (jwtTokenUtil.validateToken(jwtToken, userDetails)) { //校验用户信息 用户名一致并且token没过期/* 创建了一个UsernamePasswordAuthenticationToken实例,并通过调用setDetails方法设置了额外的细节。这个细节可以是任何对象,通常用于传递额外的信息,如客户端IP地址、会话ID等。请注意,setDetails方法不是为了设置用户名和密码,而是为了设置与认证过程相关的其他信息。如果你需要设置用户名和密码,应该在创建UsernamePasswordAuthenticationToken实例时提供。*/UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 设置SecurityContext中的Authentication对象 在上下文中设置Authentication,以指定当前用户已通过身份验证SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}chain.doFilter(request, response);}
}

七、增加配置Spring Security类 WebSecurityConfig

package com.murg.bootdemo.config;import com.murg.bootdemo.filter.JwtAuthenticationEntryPoint;
import com.murg.bootdemo.filter.JwtRequestFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;private final UserDetailsService jwtUserDetailsService;private final JwtRequestFilter jwtRequestFilter;public WebSecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,UserDetailsService jwtUserDetailsService,JwtRequestFilter jwtRequestFilter) {this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;this.jwtUserDetailsService = jwtUserDetailsService;this.jwtRequestFilter = jwtRequestFilter;}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/***认证管理器的建造器,如果要使用该功能需要配置一个userDetailsService和PasswordEncoder;为什么要配置UserDetailService,他的作用是什么?因为我们需要被认证的用户作为管理,那么UserDetailService作用就是:在认证器中根据传过来的用户名进行查找,PasswordEncoder就是将密码进行比对;认证成功后返回一个Authentication对象自定义用户认证的service 用于实现自己的认证逻辑passwordEncoder定义密码解密方式**/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception{auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// We don't need CSRF for this examplehttpSecurity.csrf().disable()// 不验证此特定请求 该请求为获取token的接口.authorizeRequests().antMatchers("/authenticate").permitAll()// 所有其他请求都需要经过身份验证.anyRequest().authenticated().and()//Spring Security中用于配置认证入口点异常处理的一部分。当Spring Security遇到认证异常时,// 它会通过配置的AuthenticationEntryPoint来处理。.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()// 所有的Rest服务一定要设置为无状态,以提升操作性能.sessionCreationPolicy(SessionCreationPolicy.STATELESS);// Add a filter to validate the tokens with every requesthttpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);}}

八、测试接口鉴权

重启服务不带token测试helloWord接口,返回鉴权失败

创建GetJwtController

调用上述预留放开的/authenticate接口获取token数据

package com.murg.bootdemo.business.controller;import com.murg.bootdemo.common.WebResult;
import com.murg.bootdemo.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;import java.util.Objects;/**使用JwtAuthenticationController暴露/authenticate。使用Spring Authentication Manager验证用户名和密码。如果凭据有效,则会使用JWTTokenUtil创建一个JWT令牌并将其提供给客户端。*/
@RestController
public class GetJwtController {@Value("${jwt.clientid}")private String CLIENTID;private final AuthenticationManager authenticationManager;private final JwtTokenUtil jwtTokenUtil;private final UserDetailsService jwtInMemoryUserDetailsService;public GetJwtController(AuthenticationManager authenticationManager,JwtTokenUtil jwtTokenUtil,UserDetailsService jwtInMemoryUserDetailsService) {this.authenticationManager = authenticationManager;this.jwtTokenUtil = jwtTokenUtil;this.jwtInMemoryUserDetailsService = jwtInMemoryUserDetailsService;}@RequestMapping(value = "/authenticate", method = RequestMethod.POST)public WebResult createAuthenticationToken(@RequestBody JwtRequest authenticationRequest)throws Exception {if(!CLIENTID.equals(authenticationRequest.getClientid())) {throw new Exception( "客户端未授权");}//   authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());final UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());final String token = jwtTokenUtil.generateToken(userDetails,authenticationRequest.getClientid());return WebResult.ok(token);}/***  这个方法会调用UserDetailsServiceImpl.loadUserByUsername,此过程中调用的流程如下:* 1. AuthenticationManager是个接口,ProviderManager是他的实现类。* 2. authenticationManager.authenticate()调用其实就是ProviderManager.authenticate()* 3. ProviderManager代码中找到provider.authenticate(authentication);* 4. provider.authenticate(authentication)是AuthenticationProvider.authenticate(authentication)方法。* 5. AuthenticationProvider也是一个接口,他的实现类是AbstractUserDetailsAuthenticationProvider。* 6. AbstractUserDetailsAuthenticationProvider的子类是DaoAuthenticationProvider。* 7. 在DaoAuthenticationProvider中调用了UserDetailsService8.写一个UserDetailsServiceImpl实现类JwtUserDetailsService ,实现UserDetailsService ,并重写loadUserByUsername()直接调用UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());*/private void authenticate(String username, String password) throws Exception {Objects.requireNonNull(username);Objects.requireNonNull(password);try {authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));} catch (DisabledException e) {throw new Exception("USER_DISABLED", e);} catch (BadCredentialsException e) {throw new Exception("INVALID_CREDENTIALS", e);}}
}

 

拿到token之后 重新带 Bearer 类型 token 测试 helloWord接口 正常返回

九、配置鉴权白名单

通过application.yaml配置放行鉴权路由白名单

1.增加配置 放行getTt26接口。

oauth:whitelist:- /getTt26

2.创建配置类 AuthwhitelistConfig 定义属性 whitelist 

参数配置在 application.yaml 文件中,通过 @ConfigurationProperties 注解注入参数whitelist

package com.murg.bootdemo.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;import java.util.List;
@Data
@Configuration
@ConfigurationProperties("oauth")
public class AuthwhitelistConfig {private List<String> whitelist;
}

3修改 WebSecurityConfig方法

引入AuthwhitelistConfig 

private final AuthwhitelistConfig authwhitelist;

 增加白名单不验证

 .antMatchers(authwhitelist.getWhitelist().toArray(new String[0])).permitAll()

@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {// We don't need CSRF for this examplehttpSecurity.csrf().disable()// 不验证此特定请求.authorizeRequests().antMatchers("/authenticate").permitAll().antMatchers(authwhitelist.getWhitelist().toArray(new String[0])).permitAll()// 所有其他请求都需要经过身份验证.anyRequest().authenticated().and()//Spring Security中用于配置认证入口点异常处理的一部分。当Spring Security遇到认证异常时,// 它会通过配置的AuthenticationEntryPoint来处理。.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()// 所有的Rest服务一定要设置为无状态,以提升操作性能.sessionCreationPolicy(SessionCreationPolicy.STATELESS);// Add a filter to validate the tokens with every requesthttpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);}

不带token测试接口

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

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

相关文章

Maven 下载配置 详解 我的学习笔记

Maven 下载配置 详解 我的学习笔记 一、Maven 简介二、maven安装配置三、maven基本使用四、idea配置mavenidea配置maven环境maven坐标idea创建maven项目配置Maven-Helper插件 五、依赖管理 一、Maven 简介 Apache Maven 是一个项目管理和构建工具&#xff0c;它基于项目对象模型…

Windows Server2012 R2搭建NFS服务器

正文共&#xff1a;1024 字 23 图&#xff0c;预估阅读时间&#xff1a;1 分钟 在测试vCenter的集群操作时&#xff0c;出现了共享vSAN错误的问题&#xff0c;导致无法继续。我也只好先创建一个共享NFS&#xff08;Network File System&#xff0c;网络文件系统&#xff09;存储…

HarmonyOs DevEco Studio小技巧28--部分鸿蒙生命周期详解

目录 前言 页面和自定义组件生命周期 页面生命周期 onPageShow --- 表示页面已经显示 onPageHide --- 表示页面已经隐藏 onBackPress --- 表示用户点击了返回键 组件生命周期 aboutToAppear --- 表示组件即将出现 onDidBuild --- 表示组件已经构建完成 aboutToDisap…

dolphin 配置data 从文件导入hive

datax 支持多种数据源的相互读写&#xff0c;作为开源软件&#xff0c;提供了离线采集功能&#xff0c;方便系统开发&#xff0c;过程中遇到诸多配置&#xff0c;需要开发者自己探索&#xff0c;免费同样有成本 配置模板 {"setting": {},"job": {"s…

高效管理iPhone存储:苹果手机怎么删除相似照片

在使用iPhone的过程中&#xff0c;我们经常会遇到存储空间不足的问题&#xff0c;尤其是当相册中充满了大量相似照片时。这些照片不仅占用了宝贵的存储空间&#xff0c;还可能使iPhone出现运行卡顿的情况。因此&#xff0c;我们迫切需要寻找苹果手机怎么删除相似照片的方法&…

C++:set详解

文章目录 前言一、set概念介绍二、set的使用1. 插入删除相关2. 查找相关1&#xff09;find2&#xff09;count3&#xff09;lower_bound与upper_bound4&#xff09;equal_range 三、set的值是不能修改的原理四、基于哈希表的set总结 前言 根据应用场景的不同&#xff0c;STL总…

leetcode:杨辉三角

题目链接 class Solution { public:vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);//生成一个长度为5&#xff0c;元素为vector<int>的顺序表for (int i 0; i < numRows; i)//对生成的顺序表初始化&#xff…

【力扣打卡系列】单调栈

坚持按题型打卡&刷&梳理力扣算法题系列&#xff0c;语言为go&#xff0c;Day20 单调栈 题目描述 解题思路 单调栈 后进先出 记录的数据加在最上面丢掉数据也先从最上面开始 单调性 记录t[i]之前会先把所有小于等于t[i]的数据丢掉&#xff0c;不可能出现上面大下面小的…

二次封装 el-pagination 组件存在的问题

在使用 Element Plus 组件时&#xff0c;有时会遇到组件不完全符合需求的情况&#xff0c;这时可能需要对其进行二次封装。在封装 Pagination 组件时&#xff0c;我们会发现一些属性和函数无法正常使用&#xff0c;下面将详细探讨这些问题&#xff0c;并提供一下思路和想法。 …

通俗易懂讲STM32为GPIO的8种模式(上拉输入、下拉输入、模拟输入、浮空输入,开漏输出,推挽输出)

本文参照这篇博客---易于理解深刻理解GPIO(上拉输入、下拉输入、模拟输入、浮空输入&#xff0c;开漏输出&#xff0c;推挽输出的区别&#xff0c;以STM32为例)_下拉输出-CSDN博客 一、输入模式 上拉输入 一句话总结&#xff1a;接上拉电阻对输入的低电平能够有效的读取&…

单元测试日志打印相关接口及类 Logger

LoggerFactory 简介 单元测试常用日志打印工具LoggerFactory。 LoggerFactory 代码结构 LoggerFactory 是 JUnit 平台中的一个类&#xff0c;用于创建 Logger 实例。它被设计用于提供日志记录功能&#xff0c;使得 JUnit 在执行测试时能够记录信息、警告、错误等。 LoggerFact…

【万字总结】数据结构常考应用大题做法画法详解_树_哈希表_图_排序大总结

文章目录 1.树相关应用大题1.1 已知二叉树的中序序列和前序or中序&#xff0c;画出二叉树1.2 二叉树的遍历、树的遍历、森林的遍历总结1.3二叉树与森林之间的转换1.3.1 已知树的先序序列和中序序列&#xff0c;画出森林 1.4 二叉树的线索化1.5 二叉排序树1.5.1 二叉排序树的删除…

越权访问漏洞

V2Board Admin.php 越权访问漏洞 ## 漏洞描述 V2board面板 Admin.php 存在越权访问漏洞&#xff0c;由于部分鉴权代码于v1.6.1版本进行了修改&#xff0c;鉴权方式变为从Redis中获取缓存判定是否存在可以调用… V2Board Admin.php 越权访问漏洞 漏洞描述 V2board面板 Admin.ph…

接口测试用例设计的关键步骤与技巧解析!

简介 接口测试在需求分析完成之后&#xff0c;即可设计对应的接口测试用例&#xff0c;然后根据用例进行接口测试。接口测试用例的设计也需要用到黑盒测试用例设计方法&#xff0c;和测试流程与理论章节的功能测试用例设计的方法类似&#xff0c;设计过程中还需要增加与接口特…

Redis常见面试题(二)

Redis性能优化 Redis性能测试 阿里Redis性能优化 使用批量操作减少网络传输 Redis命令执行步骤&#xff1a;1、发送命令&#xff1b;2、命令排队&#xff1b;3、命令执行&#xff1b;4、返回结果。其中 1 与 4 消耗时间 --> Round Trip Time&#xff08;RTT&#xff0c;…

功能超全的客服快捷回复软件

客服日常工作繁忙&#xff0c;需要一款满足各项日常需求的客服工具&#xff0c;完成咨询的快捷回复&#xff0c;并能共享客服团队优质话术&#xff0c;实现云端文件储存&#xff0c;管理表情动图等功能 前言 客服日常工作繁忙&#xff0c;需要一款满足各项日常需求的客服工具。…

靠Python真的能实现经济自由,学会了你也可以

不知道大家有没有注意到&#xff0c;最近关注的很多人都在聊“副业and兼职”这件事。 毕竟单一收入已经不能满足现代人的需求了。 对于普通人来说&#xff0c;想要跳出固定思维和舒适圈&#xff0c;相比于孤注一掷的创业&#xff0c;更推荐兼职。 很多人想要创业&#xff0c;…

【案例分享】借助 iSpring,创造客户真正欣赏的专业在线培训体验

Safety Bee Training是一家领先的认证在线学习提供商&#xff0c;专门提供职业健康、安全和环境项目。它也是中东和亚洲唯一一家提供经 NASP 等国际认证机构认可的课程的培训提供商。它已经培训了超过 28,000 名学习者&#xff0c;并且正在不断扩大其课程范围&#xff0c;以提供…

IP可用端口扫描器工具(bun + typescript)

IP可用端口扫描器工具&#xff08;bun typescript&#xff09; 学习方式&#xff1a;源码学习。通过项目和源码可以学习到如下内容&#xff1a;1、bun搭建项目&#xff0c;打包项目2、net、dns等node内置模块的使用3、yargs、assert、progress、cli-color等三方包的使用ps&am…

docker镜像仓库常用命令

docker镜像仓库常用命令 docker logindocker logoutdocker pulldocker pushdocker searchdocker imagesdocker image inspectdocker tagdocker rmidocker image prunedocker savedocker loaddocker history docker login 语法: docker login [options] [server] 功能&#xff…