Spring Security使用
文章目录
- Spring Security的起点
- FilterChain重写
- 重写登录验证逻辑
- 增加CSRF Token
- 增加方法权限校验
Spring Security的起点
- 在AbstractApplicationContext.refresh()方法时,子类ServletWebServerApplicationContext会创建一个ServletContextInitializerBeans这个Bean对象
- ServletContextInitializerBeans在执行addServletContextInitializerBeans()时会使用BeanFactory去查找ServletContextInitializer类型的Bean,这时会找到DelegatingFilterProxyRegistrationBean这个bean对象
- DelegatingFilterProxyRegistrationBean会往ServletContext注册DelegatingFilterProxy对象,对象包含了filter的默认名称springSecurityFilterChain,可以看成这个DelegatingFilterProxy对象就是一个Filter,它又包含了filterChain,相当于又做了一层包装
- DelegatingFilterProxy使用filter的名字(springSecurityFilterChain)在BeanFactory中查找该filter对象
- 后续请求来时,会先进入DelegatingFilterProxy这个外层的Tomcat Filter,然后它再把请求传给springSecurityFilterChain这个内部的Filter Chain进行处理
- 默认的springSecurityFilterChain对象定义在org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration.SecurityFilterChainConfiguration#defaultSecurityFilterChain
- 我们一般要重写这个filterChain的定义
过滤链的流程大致如下:
FilterChain重写
一般来说,filterChain是我们需要重写的,我们的应用是无法直接使用默认filter的配置
- Configuration类需要添加@EnableWebSecurity,才会有HttpSecurity对象。
- 重写主要是对HttpSecurity对象进行设置,FilterChain里的Filter配置,来自于HttpSecurity中的各个Configurer,例如CsrfConfigurer用于创建CsrfFilter,FormLoginConfigurer用于创建UsernamePasswordAuthenticationFilter,一般都是在Configurer.configure()方法中创建并添加各种filter的。
- HttpSecurity中的formLogin(Customizer)或者是csrf(Customizer)方法,Customizer是一个函数式表达式,提供让我们对这些Configurer进行自定义,就是HttpSecurity会自己创建好各种Configurer,但是还提供了方法让我们去修改这些Configurer的配置,如果不需要进行修改,就传入Customizer.withDefaults()就可以了,它默认直接返回configurer,不做修改。
- 如下图,formLogin(formLoginConfigurer->{})可以对formLoginConfigurer的配置进行修改,里面我就修改了loginProcessingUrl,这样在请求/user/login时,它创建的UsernamePasswordAuthenticationFilter会判断,如果当前请求的路径跟该路径匹配,就会走验证用户名密码的逻辑,它的默认路径是/login,我的应用程序的登录接口是/user/login,两者不匹配是不会走用户名密码验证逻辑的。
- 同时formLogin修改了usernameParameter跟passwordParameter,UsernamePasswordAuthenticationFilter会从请求中使用这两个名称从request中获取用户名跟密码去进行校验,默认值是"username"跟"password",如果请求参数跟这两个值不匹配,那么获取到的用户名密码就是null,后面的验证逻辑就走不下去了,这些默认值跟程序不匹配的地方都需要修改。
- 还有csrf,这里为了方便测试接口,不让CsrfFilter因为我没有token拦截我的请求,我修改让它对所有请求都忽略。
- 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();}
}
重写登录验证逻辑
以上是登录校验逻辑走的流程
- 由于我的应用程序是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);}}}
- 上面这样做,又会引出一个问题,获取到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()));}}
- 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);}}}
- 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;}
- 重写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;}
}
-
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);}
- 最终发起登录请求成功,后端验证成功后给出了302跳转应答

- 把生成的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,把其他接口的调用流程调通先

- 会话校验逻辑
通过上面Response Header中的SessionId,访问其他接口时Cookie带上该SessionId,就不会被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的流程:
- 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);}
}
- 进行csrfToken配置
设置CsrfFilter不校验/token请求,因为这个是用来获取CsrfToken的接口,一开始是没有CsrfToken的:
@Bean@Order(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf((t) -> {t.ignoringRequestMatchers("/token");});}
-
·发起请求,从应答头获取SessionId,从应答内容获取csrfToken

-
使用第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);}}

- 使用新的SessionId跟CsrfToken,请求其他接口,接口返回200,请求正常

如果是一个不存在的CsrfToken,则请求失败,应答码302,指示浏览器跳转到登录界面重新登录:

通过上面5个步骤,CsrfToken的添加就已经完成了,可以根据项目的实际需要修改。
增加方法权限校验
根据上面配置,用户登录后,可以使用SessionId代表一个用户,每个用户拥有各自的角色信息,我们可以根据用户的角色信息,来判断当前用户是否有权限访问谋接口,这里我给test2用户增加了USER_MANAGE这个Role,然后给添加用户的接口设置拥有该角色才能访问
- 配置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;}
}
- Security Configuration配置开启方法鉴权@EnableMethodSecurity(securedEnabled = true),没有添加这个注解的话,权限校验是不会生效的
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfiguration {
}
- 添加用户的接口设置访问需要的权限,@Secured(“USER_MANAGE”)
@PostMapping@Secured("USER_MANAGE")public ResponseEntity addUser(@RequestBody TUser tUser) {userService.save(tUser);return ResponseEntity.ok().build();}
- 使用授权用户,发起请求,请求应答成功

使用授权用户,发起请求,请求应发返回403,后端拒绝该请求

相关文章:
Spring Security使用
文章目录 Spring Security的起点FilterChain重写重写登录验证逻辑增加CSRF Token增加方法权限校验 Spring Security的起点 在AbstractApplicationContext.refresh()方法时,子类ServletWebServerApplicationContext会创建一个ServletContextInitializerBeans这个Bea…...
CSS网页布局综合练习(涵盖大多CSS知识点)
该综合练习就是为这个学校静态网页设置CSS样式,使其变成下面的模样 其基本骨架代码为: <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content…...
解决 Hardhat Verify 超时
问题背景 今天在学习使用Hardhat进行verify 合约 到 Ethscan的时候,出现了如下报错 fafafafadeMacBook-Air Web3_Solidity_Study % npx hardhat verify --network sepolia XXXXXXXXXXXXXXXXXXXXXXXX "10" Successfully verifie…...
ACIS创建各种基本体,举例说明
ACIS(Advanced CAD Interoperability System)是一个广泛使用的三维几何建模内核,它支持创建和操作各种基本的三维几何体。虽然ACIS没有专门的函数来直接创建某些特定的基本体(如椭球体),但可以通过一系列变…...
[CISCN 2019华北]PWN1-好久不见7
Partial RELRO 表示部分 RELRO 保护已启用。在这种情况下,只有某些部分(如 GOT 中的只读部分)是只读的。 NX enabled 表示这个二进制文件启用了 NX 保护,数据段是不可执行的。这可以防止某些类型的代码注入攻击。 这里是ida识别…...
代码随想录day16| 513找树左下角的值 、 路径总和 、 从中序与后序遍历序列构造二叉树
代码随想录day16| 找树左下角的值 、 路径总和 、 从中序与后序遍历序列构造二叉树 513找树左下角的值层序遍历法递归法 路径总和112. 路径总和113. 路径总和 II 从中序与后序遍历序列构造二叉树思路 513找树左下角的值 层序遍历法 使用层序遍历,找到最后一层最左边…...
使用 MMDetection 实现 Pascal VOC 数据集的目标检测项目练习(二) ubuntu的下载安装
首先,Linux系统是人工智能和深度学习首选系统。原因如下: 开放性和自由度:Linux 是一个开源操作系统,允许开发者自由修改和分发代码。这在开发和研究阶段非常有用,因为开发者可以轻松地访问和修改底层代码。社区支持:…...
书生大模型实战营(第四期)——入门岛
第 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市场,人工智能的爆火,在接下来的金九银十招聘高峰期,各大科技巨头和国有企业将会对AGI人才的争夺展开一场大战,为求职市场注入了新的活力。 为了助力求职者在面试中展现最佳状态,深入理解行业巨头的选拔标…...
Linux开发讲课47--- 详解 Linux 中的虚拟文件系统
虚拟文件系统是一种神奇的抽象,它使得 “一切皆文件” 哲学在 Linux 中成为了可能。 什么是文件系统?根据早期的 Linux 贡献者和作家 Robert Love 所说,“文件系统是一个遵循特定结构的数据的分层存储。” 不过,这种描述也同样适用…...
全球银行常用英语
Earn OCBC$ or 90 Miles or VOYAGE Miles today! Get the most out of your OCBC Card with OCBC Privileges. 今天赚取华侨银行美元或 90 英里或航程英里!通过华侨银行特权充分利用您的华侨银行卡。 Check out the rewards catalogue. Apply for a OCBC Credit Car…...
新160个crackme -090-tc.12
运行分析 需要破解注册码 PE分析 Delphi程序,32位,无壳 静态分析&动态调试 ida搜不到字符串,根据Deiphi程序的结构,直接打开来到start函数,找到CreateForm函数的参数off_445FC4,双击 逐个查找偏移&…...
Swagger文档-Unable to scan documentation context default报错
文章目录 报错情况: Unable to scan documentation context 管理端接口发生情况一:发生情况三: 报错情况: Unable to scan documentation context 管理端接口 报错日志: 2024-11-03 12:40:27.427 ERROR 3340 --- [ …...
SpringKafka生产者、消费者消息拦截
1 前言 在Spring Kafka中,可以通过配置拦截器来实现对生产者和消费者消息的拦截。拦截器可以用来记录日志、修改消息等等。 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插件(适用WSL) 安装 QOCI 插件完成后运行 Qt 项目报错: qt.sql.qsqldatabase: QSqlDatabase: QOCI driver not loaded qt.sql.qsqldatabase: QSqlDatabase: available drivers: QMIMER QPSQL QODBC…...
python mac vscode 脚本文件的运行
切换到脚本文件的目录下 路径的修改 当前文件组织形式: 脚本文件在文件夹下: 赋予权限:chmod x ./scripts/fscd_test.sh 运行:./scripts/fscd_test.sh...
Linux之du命令
华子目录 du命令常用选项示例注意事项 du命令 du(Disk Usage)命令是用于在类Unix操作系统(如Linux和macOS)中显示文件和目录所占用的磁盘空间大小的工具。它可以递归地计算目录和文件的磁盘使用情况,并提供详细的报告…...
WRF-LES与PALM微尺度气象大涡模拟
针对微尺度气象的复杂性,大涡模拟(LES)提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟,这些过程往往与天气模式、地形影响和人为因素如城市布局紧密相关。在这种规模上,传统的气象模…...
桌面程序开发框架选择
桌面程序开发框架选择 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…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
