当前位置: 首页 > news >正文

Spring Security使用

文章目录

      • Spring Security的起点
      • FilterChain重写
      • 重写登录验证逻辑
      • 增加CSRF Token
      • 增加方法权限校验

Spring Security的起点

  1. 在AbstractApplicationContext.refresh()方法时,子类ServletWebServerApplicationContext会创建一个ServletContextInitializerBeans这个Bean对象
  2. ServletContextInitializerBeans在执行addServletContextInitializerBeans()时会使用BeanFactory去查找ServletContextInitializer类型的Bean,这时会找到DelegatingFilterProxyRegistrationBean这个bean对象
  3. DelegatingFilterProxyRegistrationBean会往ServletContext注册DelegatingFilterProxy对象,对象包含了filter的默认名称springSecurityFilterChain,可以看成这个DelegatingFilterProxy对象就是一个Filter,它又包含了filterChain,相当于又做了一层包装
  4. DelegatingFilterProxy使用filter的名字(springSecurityFilterChain)在BeanFactory中查找该filter对象
  5. 后续请求来时,会先进入DelegatingFilterProxy这个外层的Tomcat Filter,然后它再把请求传给springSecurityFilterChain这个内部的Filter Chain进行处理
  6. 默认的springSecurityFilterChain对象定义在org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration.SecurityFilterChainConfiguration#defaultSecurityFilterChain
  7. 我们一般要重写这个filterChain的定义

过滤链的流程大致如下:

DelegatingFilterProxy
FilterChain
SecurityFilter1
SecurityFilter0
SecurityFilter2
...
SecurityFilterN
TomcatFilter0
TomcatFilter1
TomcatFilter2
Servlet

FilterChain重写

一般来说,filterChain是我们需要重写的,我们的应用是无法直接使用默认filter的配置

  1. Configuration类需要添加@EnableWebSecurity,才会有HttpSecurity对象。
  2. 重写主要是对HttpSecurity对象进行设置,FilterChain里的Filter配置,来自于HttpSecurity中的各个Configurer,例如CsrfConfigurer用于创建CsrfFilter,FormLoginConfigurer用于创建UsernamePasswordAuthenticationFilter,一般都是在Configurer.configure()方法中创建并添加各种filter的。
  3. HttpSecurity中的formLogin(Customizer)或者是csrf(Customizer)方法,Customizer是一个函数式表达式,提供让我们对这些Configurer进行自定义,就是HttpSecurity会自己创建好各种Configurer,但是还提供了方法让我们去修改这些Configurer的配置,如果不需要进行修改,就传入Customizer.withDefaults()就可以了,它默认直接返回configurer,不做修改。
  4. 如下图,formLogin(formLoginConfigurer->{})可以对formLoginConfigurer的配置进行修改,里面我就修改了loginProcessingUrl,这样在请求/user/login时,它创建的UsernamePasswordAuthenticationFilter会判断,如果当前请求的路径跟该路径匹配,就会走验证用户名密码的逻辑,它的默认路径是/login,我的应用程序的登录接口是/user/login,两者不匹配是不会走用户名密码验证逻辑的。
  5. 同时formLogin修改了usernameParameter跟passwordParameter,UsernamePasswordAuthenticationFilter会从请求中使用这两个名称从request中获取用户名跟密码去进行校验,默认值是"username"跟"password",如果请求参数跟这两个值不匹配,那么获取到的用户名密码就是null,后面的验证逻辑就走不下去了,这些默认值跟程序不匹配的地方都需要修改
  6. 还有csrf,这里为了方便测试接口,不让CsrfFilter因为我没有token拦截我的请求,我修改让它对所有请求都忽略。
  7. HttpSecurity中有很多配置Configurer类的方法,可以根据需要自行修改,这里只是举例修改了其中两个,还有Configurer可以注册一个到多个Filter,Configurer跟Filter不仅仅是一对一的关系,各个Filter的功能都不一样,修改的配置也不一样,需要自己测试修改适配自己的程序,没有什么好的捷径
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {@Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated()).formLogin(c->{c.loginProcessingUrl("/user/login");c.usernameParameter("userName");c.passwordParameter("password");}).httpBasic(withDefaults()).csrf((t) -> {t.ignoringRequestMatchers("/**");});return http.build();}
}

重写登录验证逻辑

请求中的用户名/密码
数据库查询到的用户对应的密码
校验结果
UsernamePasswordAuthenticationFilter
UserDetailsService
PasswordEncoder
End

以上是登录校验逻辑走的流程

  1. 由于我的应用程序是Resuful接口,传的是json,所以在filter中无法通过request.getParameter()获取参数,只能通过request.getInputStream(),再转为Map获取其中的json参数,所以需要创建一个新类,继承UsernamePasswordAuthenticationFilter ,重写其obtainPassword()跟obtainUsername(),如果不是Restful接口,传的是form数据,可以通过request.getParameter()获取数据,则不需要重写
    class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {private ObjectMapper objectMapper = new ObjectMapper();@Overrideprotected String obtainPassword(HttpServletRequest request) {try {Map requestMap = objectMapper.readValue(request.getInputStream(), Map.class);Object o = requestMap.get(getPasswordParameter());if (o != null) {return String.valueOf(o);} else {return "";}} catch (IOException e) {throw new RuntimeException(e);}}@Overrideprotected String obtainUsername(HttpServletRequest request) {try {Map requestMap = objectMapper.readValue(request.getInputStream(), Map.class);Object o = requestMap.get(getUsernameParameter());if (o != null) {return String.valueOf(o);} else {return "";}} catch (IOException e) {throw new RuntimeException(e);}}}
  1. 上面这样做,又会引出一个问题,获取到username后,inputStream就关闭了,无法再通过getInputStream()获取到password,甚至后续流程request无法通过InputStream获取到数据,导致Controller层无法转换@RequestBody,所以还需要对request进行改造,做一层封装,报request中的数据保存早一个byte[]中,后续可以重复读取
    class RequestBodyCopyServletRequestWrapper extends HttpServletRequestWrapper {private byte[] copyBody = null;public RequestBodyCopyServletRequestWrapper(HttpServletRequest request) {super(request);try {copyBody = StreamUtils.copyToByteArray(request.getInputStream());} catch (IOException e) {throw new RuntimeException(e);}}@Overridepublic ServletInputStream getInputStream() throws IOException {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(copyBody);return new ServletInputStream() {@Overridepublic int read() throws IOException {return byteArrayInputStream.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {}};}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}}
  1. MyUsernamePasswordAuthenticationFilter 使用上面创建的RequestBodyCopyServletRequestWrapper 包装一下request对象
    class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {ServletRequestWrapper requestWrapper = null;if (request instanceof HttpServletRequest) {requestWrapper = new RequestBodyCopyServletRequestWrapper((HttpServletRequest) request);super.doFilter(requestWrapper, response, chain);} else {super.doFilter(request, response, chain);}}}
  1. HttpSecurity配置MyUsernamePasswordAuthenticationFilter替换原来的UsernamePasswordAuthenticationFilter,设置路径,跟请求参数,最后在http.build()初始化后,再从httpSecurity对象获取AuthenticationManager.class设置到filter中,它使用DaoAuthenticationProvider用来执行数据库的用户名密码的校验流程:
    @Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter();myUsernamePasswordAuthenticationFilter.setFilterProcessesUrl("/user/login");myUsernamePasswordAuthenticationFilter.setUsernameParameter("userName");myUsernamePasswordAuthenticationFilter.setPasswordParameter("password");http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated()).formLogin(withDefaults()).addFilterAt(myUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).csrf((t) -> {t.ignoringRequestMatchers("/**");});DefaultSecurityFilterChain securityFilterChain = http.build();myUsernamePasswordAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));return securityFilterChain;}
  1. 重写UserDetailsService,从我们的数据库中获取到User信息,同时需要对我们的User类跟Role类进行改造,分别实现UserDetails跟GrantedAuthority,返回用户跟角色的关联信息
    @Beanpublic UserDetailsService userDetailsService(ITUserService userService) {UserDetailsService userDetailsService = new MyUserDetailsService(userService);return userDetailsService;}class MyUserDetailsService implements UserDetailsService {private ITUserService userService;public MyUserDetailsService(ITUserService userService) {this.userService = userService;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return userService.loadUserByUsername(username);}}
@Getter
@Setter
@TableName("t_user")
public class TUser implements Serializable, UserDetails {private static final long serialVersionUID = 1L;@TableId(value = "user_id", type = IdType.AUTO)private Integer userId;private String userName;private String password;private LocalDateTime createTime;private Short status;@TableField(exist = false)private List<TRole> roles;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return roles;}@Overridepublic String getUsername() {return userName;}
}@Getter
@Setter
@TableName("t_role")
public class TRole implements Serializable, GrantedAuthority {private static final long serialVersionUID = 1L;@TableId(value = "role_id", type = IdType.AUTO)private Integer roleId;private String roleName;private String roleDesc;private Short status;@Overridepublic String getAuthority() {return roleName;}
}
  1. userDetailsService就已经改造完了,现在可以通过前端传过来的用户名去数据库查询到用户信息了,接下来就是走PasswordEncoder,校验前端传来的password跟数据库查出来的User的password是否一致,这里先使用明文密码校验的方式,我们需要自定义一个PasswordEncoder,返回一个 DelegatingPasswordEncoder对象。

    正常来说的话,SpringSecurity框架存储密码的形式是{SHA-1}ajzcvkzbcz=,前面的{SHA-1}指定了密码的加密方式,它会根据我们数据库存储的密码的前缀,获取到加密方式,用来加密前端传来的密码,再对比两个密文是否一致,但这里使用的明文进行比较,需要使用到一个不安全的类NoOpPasswordEncoder,默认的DelegatingPasswordEncoder由PasswordEncoderFactories创建,直接复制它的代码,再加一行encoders.put(null, NoOpPasswordEncoder.getInstance());因为明文获取不到类似{SHA-1}这种标识,所以key是空时,就直接匹配明文校验器

    @Beanpublic PasswordEncoder passwordEncoder() {String encodingId = "bcrypt";Map<String, PasswordEncoder> encoders = new HashMap<>();encoders.put(encodingId, new BCryptPasswordEncoder());encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());encoders.put("pbkdf2", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5());encoders.put("pbkdf2@SpringSecurity_v5_8", Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8());encoders.put("scrypt", SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1());encoders.put("scrypt@SpringSecurity_v5_8", SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8());encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));encoders.put("SHA-256",new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());encoders.put("argon2", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2());encoders.put("argon2@SpringSecurity_v5_8", Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8());encoders.put(null, NoOpPasswordEncoder.getInstance());return new DelegatingPasswordEncoder(encodingId, encoders);}
  1. 最终发起登录请求成功,后端验证成功后给出了302跳转应答
    在这里插入图片描述
  2. 把生成的Authentication信息跟http会话相关联
    上面是登录接口认证流程,如果我们需要访问其他接口,那么需要在登录成功后,把登录信息存储到当前会话中,需要修改UsernamePasswordAuthenticationFilter定义,增加一行SecurityContextRepository设置,指向HttpSessionSecurityContextRepository,这样就可以把Authentication存储到会话中:
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter();myUsernamePasswordAuthenticationFilter.setFilterProcessesUrl("/user/login");myUsernamePasswordAuthenticationFilter.setUsernameParameter("userName");myUsernamePasswordAuthenticationFilter.setPasswordParameter("password");myUsernamePasswordAuthenticationFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
}

在此发起登录认证,查看应答可以看到有Set-Cookie JSESSIONID=3D3BA03492282FB5E72ABDD2FFB987E9,有这个会话Id,让我们设置cookie,这里直接使用cookie存储会话Id会引起csrf攻击,但先不考虑csrf,把其他接口的调用流程调通先
在这里插入图片描述

  1. 会话校验逻辑
    通过上面Response Header中的SessionId,访问其他接口时Cookie带上该SessionId,就不会被AuthorizationFilter校验住,校验会话的大致流程如下:
SessionId
Authentication
request
SecurityContextHolderFilter
AuthorizationFilter
  • 请求传来了SessionId,SecurityContextHolderFilter使用SessionId获取关联的会话,从中获取Authentication信息-
  • AuthorizationFilter校验Authentication是否校验通过

SecurityContextHolderFilter中也会使用到SecurityContextRepository,但它只用来加载Authentication,默认配置带有HttpSessionSecurityContextRepository,使用到httpSession管理Authentication:
在这里插入图片描述

发起用户信息查询请求,Cookie加上会话SessionId,接口可以正常返回
在这里插入图片描述

如果不带上SessionId,或带上错误的SessionId,则接口的应答状态变成302,指示浏览器跳转到登录页面进行登录认证:
在这里插入图片描述
通过上面9个步骤,会话校验流程已经修改完毕


增加CSRF Token

上面登录认证流程实现了用户会话访问接口,但是SessionId存储在cookie中,这样会导致csrf攻击,所以前端还需要把SessionId存储到其他位置,比如localStorage,在请求时,把它放到Header中,避免csrf攻击,或者直接生成一个新的csrf token,在每次请求时把它放到header或parameter中,在后端服务进行验证。

首先是获取CsrfToken的流程:

/token GET
request.setAttribute CsrfToken
get CsrfToken From Request + Save token in session
Request
CsrfFilter
TokenController
Response
  1. Controller增加一个获取csrfToken的方法
@RestController
@RequestMapping("/token")
public class TokenController {@GetMappingpublic ResponseEntity<CsrfToken> getToken(HttpServletRequest request) {CsrfToken deferredCsrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());return ResponseEntity.ok(deferredCsrfToken);}
}
  1. 进行csrfToken配置
    设置CsrfFilter不校验/token请求,因为这个是用来获取CsrfToken的接口,一开始是没有CsrfToken的:
    @Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf((t) -> {t.ignoringRequestMatchers("/token");});}
  1. ·发起请求,从应答头获取SessionId,从应答内容获取csrfToken
    在这里插入图片描述

  2. 使用第3步获取的SessionId跟 csrfToken,发起登录请求

    请求头带上X-CSRF-TOKEN,Cookie带上SessionId,发起登录请求,需要注意登录成功时,需要更新当前的SessionId跟csrfToken,避免会话固定攻击,在UsernamePasswordAuthenticationFilter设置一些额外策略,可以帮助我们自动更新这两个值:

    • CsrfAuthenticationStrategy:清理掉Session中当前的CsrfToken,可以再次调用/token接口获取token,或者是新建一个Strategy类,把新的token设置到应答中

    • SessionFixationProtectionStrategy:重新生成会话,在应答的SetCookie中返回一个新的SessionId

      我这里把MyUsernamePasswordAuthenticationFilter 单独抽取出来定义,同时增加了一个CsrfSaveAuthenticationStrategy,在登录认证成功后,它会把CsrfAuthenticationStrategy生成的csrfToken放到response头部中:

    @Beanpublic MyUsernamePasswordAuthenticationFilter UsernamePasswordAuthenticationFilter(AuthenticationConfiguration authenticationConfiguration) {try {MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter();myUsernamePasswordAuthenticationFilter.setFilterProcessesUrl("/user/login");myUsernamePasswordAuthenticationFilter.setUsernameParameter("userName");myUsernamePasswordAuthenticationFilter.setPasswordParameter("password");myUsernamePasswordAuthenticationFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());// 增加策略,登录成功后更新csrftoken跟SessionIdList<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();// 下面这个策略是移除session中的老csrfToken,生成新的放到request的Attribute中sessionAuthenticationStrategies.add(new CsrfAuthenticationStrategy(new HttpSessionCsrfTokenRepository()));// 下面这个是重新生成一个Session,把原来的数据放到新Session中sessionAuthenticationStrategies.add(new SessionFixationProtectionStrategy());// 下面这个是我自定义的,把新的csrfToken从request的Atribute中取出,放到Response的Header中sessionAuthenticationStrategies.add(new CsrfSaveAuthenticationStrategy());myUsernamePasswordAuthenticationFilter.setSessionAuthenticationStrategy(new CompositeSessionAuthenticationStrategy(sessionAuthenticationStrategies));myUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationConfiguration.getAuthenticationManager());return myUsernamePasswordAuthenticationFilter;} catch (Exception e) {throw new RuntimeException(e);}}@Getter@Setterclass CsrfSaveAuthenticationStrategy implements SessionAuthenticationStrategy {@Overridepublic void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException {CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());String token = csrfToken.getToken();String headerName = csrfToken.getHeaderName();response.addHeader(headerName,token);}}

在这里插入图片描述

  1. 使用新的SessionId跟CsrfToken,请求其他接口,接口返回200,请求正常
    在这里插入图片描述
    如果是一个不存在的CsrfToken,则请求失败,应答码302,指示浏览器跳转到登录界面重新登录:
    在这里插入图片描述
    通过上面5个步骤,CsrfToken的添加就已经完成了,可以根据项目的实际需要修改。

增加方法权限校验

根据上面配置,用户登录后,可以使用SessionId代表一个用户,每个用户拥有各自的角色信息,我们可以根据用户的角色信息,来判断当前用户是否有权限访问谋接口,这里我给test2用户增加了USER_MANAGE这个Role,然后给添加用户的接口设置拥有该角色才能访问

  1. 配置Role数据,同时插入用户角色关联信息
    在这里插入图片描述
    2.同时用户跟角色的Bean对象需要分别继承UserDetails,GrantedAuthority,实现getAuthorities跟getAuthority方法,这个步骤在之前的用户登录校验就已经做好适配了,框架用getAuthorities()获取当前用户的Role列表,再使用getAuthority获取角色的授权信息,这里返回的是roleName字段
@Getter
@Setter
@TableName("t_user")
public class TUser implements Serializable, UserDetails {private static final long serialVersionUID = 1L;@TableId(value = "user_id", type = IdType.AUTO)private Integer userId;private String userName;private String password;private LocalDateTime createTime;private Short status;@TableField(exist = false)private List<TRole> roles;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return roles;}@Overridepublic String getUsername() {return userName;}
}@Getter
@Setter
@TableName("t_role")
public class TRole implements Serializable, GrantedAuthority {private static final long serialVersionUID = 1L;@TableId(value = "role_id", type = IdType.AUTO)private Integer roleId;private String roleName;private String roleDesc;private Short status;@Overridepublic String getAuthority() {return roleName;}
}
  1. Security Configuration配置开启方法鉴权@EnableMethodSecurity(securedEnabled = true),没有添加这个注解的话,权限校验是不会生效的
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfiguration {
}
  1. 添加用户的接口设置访问需要的权限,@Secured(“USER_MANAGE”)
    @PostMapping@Secured("USER_MANAGE")public ResponseEntity addUser(@RequestBody TUser tUser) {userService.save(tUser);return ResponseEntity.ok().build();}
  1. 使用授权用户,发起请求,请求应答成功
    在这里插入图片描述

使用授权用户,发起请求,请求应发返回403,后端拒绝该请求
在这里插入图片描述

相关文章:

Spring Security使用

文章目录 Spring Security的起点FilterChain重写重写登录验证逻辑增加CSRF Token增加方法权限校验 Spring Security的起点 在AbstractApplicationContext.refresh()方法时&#xff0c;子类ServletWebServerApplicationContext会创建一个ServletContextInitializerBeans这个Bea…...

CSS网页布局综合练习(涵盖大多CSS知识点)

该综合练习就是为这个学校静态网页设置CSS样式&#xff0c;使其变成下面的模样 其基本骨架代码为&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content…...

解决 Hardhat Verify 超时

问题背景 今天在学习使用Hardhat进行verify 合约 到 Ethscan的时候&#xff0c;出现了如下报错 fafafafadeMacBook-Air Web3_Solidity_Study % npx hardhat verify --network sepolia XXXXXXXXXXXXXXXXXXXXXXXX "10" Successfully verifie…...

ACIS创建各种基本体,举例说明

ACIS&#xff08;Advanced CAD Interoperability System&#xff09;是一个广泛使用的三维几何建模内核&#xff0c;它支持创建和操作各种基本的三维几何体。虽然ACIS没有专门的函数来直接创建某些特定的基本体&#xff08;如椭球体&#xff09;&#xff0c;但可以通过一系列变…...

[CISCN 2019华北]PWN1-好久不见7

Partial RELRO 表示部分 RELRO 保护已启用。在这种情况下&#xff0c;只有某些部分&#xff08;如 GOT 中的只读部分&#xff09;是只读的。 NX enabled 表示这个二进制文件启用了 NX 保护&#xff0c;数据段是不可执行的。这可以防止某些类型的代码注入攻击。 这里是ida识别…...

代码随想录day16| 513找树左下角的值 、 路径总和 、 从中序与后序遍历序列构造二叉树

代码随想录day16| 找树左下角的值 、 路径总和 、 从中序与后序遍历序列构造二叉树 513找树左下角的值层序遍历法递归法 路径总和112. 路径总和113. 路径总和 II 从中序与后序遍历序列构造二叉树思路 513找树左下角的值 层序遍历法 使用层序遍历&#xff0c;找到最后一层最左边…...

使用 MMDetection 实现 Pascal VOC 数据集的目标检测项目练习(二) ubuntu的下载安装

首先&#xff0c;Linux系统是人工智能和深度学习首选系统。原因如下: 开放性和自由度&#xff1a;Linux 是一个开源操作系统&#xff0c;允许开发者自由修改和分发代码。这在开发和研究阶段非常有用&#xff0c;因为开发者可以轻松地访问和修改底层代码。社区支持&#xff1a;…...

书生大模型实战营(第四期)——入门岛

第 1 关 Linux 前置基础 闯关任务完成SSH连接与端口映射并运行hello_world.py10min可选任务 1将Linux基础命令在开发机上完成一遍10min可选任务 2使用 VSCODE 远程连接开发机并创建一个conda环境10min 完成SSH连接 创建python文件 建环境 运行 第 2 关 Python 前置基础 Leet…...

压强随着时间的变化

import numpy as np import matplotlib.pyplot as plt# 参数设置 L 50 # 长度 (m) D 4 # 直径 (m) d 0.01 # 洞的直径 (m) P0 101300 # 初始压力 (Pa) P_final 0.3 * P0 # 最终压力 (Pa) R 287 # 理想气体常数 (J/(kgK)) T 20 273.15 # 温度 (K) M 0.029 # 空…...

2024年大厂AI大模型面试题精选与答案解析

前言 随着AI市场&#xff0c;人工智能的爆火&#xff0c;在接下来的金九银十招聘高峰期&#xff0c;各大科技巨头和国有企业将会对AGI人才的争夺展开一场大战&#xff0c;为求职市场注入了新的活力。 为了助力求职者在面试中展现最佳状态&#xff0c;深入理解行业巨头的选拔标…...

Linux开发讲课47--- 详解 Linux 中的虚拟文件系统

虚拟文件系统是一种神奇的抽象&#xff0c;它使得 “一切皆文件” 哲学在 Linux 中成为了可能。 什么是文件系统&#xff1f;根据早期的 Linux 贡献者和作家 Robert Love 所说&#xff0c;“文件系统是一个遵循特定结构的数据的分层存储。” 不过&#xff0c;这种描述也同样适用…...

全球银行常用英语

Earn OCBC$ or 90 Miles or VOYAGE Miles today! Get the most out of your OCBC Card with OCBC Privileges. 今天赚取华侨银行美元或 90 英里或航程英里&#xff01;通过华侨银行特权充分利用您的华侨银行卡。 Check out the rewards catalogue. Apply for a OCBC Credit Car…...

新160个crackme -090-tc.12

运行分析 需要破解注册码 PE分析 Delphi程序&#xff0c;32位&#xff0c;无壳 静态分析&动态调试 ida搜不到字符串&#xff0c;根据Deiphi程序的结构&#xff0c;直接打开来到start函数&#xff0c;找到CreateForm函数的参数off_445FC4&#xff0c;双击 逐个查找偏移&…...

Swagger文档-Unable to scan documentation context default报错

文章目录 报错情况&#xff1a; Unable to scan documentation context 管理端接口发生情况一&#xff1a;发生情况三&#xff1a; 报错情况&#xff1a; Unable to scan documentation context 管理端接口 报错日志&#xff1a; 2024-11-03 12:40:27.427 ERROR 3340 --- [ …...

SpringKafka生产者、消费者消息拦截

1 前言 在Spring Kafka中&#xff0c;可以通过配置拦截器来实现对生产者和消费者消息的拦截。拦截器可以用来记录日志、修改消息等等。 2 基于Kafka管理的拦截器 Kafka原生提供的拦截器接口是org.apache.kafka.clients.producer.ProducerInterceptor和 org.apache.kafka.cli…...

Qt报错QOCI driver not loaded且QOCI available的解决方法

参考 Linux Qt 6安装Oracle QOCI SQL Driver插件&#xff08;适用WSL&#xff09; 安装 QOCI 插件完成后运行 Qt 项目报错&#xff1a; qt.sql.qsqldatabase: QSqlDatabase: QOCI driver not loaded qt.sql.qsqldatabase: QSqlDatabase: available drivers: QMIMER QPSQL QODBC…...

python mac vscode 脚本文件的运行

切换到脚本文件的目录下 路径的修改 当前文件组织形式&#xff1a; 脚本文件在文件夹下&#xff1a; 赋予权限&#xff1a;chmod x ./scripts/fscd_test.sh 运行&#xff1a;./scripts/fscd_test.sh...

Linux之du命令

华子目录 du命令常用选项示例注意事项 du命令 du&#xff08;Disk Usage&#xff09;命令是用于在类Unix操作系统&#xff08;如Linux和macOS&#xff09;中显示文件和目录所占用的磁盘空间大小的工具。它可以递归地计算目录和文件的磁盘使用情况&#xff0c;并提供详细的报告…...

WRF-LES与PALM微尺度气象大涡模拟

针对微尺度气象的复杂性&#xff0c;大涡模拟&#xff08;LES&#xff09;提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟&#xff0c;这些过程往往与天气模式、地形影响和人为因素如城市布局紧密相关。在这种规模上&#xff0c;传统的气象模…...

桌面程序开发框架选择

桌面程序开发框架选择 1、WinForm(Windows Form)优点缺点 2、WPF(Windows Presentation Foundation)优点缺点 3、Electron优点缺点 4、Delphi优点缺点 5、QT优点缺点 6、MFC(Microsoft Foundation Class Library)优点缺点 7、JavaFX优点缺点 8、SwingAWT9、Avalonia10、Flutter…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会

在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...

redis和redission的区别

Redis 和 Redisson 是两个密切相关但又本质不同的技术&#xff0c;它们扮演着完全不同的角色&#xff1a; Redis: 内存数据库/数据结构存储 本质&#xff1a; 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能&#xff1a; 提供丰…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...

基于Uniapp的HarmonyOS 5.0体育应用开发攻略

一、技术架构设计 1.混合开发框架选型 &#xff08;1&#xff09;使用Uniapp 3.8版本支持ArkTS编译 &#xff08;2&#xff09;通过uni-harmony插件调用原生能力 &#xff08;3&#xff09;分层架构设计&#xff1a; graph TDA[UI层] -->|Vue语法| B(Uniapp框架)B --&g…...