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

【深入浅出 Spring Security(七)】RememberMe的实现原理详讲

RememberMe 的实现原理

  • 一、RememberMe 的基本使用
  • 二、RememberMeAuthenticationFilter 源码分析
    • RememberMeServices
      • TokenBasedRememberMeServices
        • TokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现
        • 总结
        • 原理图式
  • 三、提高安全性
    • PersistentTokenBasedRememberMeServices
    • 内存令牌登录测试
  • 四、令牌数据库的持久化
  • 五、总结

一、RememberMe 的基本使用

先看看最简单用法的默认页面效果变化。

SecurityConfig 配置类

@EnableWebSecurity
public class SecurityConfig {@Beanpublic InMemoryUserDetailsManager inMemoryUserDetailsManager(){return new InMemoryUserDetailsManager(User.withUsername("admin").password("{noop}123").roles("admin").build());}@Beanpublic AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(inMemoryUserDetailsManager()).and().build();}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe().and().csrf().disable().build();}}

测试 TestController 代码

@RestController
public class TestController {@GetMapping("/test")public String test(){return "test";}}

以下是给出的默认的登录页面。

在这里插入图片描述
观察页面源代码可以发现,比原先没配置 RememberMe 之前多了个 name 为remember-me 的 checkbox 选项。

在这里插入图片描述
如果我们勾选了它并且登录成功后,当我们关闭掉当前浏览器,重新打开,是不必进行重新登录就可以访问登录用户所能访问了的资源的。在 【深入浅出Spring Security(二)】Spring Security的实现原理 中小编列举了 Spring Security 中自带的过滤器,其中不是默认加载的队列中有个叫 RememberMeAuthenticationFilter ,它是用来处理 RememberMe 登录的。下面来通过对 RememberMeAuthenticationFilter 进行源码分析,了解 RememberMe 登录的实现原理。

二、RememberMeAuthenticationFilter 源码分析

我们知道 RememberMeAuthenticationFilter 是一个过滤器,其核心代码即在 doFilter 方法中,接下来就是对这个方法的源码分析。

doFilter 方法中主要实现可分为三步:

  1. 请求到达过滤器后,首先判断 SecurityContextHolder 中是否有值,没值的话表示用户尚未登录,此时调用 autoLogin 方法进行自动登录。
  2. 当自动登录成功后返回的 rememberMeAuth 不为 null 时,表示自动登录成功,此时调用 authenticate方法对 key 进行校验,并且将登录成功的用户信息保存到 SecurityContextHolder 对象中,然后调用登录成功的回调,并发布登录成功时间。需要注意的是:登录成功的回调并不包含 RememberMeServices 中的 loginSuccess 方法。
  3. 失败的话会进行一些登录失败的回调,打印失败的日志信息。

可以看见其实现还得看 rememberMeServices 中的 autoLogin 方法,是否登录成功决定了后面成功和失败的回调。

// RememberMeAuthenticationFilter 中的 doFilter 方法private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {/*1. 请求到达过滤器后,首先判断 SecurityContextHolder 中是否有值,没值的话表示用户尚未登录,此时调用 autoLogin 方法进行自动登录
*/if (SecurityContextHolder.getContext().getAuthentication() != null) {this.logger.debug(LogMessage.of(() -> "SecurityContextHolder not populated with remember-me token, as it already contained: '"+ SecurityContextHolder.getContext().getAuthentication() + "'"));chain.doFilter(request, response);return;}Authentication rememberMeAuth = this.rememberMeServices.autoLogin(request, response);/*2. 当自动登录成功后返回的 rememberMeAuth 不为 null 时,表示自动登录成功,此时调用 authentication 方法对 key 进行校验,并且将登录成功的用户信息保存到 SecurityContextHolder 对象中,然后调用登录成功的回调,并发布登录成功时间。需要注意的是:登录成功的回调并不包含 RememberMeServices 中的 loginSuccess 方法。*/if (rememberMeAuth != null) {// Attempt authenticaton via AuthenticationManagertry {rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);SecurityContext context = SecurityContextHolder.createEmptyContext();context.setAuthentication(rememberMeAuth);SecurityContextHolder.setContext(context);onSuccessfulAuthentication(request, response, rememberMeAuth);// 这里打印了保存到 SecurityContextHolder 中的authentication日志信息this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '"+ SecurityContextHolder.getContext().getAuthentication() + "'"));this.securityContextRepository.saveContext(context, request, response);if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(SecurityContextHolder.getContext().getAuthentication(), this.getClass()));}if (this.successHandler != null) {this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);return;}}catch (AuthenticationException ex) {/*3. 失败的话会进行一些登录失败的回调,打印失败的日志信息
*/this.logger.debug(LogMessage.format("SecurityContextHolder not populated with remember-me token, as AuthenticationManager "+ "rejected Authentication returned by RememberMeServices: '%s'; "+ "invalidating remember-me token", rememberMeAuth),ex);this.rememberMeServices.loginFail(request, response);onUnsuccessfulAuthentication(request, response, ex);}}chain.doFilter(request, response);}

RememberMeServices

RememberMeServices 是一个接口,其中定义了三个方法:

  • autoLogin 方法可以从请求中提取出需要的参数,完成自动登录功能;
  • loginFail 方法是自动登录失败的回调;
  • loginSuccess 方法是自动登录成功的回调。

下面是 Spring Security 中对 RememberMeServices 的实现类
在这里插入图片描述

TokenBasedRememberMeServices

TokenBasedRememberMeServices 是 Spring Security 的默认实现,接下来对其 autoLogin 进行源码分析,其实 autoLogin 是 AbstractRememberMeServices 中的一个方法,其子类并没有进行重写。子类重点重写的是 processAutoLoginCookie 方法。

先看看 AbstractRememberMeServices 中实现的 autoLogin 方法(下面再对其内部调用的核心方法进行分析)

// AbstractRememberMeServices 中实现的 autoLogin 方法@Overridepublic final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {// 从request 对象中取对应 remember-me 名的 cookie 值String rememberMeCookie = extractRememberMeCookie(request);// 如果不存在的话就返回空if (rememberMeCookie == null) {return null;}this.logger.debug("Remember-me cookie detected");// 或者说是一个空值也返回空if (rememberMeCookie.length() == 0) {this.logger.debug("Cookie was empty");cancelCookie(request, response);return null;}try {// 先进行base64解码// 然后对rememberMeCookie进行 :分割String[] cookieTokens = decodeCookie(rememberMeCookie);// 自动登录认证吧,算是// 去把用户名、密码、时长搞成MD5然后和cookie返回来的进行比对,认证过程在这个方法里UserDetails user = processAutoLoginCookie(cookieTokens, request, response);this.userDetailsChecker.check(user);this.logger.debug("Remember-me cookie accepted");// 创建一个RememberMeAuthenticationToken即为认证数据源return createSuccessfulAuthentication(request, user);}cancelCookie(request, response);return null;
}

获取对应 remember-name 名的 Cookie 值的方法源码如下

在这里插入图片描述
将 Cookie 值进行 base64 解码,分割成字符串数组进行返回。

在这里插入图片描述

TokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现

TokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现,它主要是用来验证 Cookie 中的令牌信息是否合法的(下面说的 CookieTokens 是上面用base64解密,再分割的字符串数组):

  1. 首先判断 CookieTokens 长度是否为 3,不为 3 就抛出异常。
  2. 从 CookieTokens 中下标为 1 的值,也就是过期时间,判断令牌是否过期,如果已经过期,则抛出异常;
  3. 根据用户名(CookieTokens 下标为 0 的值)查询当前用户对象;
  4. 调用 makeTokenSignature 方法生成一个签名,签名的生成过程如下:首先将用户名、令牌过期时间、用户名密码以及key 组成一个字符串,中间用“:”隔开,然后通过 MD5 消息摘要算法对该字符串进行加密,并将加密结果转换为一个字符串返回。
  5. 判断第 4 步生成的签名和通过 Cookie 传来的签名是否相等(即 cookieTokens 数组小标为 2 的值),如果相等,则表示令牌合法,直接返回用户对象,否则抛出异常。

下面是其源码,小编标号编号,与上解析相互对应

	@Overrideprotected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,HttpServletResponse response) {// 1if (cookieTokens.length != 3) {throw new InvalidCookieException("Cookie token did not contain 3" + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");}// 2long tokenExpiryTime = getTokenExpiryTime(cookieTokens);if (isTokenExpired(tokenExpiryTime)) {throw new InvalidCookieException("Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime)+ "'; current time is '" + new Date() + "')");}// 3UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);Assert.notNull(userDetails, () -> "UserDetailsService " + getUserDetailsService()+ " returned null for username " + cookieTokens[0] + ". " + "This is an interface contract violation");// 4String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),userDetails.getPassword());// 5if (!equals(expectedTokenSignature, cookieTokens[2])) {throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]+ "' but expected '" + expectedTokenSignature + "'");}return userDetails;}

总结

当用户通过用户名/密码的形式登录成功后,系统会根据用户的用户名、密码以及令牌的过期时间计算出一个签名,这个签名使用 MD5 消息摘要算法生成,是不可逆的。然后再将用户名、令牌过期时间以及签名拼接成一个字符串,中间用":"隔开,对拼接好的字符串进行 Base64 编码,然后将编码后的字符串一起封装成 Cookie 值,返回给前端,也就是我们在浏览器中看到的令牌。当关闭浏览器再次打开,访问系统资源时会自动携带上Cookie中的令牌,服务端拿到 Cookie 中的令牌后,先进行 Base64 解码,解码之后分别提取令牌中的三项数据;接着根据令牌中的数据判断令牌是否过期,如果没有过期,则根据令牌中的用户名查询出用户信息;接着再计算出一个签名和令牌中的签名进行比对,如果一致,表示会牌是合法令牌,自动登录成功,否则自动登录失败。

在这里插入图片描述

原理图式

在这里插入图片描述

三、提高安全性

通过上面的源码分析,我们知道,登录认证成功会去调用 TokenBasedRememberMeServices 中的 onLoginSuccess 方法生成 Cookie 响应给浏览器(其实对应的Cookie值就是Base6
编码后的值
),浏览器会保存下来,当我们关闭浏览器后,可以利用请求报文中带有的这个 Cookie 进行认证从而访问到资源。这种免登录的方式是缺乏不安全的,有关 remember-me 的 Cookie 值暴露在外面,对隐私是有害的,那有什么办法让它变得安全吗?答案是没有绝对的安全,只能说是提高它的安全性。

PersistentTokenBasedRememberMeServices

PersistentTokenBasedRememberMeServices 中的 onLoginSuccess 和 processAutoLoginCookie 不同于 TokenBasedRememberMeServices,下面看一下它的具体实现。

onLoginSuccess 具体实现

在这里插入图片描述

  1. 先从认证数据源中获取用户名,然后将一序列号和用户名封装成一个 PersistentRememberMeToken 数据源;
  2. 将这个数据源放入到 tokenRepository 仓库中,它是一个基于内存的仓库;
  3. 最后将Cookie创建出来响应给浏览器,这个Cookie值是由 series 和 token 两项合并然后进行Base64编码的值。

processAutlLoginCookie 具体实现

在这里插入图片描述

  1. 从 CookieTokens 数组中分别提取到 series 和 token,然后根据 series 去内存中查询出一个 PersistentRememberMeToken 对象。如果查询出来的对象为null,表示内存中并没有 series 对应的值,本次登录失败。如果查询出来的 token 和从 CookieTokens 中解析出来的 token 不相同,说明自动登录令牌已经泄露(恶意用户利用令牌登录后,内存中的token就变了),此时移除当前用户的所有自动登录记录并抛出异常。
  2. 根据数据库中查询出来的结果判断令牌是否过期,如果过期就抛出异常;
  3. 生成一个新的 PersistentRememberMeToken 对象,用户名和series不变,token重新生成,data 也使用当前时间。newToken 生成后,根据 series 去修改内存中的 token 和data(即每次自动登录后都会产生新的 token 和date)
  4. 调用 addCookie 方法添加 Cookie,在 addCookie 方法中,实质是去调用了父类 AbstractRememberMeServices 中的 setCookie 方法,但是要注意第一个数组参数中只有两项:series 和 token(即返回到前端的令牌是通过对 series 和 token 进行 base64 编码得到的)
  5. 最后将根据用户名查询用户对象并返回。

内存令牌登录测试

Security 配置类

@EnableWebSecurity
public class SecurityConfig {@Beanpublic InMemoryUserDetailsManager inMemoryUserDetailsManager(){return new InMemoryUserDetailsManager(User.withUsername("admin").password("{noop}123").roles("admin").build());}@Beanpublic AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(inMemoryUserDetailsManager()).and().build();}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe()// .rememberMeParameter("rememberMe").rememberMeCookieName("rememberMe").rememberMeServices(rememberMeServices()).and().csrf().disable().build();}@Beanpublic RememberMeServices rememberMeServices(){return new PersistentTokenBasedRememberMeServices(UUID.randomUUID().toString(),inMemoryUserDetailsManager(),new InMemoryTokenRepositoryImpl());}}

测试结果:开一个浏览器进行登录认证,认证成功后携带了 remember-me Cookie,重开一个浏览器携带这个Cookie去访问服务器需认证的资源,然后再关闭刚开始的浏览器,再打开它进行访问一样的资源,会403重定向到登录页面且服务器端会报异常(此时内存中的令牌已经没了),报的是 CookieTheftException(Cookie被盗窃)。

注意:刷新没有报异常且可以访问资源,是因为存在SessionID,它在服务器端也可以进行认证,所以需要关闭浏览器让 SessionID 匹配不上(服务器端对应的 Session 自然会因超时被销毁)然后进行测试。

四、令牌数据库的持久化

在 PersistentTokenBasedRememberMeServices 中存储令牌的 PersistentTokenRepository 仓库默认是基于内存实现的(即 InMemoryTokenRepositoryImpl),使用这种方式会出现一个问题,当服务器端重启应用程序时,那浏览器用户登录则需要重新登录,因为内存中肯定是没有对应的令牌了。

Spring Security 除了提供了 InMemoryTokenRepositoryImpl 实现外,还提供了JdbcTokenRepositoryImpl 对 PersistentTokenRepository 的实现。即将令牌会保存置数据库中,无需担心上面阐述的问题。

在 JdbcTokenRepositoryImpl 中,已经为我们写好了需要的 SQL,令牌的增删改查实现已为我们提供了与数据库进行交互的支持。

在这里插入图片描述

测试一波,下面是配置代码

@EnableWebSecurity
public class SecurityConfig {@Beanpublic InMemoryUserDetailsManager inMemoryUserDetailsManager(){return new InMemoryUserDetailsManager(User.withUsername("admin").password("{noop}123").roles("admin").build());}@Beanpublic AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(inMemoryUserDetailsManager()).and().build();}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().rememberMe()// .rememberMeParameter("rememberMe").rememberMeCookieName("remember-me")// .rememberMeServices(rememberMeServices()).tokenRepository(persistentTokenRepository()).and().csrf().disable().build();}@Resourceprivate DataSource dataSource;@Beanpublic PersistentTokenRepository persistentTokenRepository(){JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);// 设置为true要保障数据库该表不存在,不然会报异常哦// 所以第二次打开服务器应用程序的时候得把它设为falsetokenRepository.setCreateTableOnStartup(true);return tokenRepository;}}

当开启服务器应用的时候,自动为我们创建了表,如下图所示

在这里插入图片描述
认证后数据库表中会新增数据

在这里插入图片描述

五、总结

  • RememberMe 实质呢就是将令牌以 Cookie 的形式响应给浏览器,然后浏览器进行了本地存储,等下次再次访问资源的时候,即会拿该令牌连请求一起到服务器端,令牌会使得后台进行自动登录(即用户认证)。
  • 当我们自定义 SecurityFilterChain 安全过滤器链的时候,如果配置了 rememberMe() 的话,那RememberMeAuthenticationFilter 即会是过滤器链中的一员。
  • 阐述一下流程(实现该功能最主要的类是 RememberMeServices,首次认证的响应和自动登录的认证它都是主角):在没有自定义过滤器的情况下,一般都是使用 UsernamePasswordAuthenticationFilter 进行用户认证的,当点击了 checkbox 按钮连同用户信息一起向服务器端请求时,首先是 UsernamePasswordAuthenticationFilter 去完成认证,如果认证成功会调用 RememberMeServices 中的 onLoginSuccess 方法(这个看具体实现),此时响应中就已存在其令牌信息了(即 Cookie)。当关闭浏览器或Session过期,访问服务器端需要认证的资源时,请求报文中会带着这个Cookie一起到服务器端,会进入到 RememberMeAuthenticationFilter 过滤器中,它会调用 RememberMeServices 中的 autoLogin 方法进行自动登录,在 autoLogin 自动登录过程中,会调用 processAutoLoginCookie 方法进行令牌认证(该方法取决于 RememberMeServices 的实现类),autoLogin 方法执行完成认证成功后,返回了用户数据源信息,接下来就是一些封装数据信息到 SecurityContextHolder 中等等一些操作。
  • RememberMeServices 是核心类,Spring Security 中提供了两种实现类,一种是 TokenBasedRememberMeServices,这种方式就是将用户名、超时时间、密码等信息进行Base64编码即一些操作组成的令牌给服务器,验证令牌就是对浏览器中发来的进行反编码看看是否一致,这种方式安全度很低;另一种实现是 PersistentTokenBasedRememberMeServices,它内部依赖于 PersistentTokenRepository 仓库,提供了基于内存和基于 Jdbc 的实现,它比前一种安全,原因是它每次自动认证后会更新令牌(即Cookie),如果在自动认证过程中发现令牌不一致会及时剔除(即从PersistentTokenRepository仓库中删除),报Cookie被盗窃异常。
  • PersistentTokenBasedRememberMeServices 中同 TokenBasedRememberMeServices 一样的是它也使用的是 Base64 编码后进行令牌设置,自动登录认证令牌的时候也需要解码;不同的是它是由两数据组成的(序列号(固定的),token(会变化的)),而 TokenBasedRememberMeServices 是三。也不能说序列号是固定的吧,就是说它是在 onLoginSuccess 方法中生成的,然后存在浏览器上,在 processAutoLoginCookie 方法中不会改变这个序列号,只会变token,可以说浏览器Cookie解码后的序列号是固定死的(没有重新登录验证的情况下)。
  • 使用起来很简单(一般使用的是PersistentTokenBasedRememberMeServices,且使用的仓库实现是 JdbcTokenRepositoryImpl),在配置 rememberMeConfigurer 时,配置一个 tokenRepository(PersistentTokenRepository) 即可,它会自动为我们配置 rememberMeServices 的,因为Spring Security就俩那实现,如果你配置了 PersistentTokenRepository,实质就默认你是使用了 PersistentTokenBasedRememberMeServices

相关文章:

【深入浅出 Spring Security(七)】RememberMe的实现原理详讲

RememberMe 的实现原理 一、RememberMe 的基本使用二、RememberMeAuthenticationFilter 源码分析RememberMeServicesTokenBasedRememberMeServicesTokenBasedRememberMeServices 中对 processAutoLoginCookie 方法的实现总结原理图式 三、提高安全性PersistentTokenBasedRememb…...

Cesium 实战 - 使用 gltf-vscode 查看、预览以及编辑 glTF 和 GLB 模型

Cesium 实战 - 使用 gltf-vscode 查看、预览以及编辑 glTF 和 GLB 模型 VScode(Visual Studio Code) 安装模型必要插件VScode 预览自定义关节(articulations)动作VScode 导入 GLB 格式模型VScode 导出 GLB 格式模型 模型渲染作为 …...

Python自动化测试框架:Pytest和Unittest的区别

pytest和unittest是Python中常用的两种测试框架,它们都可以用来编写和执行测试用例,但两者在很多方面都有所不同。本文将从不同的角度来论述这些区别,以帮助大家更好地理解pytest和unittest。 1. 原理 pytest是基于Python的assert语句和Pytho…...

考研算法29天:希尔排序 【希尔排序】

算法介绍 希尔排序 等差数列 普通版插入排序 循环数组 第一次每n/2为间隔分为4组,然后组内排序。 第二次每n/4为间隔分为2组。然后组内排序 第三次n/8为间隔分为一组。然后组内排序。 组内排序用插入排序来排序。 注:也可以第一次为n/3为间隔&am…...

RN 学习小记之使用 Expo 创建项目

本文Hexo博客链接🔗 https://ysx.cosine.ren/react-native-note-1 xLog链接🔗 https://x.cosine.ren/react-native-note-1 RSS订阅 📢 https://x.cosine.ren/feed/xml 由于业务需要,开始学习RN以备后面的需求,而虽然之…...

python爬虫从入门到精通

目录 一、正确认识Python爬虫 二、了解爬虫的本质 1. 熟悉Python编程 2. 了解HTML 3. 了解网络爬虫的基本原理 4. 学习使用Python爬虫库 三、了解非结构化数据的存储 1. 本地文件 2. 数据库 四、掌握各种技巧,应对特殊网站的反爬措施 1. User-Agent 2. C…...

从0到1精通自动化,接口自动化测试——数据驱动DDT实战

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 DDT简介 名称&am…...

【微服务】springboot整合swagger多种模式使用详解

目录 一、前言 1.1 编写API文档 1.2 使用一些在线调试工具 1.3 postman 1.4 swagger 二、swagger简介</...

AI 绘画(1):生成一个图片的标准流程

文章目录 文章回顾感谢人员生成一个图片的标准流程前期准备&#xff0c;以文生图为例去C站下载你需要的绘画模型导入参数导入生成结果&#xff1f;可能是BUG事后处理 图生图如何高度贴合原图火柴人转角色 涂鸦局部重绘 Ai绘画公约 文章回顾 AI 绘画&#xff08;0&#xff09;&…...

CPU、内存、缓存的关系

术语解释 &#xff08;1&#xff09;CPU&#xff08;Central Processing Unit&#xff09; 中央处理器 &#xff08;2&#xff09;内存 内存用于暂时存放CPU中的运算数据&#xff0c;以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁&#xff0c;内存的运行决定…...

AI黑客松近期比赛清单;36氪AI淘宝店盈利复盘;GitHub Copilot官方最佳实践;AI在HR领域的应用探索 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; ⋙ 点击查看 AI Hackathon (黑客马拉松) 汇总清单 &#x1f916; 〖飞桨〗2023大模型应用创新挑战赛 百度飞桨联合上海市青年五十人创新创业研究院等…...

想要让视频素材格式快速调整转换的方法分享

有时候有些视频播放软件不支持播放某些格式的视频文件&#xff1f;那要怎么解决呢&#xff1f;换一个播放软件&#xff1f;不妨试试批量转换视频格式&#xff0c;简单的几步操作就能快速解决烦恼&#xff0c;跟着小编一起来看看具体的操作环节吧。 首先先进入“固乔科技”的官网…...

面向对象分析与设计 UML2.0 学习笔记

一、认识UML UML-Unified Modeling Language 统一建模语言&#xff0c;又称标准建模语言。是用来对软件密集系统进行可视化建模的一种语言。UML的定义包括UML语义和UML表示法两个元素。 UML是在开发阶段&#xff0c;说明、可视化、构建和书写一个面向对象软件密集系统的制品的…...

[数据库系统] 五、数据增删改

第一关&#xff1a;数据插入 用insert给数据库添加数据 相关知识 有关系student(sno,sname,ssex,sage,sdept)&#xff0c;属性对应含义&#xff1a;学号&#xff0c;姓名&#xff0c;性别&#xff0c;所在系。现有的部分元组如下所示 insert 向数据库表插入数据的基本格式有…...

docker私有注册表创建和使用

说明 本文给出了一个具体的使用docker registry和nginx配置docker私有注册表的方案。 创建和配置 docker compose 使用docker compose的方式运行registry容器&#xff0c;配置如下&#xff1a; # cat docker-compose.yml services:registry:image: registry:2ports:- &quo…...

用OpenCV进行OCR字符分割

1. 引言 本文重点介绍如何利用传统的图像处理的方法来进行OCR字符切分&#xff0c;进而可以用分割后的单个字符做相应的后续任务&#xff0c;虽然现在计算机视觉依然是卷积神经网络的天下&#xff0c;但是对于一些相对简单的落地场景传统方案还是很有效的。 闲话少说&#xff…...

MyCat Docker 搭建与测试

mycat 是mysql分库分表的中间件&#xff0c;由java编写&#xff0c;本次进行mysql、mycat 的docker搭建&#xff0c;理解mycat的原理与特性。 一、mysql docker 搭建 这里启动两个实例&#xff1a; docker run -itd --name mysql1 -p 3307:3306 -e MYSQL_ROOT_PASSWORD123 m…...

车载通讯USB开发,增强车内娱乐体验

车载通讯开发中使用的 USB 协议常见于车内娱乐系统、车载设备和汽车诊断工具等应用。USB&#xff08;Universal Serial Bus&#xff0c;通用串行总线&#xff09;是一种常见的数字通信接口标准&#xff0c;用于连接计算机、外部设备及其他电子设备之间的数据传输和通信。 USB …...

js的一些小技巧

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 作用域 全局作用域局部作用域&#xff08;函数里&#xff09;也称函数作用域块级作用域 {…...

Springboot Mybatis 自定义顺序排序查询,指定某个字段

前言 与本文无关 "我进去了" ....... 正文 今天要讲些什么&#xff1f; 其实很简单&#xff0c;就是查询数据的时候&#xff0c;想根据自己指定的字段的自定义顺序&#xff0c;做排序查询数据。 本篇文章会讲到的几个点 &#xff1a; 1. 单纯sql 怎么实现 排序2. …...

期刊会议审稿意见

AAAI 修改意见 违背了研究方向的假设&#xff1b;虽然实验结果不错&#xff0c;但是没有明确地指向任何成功的方向&#xff0c;作者也没有充分地处理失败的案例——The results, though good are not clearly pointing to any direction of success, and the authors have no…...

Java类加载机制:从字节码到对象的奇妙之旅

目录 什么是类加载机制&#xff1f; 类加载顺序 类加载顺序图 双亲委派模型 双亲委派模型示意图 如何打破双亲委派模型&#xff1f; 要想学好java&#xff0c;首先得知道它是什么&#xff0c;怎么运行的&#xff0c;怎么加载的&#xff0c;运行的是个什么东西&#xff0c…...

代码随想录第一天|二分法、双指针

代码随想录第一天 Leetcode 704 二分查找Leetcode 35 搜索插入位置Leetcode 34 在排序数组中查找元素的第一个和最后一个位置Leetcode 69 x 的平方根Leetcode 367 有效的完全平方数Leetcode 27 移除元素Leetcode 26 删除有序数组中的重复项Leetcode 283 移动零Leetcode 844 比较…...

Flink中KeyedStateStore实现--怎么做到一个Key对应一个State

背景 在Flink中有两种基本的状态&#xff1a;Keyed State和Operator State&#xff0c;Operator State很好理解&#xff0c;一个特定的Operator算子共享同一个state&#xff0c;这是实现层面很好做到的。 但是 Keyed State 是怎么实现的&#xff1f;一般来说&#xff0c;正常的…...

flex: 0 0 100%;

flex: 0 0 100%; flex: 0 0 100%; 是一个用于设置flex项的flex-grow、flex-shrink和flex-basis属性的缩写flex-grow&#xff1a;指定了flex项在剩余空间中的放大比例&#xff0c;默认为0&#xff0c;表示不放大。在这个例子中&#xff0c;设置为0表示不允许flex项在水平方向上…...

IMX6ULL系统移植篇-镜像烧写方法

一. 烧录镜像简介 本文我们就来学习&#xff1a;windows 系统下烧录镜像的方法。 如何使用 NXP 官方提供的 MfgTool 工具通过 USB OTG 口来 烧写系统。 二. windows下烧录镜像 1. 烧录镜像前准备工作 &#xff08;1&#xff09;从开发板上拔下 SD卡。 &#xff08;2…...

【Android】实现雷达扫描效果,使用自定义View来绘制雷达扫描动画

要在Android上实现雷达扫描效果&#xff0c;你可以使用自定义View来绘制雷达扫描动画。以下是一个简单的示例代码&#xff1a; 创建一个名为RadarView的自定义View类&#xff0c;继承自View&#xff1a; import android.content.Context; import android.graphics.Canvas; im…...

小程序 - 文件预览

小程序文件预览 /** 预览 - txt文本 */viewTxt(path) {let fs wx.getFileSystemManager();let _this this;fs.readFile({filePath: path,encoding: "utf8",position: 0,success(res) {_this.setData({setNoRefresh: true});wx.navigateTo({url: /pages/view-txt/v…...

将String类型的证书转换为X509Certificate类型对象,读取证书链文件内容,完成证书链校验

证书内容如下所示: 证书内容如下 -----BEGIN CERTIFICATE----- MIIFZDCCA0ygAwIBAgIIYsLLTehAXpYwDQYJKoZIhvcNAQELBQAwUDELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEbMBkGA1UEAwwSSHVhd2VpIENCRyBSb290IENBMB4XDTE3MDgyMTEwNTYyN1oXDTQyMDgxNTEw…...

v-model实现原理(一根绳上的蚂蚱)

目录 1、什么是v-model2、v-model实现原理3、实现示例3.1 实现text和textarea3.2 实现checkbox和radio3.3 实现select 1、什么是v-model v-model 本质上是一颗语法糖&#xff0c;可以用 v-model 指令在表单 <input>、<textarea> 及 <select>元素上创建双向数…...