【深入浅出 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
方法中主要实现可分为三步:
- 请求到达过滤器后,首先判断 SecurityContextHolder 中是否有值,没值的话表示用户尚未登录,此时调用 autoLogin 方法进行自动登录。
- 当自动登录成功后返回的 rememberMeAuth 不为 null 时,表示自动登录成功,此时调用 authenticate方法对 key 进行校验,并且将登录成功的用户信息保存到 SecurityContextHolder 对象中,然后调用登录成功的回调,并发布登录成功时间。需要注意的是:登录成功的回调并不包含 RememberMeServices 中的 loginSuccess 方法。
- 失败的话会进行一些登录失败的回调,打印失败的日志信息。
可以看见其实现还得看 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解密,再分割的字符串数组):
- 首先判断 CookieTokens 长度是否为 3,不为 3 就抛出异常。
- 从 CookieTokens 中下标为 1 的值,也就是过期时间,判断令牌是否过期,如果已经过期,则抛出异常;
- 根据用户名(CookieTokens 下标为 0 的值)查询当前用户对象;
- 调用 makeTokenSignature 方法生成一个签名,签名的生成过程如下:首先将
用户名、令牌过期时间、用户名密码以及key
组成一个字符串,中间用“:
”隔开,然后通过MD5
消息摘要算法对该字符串进行加密,并将加密结果转换为一个字符串返回。 - 判断第 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 具体实现
- 先从认证数据源中获取用户名,然后将一序列号和用户名封装成一个 PersistentRememberMeToken 数据源;
- 将这个数据源放入到 tokenRepository 仓库中,它是一个基于内存的仓库;
- 最后将Cookie创建出来响应给浏览器,这个Cookie值是由 series 和 token 两项合并然后进行Base64编码的值。
processAutlLoginCookie 具体实现
- 从 CookieTokens 数组中分别提取到 series 和 token,然后根据 series 去内存中查询出一个 PersistentRememberMeToken 对象。如果查询出来的对象为null,表示内存中并没有 series 对应的值,本次登录失败。如果查询出来的 token 和从 CookieTokens 中解析出来的 token 不相同,说明自动登录令牌已经泄露(恶意用户利用令牌登录后,内存中的token就变了),此时移除当前用户的所有自动登录记录并抛出异常。
- 根据数据库中查询出来的结果判断令牌是否过期,如果过期就抛出异常;
- 生成一个新的 PersistentRememberMeToken 对象,用户名和series不变,token重新生成,data 也使用当前时间。newToken 生成后,根据 series 去修改内存中的 token 和data(即每次自动登录后都会产生新的 token 和date)
- 调用 addCookie 方法添加 Cookie,在 addCookie 方法中,实质是去调用了父类
AbstractRememberMeServices
中的 setCookie 方法,但是要注意第一个数组参数中只有两项:series 和 token(即返回到前端的令牌是通过对 series 和 token 进行 base64 编码得到的) - 最后将根据用户名查询用户对象并返回。
内存令牌登录测试
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):生成一个图片的标准流程
文章目录 文章回顾感谢人员生成一个图片的标准流程前期准备,以文生图为例去C站下载你需要的绘画模型导入参数导入生成结果?可能是BUG事后处理 图生图如何高度贴合原图火柴人转角色 涂鸦局部重绘 Ai绘画公约 文章回顾 AI 绘画(0)&…...

CPU、内存、缓存的关系
术语解释 (1)CPU(Central Processing Unit) 中央处理器 (2)内存 内存用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。它是外存与CPU进行沟通的桥梁,内存的运行决定…...
AI黑客松近期比赛清单;36氪AI淘宝店盈利复盘;GitHub Copilot官方最佳实践;AI在HR领域的应用探索 | ShowMeAI日报
👀日报&周刊合集 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! ⋙ 点击查看 AI Hackathon (黑客马拉松) 汇总清单 🤖 〖飞桨〗2023大模型应用创新挑战赛 百度飞桨联合上海市青年五十人创新创业研究院等…...

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

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

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

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

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

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

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

js的一些小技巧
大厂面试题分享 面试题库 前后端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库 web前端面试题库 VS java后端面试题库大全 作用域 全局作用域局部作用域(函数里)也称函数作用域块级作用域 {…...

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

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

Java类加载机制:从字节码到对象的奇妙之旅
目录 什么是类加载机制? 类加载顺序 类加载顺序图 双亲委派模型 双亲委派模型示意图 如何打破双亲委派模型? 要想学好java,首先得知道它是什么,怎么运行的,怎么加载的,运行的是个什么东西,…...

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

Flink中KeyedStateStore实现--怎么做到一个Key对应一个State
背景 在Flink中有两种基本的状态:Keyed State和Operator State,Operator State很好理解,一个特定的Operator算子共享同一个state,这是实现层面很好做到的。 但是 Keyed State 是怎么实现的?一般来说,正常的…...

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

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

【Android】实现雷达扫描效果,使用自定义View来绘制雷达扫描动画
要在Android上实现雷达扫描效果,你可以使用自定义View来绘制雷达扫描动画。以下是一个简单的示例代码: 创建一个名为RadarView的自定义View类,继承自View: 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 本质上是一颗语法糖,可以用 v-model 指令在表单 <input>、<textarea> 及 <select>元素上创建双向数…...