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

Shiro框架:Shiro登录认证流程源码解析

目录

1.用户登录认证流程

1.1 生成认证Token

1.2 用户登录认证

1.2.1  SecurityManager login流程解析

1.2.1.1 authenticate方法进行登录认证

1.2.1.1.1 单Realm认证

1.2.1.2 认证通过后创建登录用户对象

1.2.1.2.1 复制SubjectContext

1.2.1.2.2 对subjectContext设置securityManager

1.2.1.2.3 对subjectContext设置session

1.2.1.2.4 对subjectContext设置Principals

1.2.1.2.5 根据subjectContext创建Subject

1.2.1.2.6 保存Subject 


Shiro作为一款比较流行的登录认证、访问控制安全框架,被广泛应用在程序员社区;Shiro登录验证、访问控制、Session管理等流程内部都是委托给SecurityManager安全管理器来完成的,SecurityManager安全管理器上篇文章已经进行了详细解析,详见:Shiro框架:Shiro SecurityManager安全管理器解析-CSDN博客,在此基础上,本篇文章继续对Shiro关联链路处理流程之一---登录认证流程 进行解析;

想要深入了解Shiro框架整体原理,可移步:

Shiro框架:ShiroFilterFactoryBean过滤器源码解析-CSDN博客、

Shiro框架:Shiro内置过滤器源码解析-CSDN博客

1.用户登录认证流程

在Shiro框架:Shiro内置过滤器源码解析-CSDN博客内置过滤器分析中,我们知道用户执行登录的认证操作是在过滤器FormAuthenticationFilter中执行的,如下:

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {if (isLoginRequest(request, response)) {if (isLoginSubmission(request, response)) {if (log.isTraceEnabled()) {log.trace("Login submission detected.  Attempting to execute login.");}return executeLogin(request, response);} else {if (log.isTraceEnabled()) {log.trace("Login page view.");}//allow them to see the login page ;)return true;}} else {if (log.isTraceEnabled()) {log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +"Authentication url [" + getLoginUrl() + "]");}saveRequestAndRedirectToLogin(request, response);return false;}}

登录认证操作是在executeLogin方法中完成的:

    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {AuthenticationToken token = createToken(request, response);if (token == null) {String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +"must be created in order to execute a login attempt.";throw new IllegalStateException(msg);}try {Subject subject = getSubject(request, response);subject.login(token);return onLoginSuccess(token, subject, request, response);} catch (AuthenticationException e) {return onLoginFailure(token, e, request, response);}}

上述源码的执行过程表示为时序图会更直观,时序图如下:

该认证过程主要包含以下几个部分:

  1. 根据用户名密码等生成认证Token
  2. 获取当前登录用户Subject
  3. 调用Subject的login方法进行登录认证
  4. 其它的登录成功、或登录失败的拦截方法

下面主要对生成认证Token和用户登录认证实现进行详细说明;

1.1 生成认证Token

createToken的实现在FormAuthenticationFilter内,如下:

这里用户名和密码是通过request请求对象获取的:

    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {String username = getUsername(request);String password = getPassword(request);return createToken(username, password, request, response);}

在父类AuthenticatingFilter中进一步实现如下,这里构造了UsernamePasswordToken类,表示采用用户名密码的认证方式;

    protected AuthenticationToken createToken(String username, String password,ServletRequest request, ServletResponse response) {boolean rememberMe = isRememberMe(request);String host = getHost(request);return createToken(username, password, rememberMe, host);}protected AuthenticationToken createToken(String username, String password,boolean rememberMe, String host) {return new UsernamePasswordToken(username, password, rememberMe, host);}

图示UsernamePasswordToken的继承结构:

1.2 用户登录认证

在Subject的login的具体实现如下:

  • 实际的login是委托给SecurityManager完成的
  • 登录成功后设置当前登录对象为认证成功

下面主要对SecurityManager的login方法进行具体分析; 

1.2.1  SecurityManager login流程解析

login方法具体实现如下:

    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;try {info = authenticate(token);} catch (AuthenticationException ae) {try {onFailedLogin(token, ae, subject);} catch (Exception e) {if (log.isInfoEnabled()) {log.info("onFailedLogin method threw an " +"exception.  Logging and propagating original AuthenticationException.", e);}}throw ae; //propagate}Subject loggedIn = createSubject(token, info, subject);onSuccessfulLogin(token, info, loggedIn);return loggedIn;}

上述登录流程主要包含2部分内容: 

  1. 通过authenticate方法进行登录认证
  2. 认证通过后创建登录用户对象

下面分别进行展开分析;

1.2.1.1 authenticate方法进行登录认证

具体的authenticate实现内部又委托给了认证器Authenticator来实现,具体的认证器为ModularRealmAuthenticator,其继承结构如下:

authenticate方法具体实现是在父类AbstractAuthenticator中,如下:

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {if (token == null) {throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");}log.trace("Authentication attempt received for token [{}]", token);AuthenticationInfo info;try {info = doAuthenticate(token);if (info == null) {String msg = "No account information found for authentication token [" + token + "] by this " +"Authenticator instance.  Please check that it is configured correctly.";throw new AuthenticationException(msg);}} catch (Throwable t) {AuthenticationException ae = null;if (t instanceof AuthenticationException) {ae = (AuthenticationException) t;}if (ae == null) {//Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more//severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +"error? (Typical or expected login exceptions should extend from AuthenticationException).";ae = new AuthenticationException(msg, t);if (log.isWarnEnabled())log.warn(msg, t);}try {notifyFailure(token, ae);} catch (Throwable t2) {if (log.isWarnEnabled()) {String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +"Please check your AuthenticationListener implementation(s).  Logging sending exception " +"and propagating original AuthenticationException instead...";log.warn(msg, t2);}}throw ae;}log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);notifySuccess(token, info);return info;}

通过方法doAuthenticate对认证Token进行认证,通过notifySuccess、notifyFailure监听认证通过或失败的事件并调用注册的监听器;

方法doAuthenticate的具体实现是在子类完成的,如下:

    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {assertRealmsConfigured();Collection<Realm> realms = getRealms();if (realms.size() == 1) {return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);} else {return doMultiRealmAuthentication(realms, authenticationToken);}}

这里首先获取已注册的安全组件Realm,其注册过程是在RealmSecurityManager初始化的过程中完成的(对SecurityManager安全管理器的处理过程感兴趣可以参见:Shiro框架:Shiro SecurityManager安全管理器解析-CSDN博客)

然后根据Realm的个数分别执行单Realm认证或多Realm认证,这里已单Realm认证为例进行说明,多Realm认证类似;

1.2.1.1.1 单Realm认证

通过doSingleRealmAuthentication方法完成单Realm认证,实现如下:

    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {if (!realm.supports(token)) {String msg = "Realm [" + realm + "] does not support authentication token [" +token + "].  Please ensure that the appropriate Realm implementation is " +"configured correctly or that the realm accepts AuthenticationTokens of this type.";throw new UnsupportedTokenException(msg);}AuthenticationInfo info = realm.getAuthenticationInfo(token);if (info == null) {String msg = "Realm [" + realm + "] was unable to find account data for the " +"submitted AuthenticationToken [" + token + "].";throw new UnknownAccountException(msg);}return info;}

这里Reaml接口包含一整套的继承层次实现,如下,这里不过多展开解析;

getAuthenticationInfo是在AuthenticatingRealm中完成的,如下:

    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info = getCachedAuthenticationInfo(token);if (info == null) {//otherwise not cached, perform the lookup:info = doGetAuthenticationInfo(token);log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);if (token != null && info != null) {cacheAuthenticationInfoIfPossible(token, info);}} else {log.debug("Using cached authentication info [{}] to perform credentials matching.", info);}if (info != null) {assertCredentialsMatch(token, info);} else {log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);}return info;}

 这里首先获取AuthenticationInfo对象,要么从缓存中获取,要么通过引入的抽象方法doGetAuthenticationInfo获取(交由应用层子类具体实现);

获取到AuthenticationInfo后,将其余用户录入的登录Token进行比对,这部分具体是方法assertCredentialsMatch完成的,具体如下:

    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {CredentialsMatcher cm = getCredentialsMatcher();if (cm != null) {if (!cm.doCredentialsMatch(token, info)) {//not successful - throw an exception to indicate this:String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";throw new IncorrectCredentialsException(msg);}} else {throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +"credentials during authentication.  If you do not wish for credentials to be examined, you " +"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");}}

这里获取了默认的匹配器SimpleCredentialsMatcher,并调用doCredentialsMatch方法进行匹配,匹配实现如下:

    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {Object tokenCredentials = getCredentials(token);Object accountCredentials = getCredentials(info);return equals(tokenCredentials, accountCredentials);}

首先获取Credentials,也即用户密码,然后调用equals方法进行匹配,如下通过字节流的方式进行比对(主要是为了安全考虑,系统处理时采用非明文形式)。

    protected boolean equals(Object tokenCredentials, Object accountCredentials) {if (log.isDebugEnabled()) {log.debug("Performing credentials equality check for tokenCredentials of type [" +tokenCredentials.getClass().getName() + " and accountCredentials of type [" +accountCredentials.getClass().getName() + "]");}if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {if (log.isDebugEnabled()) {log.debug("Both credentials arguments can be easily converted to byte arrays.  Performing " +"array equals comparison");}byte[] tokenBytes = toBytes(tokenCredentials);byte[] accountBytes = toBytes(accountCredentials);return MessageDigest.isEqual(tokenBytes, accountBytes);} else {return accountCredentials.equals(tokenCredentials);}}

 至此,用户账号密码匹配完成,匹配完成后,会重新创建用户登录对象,并更新用户状态等,下面具体分析;

1.2.1.2 认证通过后创建登录用户对象

认证通过后创建Subject的实现如下:

    protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {SubjectContext context = createSubjectContext();context.setAuthenticated(true);context.setAuthenticationToken(token);context.setAuthenticationInfo(info);if (existing != null) {context.setSubject(existing);}return createSubject(context);}@Overrideprotected SubjectContext createSubjectContext() {return new DefaultWebSubjectContext();}

这里创建了DefaultWebSubjectContext,用户Subject创建,其中设置了认证状态、认证Token、已认证信息,然后调用createSubject方法继续进行构造,如下:

    public Subject createSubject(SubjectContext subjectContext) {//create a copy so we don't modify the argument's backing map:SubjectContext context = copy(subjectContext);//ensure that the context has a SecurityManager instance, and if not, add one:context = ensureSecurityManager(context);//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before//sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the//process is often environment specific - better to shield the SF from these details:context = resolveSession(context);//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first//if possible before handing off to the SubjectFactory:context = resolvePrincipals(context);Subject subject = doCreateSubject(context);//save this subject for future reference if necessary://(this is needed here in case rememberMe principals were resolved and they need to be stored in the//session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).//Added in 1.2:save(subject);return subject;}

如上创建过程包含了以下几部分,分别进行分析;

1.2.1.2.1 复制SubjectContext

对subjectContext调用复制构造方法进行复制,subjectContext底层通过backingMap保存上下文信息:

    public MapContext(Map<String, Object> map) {this();if (!CollectionUtils.isEmpty(map)) {this.backingMap.putAll(map);}}
1.2.1.2.2 对subjectContext设置securityManager

获取securityManager的顺序如下:

  1. 首先从subjectContext中获取
  2. 若无,则从当前线程上下文中获取
  3. 否则将当前securityManager设置到subjectContext中
    protected SubjectContext ensureSecurityManager(SubjectContext context) {if (context.resolveSecurityManager() != null) {log.trace("Context already contains a SecurityManager instance.  Returning.");return context;}log.trace("No SecurityManager found in context.  Adding self reference.");context.setSecurityManager(this);return context;}
    public SecurityManager resolveSecurityManager() {SecurityManager securityManager = getSecurityManager();if (securityManager == null) {if (log.isDebugEnabled()) {log.debug("No SecurityManager available in subject context map.  " +"Falling back to SecurityUtils.getSecurityManager() lookup.");}try {securityManager = SecurityUtils.getSecurityManager();} catch (UnavailableSecurityManagerException e) {if (log.isDebugEnabled()) {log.debug("No SecurityManager available via SecurityUtils.  Heuristics exhausted.", e);}}}return securityManager;}
1.2.1.2.3 对subjectContext设置session

解析session方法如下,其中session的解析顺序为:

  1. 首先从subjectContext获取session,判断是否已设置session(见源码2)
  2. 否则,通过subjectContext中保存的Subject对象获取关联的session(见源码2)
  3. 其次,通过sessionManager Session管理器获取(见源码3)
  4. Web服务中,具体的Session管理器为ServletContainerSessionManager,尝试从request请求中获取Servlet管理的Session;(见源码4)

源码1: 

    protected SubjectContext resolveSession(SubjectContext context) {if (context.resolveSession() != null) {log.debug("Context already contains a session.  Returning.");return context;}try {//Context couldn't resolve it directly, let's see if we can since we have direct access to //the session manager:Session session = resolveContextSession(context);if (session != null) {context.setSession(session);}} catch (InvalidSessionException e) {log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +"(session-less) Subject instance.", e);}return context;}

源码2: 

    public Session resolveSession() {Session session = getSession();if (session == null) {//try the Subject if it exists:Subject existingSubject = getSubject();if (existingSubject != null) {session = existingSubject.getSession(false);}}return session;}

源码3: 

    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {SessionKey key = getSessionKey(context);if (key != null) {return getSession(key);}return null;}public Session getSession(SessionKey key) throws SessionException {return this.sessionManager.getSession(key);}

源码4: ServletContainerSessionManager获取Session

    public Session getSession(SessionKey key) throws SessionException {if (!WebUtils.isHttp(key)) {String msg = "SessionKey must be an HTTP compatible implementation.";throw new IllegalArgumentException(msg);}HttpServletRequest request = WebUtils.getHttpRequest(key);Session session = null;HttpSession httpSession = request.getSession(false);if (httpSession != null) {session = createSession(httpSession, request.getRemoteHost());}return session;}
1.2.1.2.4 对subjectContext设置Principals

resolvePrincipals方法实现如下,这里获取Principals的顺序为:

  1. 先从SubjectContext中直接获取Principals(见源码2)
  2. 否则,通过SubjectContext中已认证的AuthenticationInfo获取(见源码2)
  3. 其次,通过SubjectContext中的Subject获取(见源码2)
  4. 再次,通过SubjectContext中的Session获取(见源码2)
  5. 最后,通过RememberMeManager中的Cookie中获取

源码1:

    protected SubjectContext resolvePrincipals(SubjectContext context) {PrincipalCollection principals = context.resolvePrincipals();if (isEmpty(principals)) {log.trace("No identity (PrincipalCollection) found in the context.  Looking for a remembered identity.");principals = getRememberedIdentity(context);if (!isEmpty(principals)) {log.debug("Found remembered PrincipalCollection.  Adding to the context to be used " +"for subject construction by the SubjectFactory.");context.setPrincipals(principals);} else {log.trace("No remembered identity found.  Returning original context.");}}return context;}

源码2:从SubjectContext获取Principals

    public PrincipalCollection resolvePrincipals() {PrincipalCollection principals = getPrincipals();if (isEmpty(principals)) {//check to see if they were just authenticated:AuthenticationInfo info = getAuthenticationInfo();if (info != null) {principals = info.getPrincipals();}}if (isEmpty(principals)) {Subject subject = getSubject();if (subject != null) {principals = subject.getPrincipals();}}if (isEmpty(principals)) {//try the session:Session session = resolveSession();if (session != null) {principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);}}return principals;}
1.2.1.2.5 根据subjectContext创建Subject

如下,通过createSubject创建了WebDelegatingSubject对象;

    public Subject createSubject(SubjectContext context) {if (!(context instanceof WebSubjectContext)) {return super.createSubject(context);}WebSubjectContext wsc = (WebSubjectContext) context;SecurityManager securityManager = wsc.resolveSecurityManager();Session session = wsc.resolveSession();boolean sessionEnabled = wsc.isSessionCreationEnabled();PrincipalCollection principals = wsc.resolvePrincipals();boolean authenticated = wsc.resolveAuthenticated();String host = wsc.resolveHost();ServletRequest request = wsc.resolveServletRequest();ServletResponse response = wsc.resolveServletResponse();return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,request, response, securityManager);}
1.2.1.2.6 保存Subject 

 Subject保存是通过DefaultSubjectDAO完成的,

如下,通过saveToSession方法保存Principals和认证状态到Session中;

    protected void saveToSession(Subject subject) {//performs merge logic, only updating the Subject's session if it does not match the current state:mergePrincipals(subject);mergeAuthenticationState(subject);}

这里在保存Principals的过程中,如果Principals不为空,且非Servlet Session的条件下,这里会调用subject.getSession()方法创建shiro管理的native Session对象:

subject.getSession()方法实现如下:

    public Session getSession() {return getSession(true);}public Session getSession(boolean create) {if (log.isTraceEnabled()) {log.trace("attempting to get session; create = " + create +"; session is null = " + (this.session == null) +"; session has id = " + (this.session != null && session.getId() != null));}if (this.session == null && create) {//added in 1.2:if (!isSessionCreationEnabled()) {String msg = "Session creation has been disabled for the current subject.  This exception indicates " +"that there is either a programming error (using a session when it should never be " +"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +"for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +"for more.";throw new DisabledSessionException(msg);}log.trace("Starting session for host {}", getHost());SessionContext sessionContext = createSessionContext();Session session = this.securityManager.start(sessionContext);this.session = decorate(session);}return this.session;}

如上,通过securityManager的start方法创建Session对象,start具体实现为 :

    public Session start(SessionContext context) throws AuthorizationException {return this.sessionManager.start(context);}

这里又委托给了Session管理器完成session创建,这里的Session管理器实现为AbstractNativeSessionManager,start方法实现如下:

    public Session start(SessionContext context) {Session session = createSession(context);applyGlobalSessionTimeout(session);onStart(session, context);notifyStart(session);//Don't expose the EIS-tier Session object to the client-tier:return createExposedSession(session, context);}

上述主要完成了以下几部分功能:

1. 通过createSession方法创建SimpleSession对象,其中包括了Session创建、Session保存到            sessionDAO中(比如保存到内存中的子类MemorySessionDAO,或者保存在数据库中的子类        EnterpriseCacheSessionDAO等),SessionId生成等操作;

    图示sessionDAO的整体继承结构:

    

2. Session全局超时时间配置(默认超时时间为30min)

3. onStart将SessionId存储到Cookie中,如下:    

    private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {if (currentId == null) {String msg = "sessionId cannot be null when persisting for subsequent requests.";throw new IllegalArgumentException(msg);}Cookie template = getSessionIdCookie();Cookie cookie = new SimpleCookie(template);String idString = currentId.toString();cookie.setValue(idString);cookie.saveTo(request, response);log.trace("Set session ID cookie for session with id {}", idString);}

至此,用户登录认证过程完成了生成认证Token、通过Realm从应用中获取用户认证信息、用户认证Token匹配,以及认证成功后创建用户Subject对象、保存Subject到Session、SessionId保存到Cookie等操作。

相关文章:

Shiro框架:Shiro登录认证流程源码解析

目录 1.用户登录认证流程 1.1 生成认证Token 1.2 用户登录认证 1.2.1 SecurityManager login流程解析 1.2.1.1 authenticate方法进行登录认证 1.2.1.1.1 单Realm认证 1.2.1.2 认证通过后创建登录用户对象 1.2.1.2.1 复制SubjectContext 1.2.1.2.2 对subjectContext设…...

WEB前端人机交互导论实验-实训2格式化文本、段落与列表

1.项目1 文本与段落标记的应用&#xff1a; A.题目要求: B.思路&#xff1a; &#xff08;1&#xff09;首先&#xff0c;HTML文档的基本结构是通过<html>...</html>标签包围的&#xff0c;包含了头部信息和页面主体内容。 &#xff08;2&#xff09;在头部信息…...

Python:list列表与tuple元组的区别

在Python中&#xff0c;List&#xff08;列表&#xff09; 和Tuple&#xff08;元组&#xff09; 都是用于存储一组有序元素的数据结构&#xff0c;但它们有一些关键的区别&#xff0c;包括可变性、性能、语法等方面。 1. List&#xff08;列表&#xff09; 用法&#xff1a;…...

如何基于 Gin 封装出属于自己 Web 框架?

思路 在基于 Gin 封装出属于自己的 Web 框架前&#xff0c;你需要先了解 Gin 的基本用法和设计理念。 然后&#xff0c;你可以通过以下步骤来封装自己的 Web 框架&#xff1a; 封装路由&#xff1a;Gin 的路由是通过 HTTP 方法和 URL 路径进行匹配的&#xff0c;你可以根据自己…...

VUE element-ui实现表格动态展示、动态删减列、动态排序、动态搜索条件配置、表单组件化。

1、实现效果 1.1、文件目录 1.2、说明 1、本组件支持列表的表头自定义配置&#xff0c;checkbox实现 2、本组件支持列表列排序&#xff0c;vuedraggable是拖拽插件&#xff0c;上图中字段管理里的拖拽效果 &#xff0c;需要的话请自行npm install 3、本组件支持查询条件动态…...

压测工具ab

Apache Benchmark(简称ab) 是Apache安装包中自带的压力测试工具 &#xff0c;简单易用, Apache的ab命令模拟多线程并发请求&#xff0c;测试服务器负载压力&#xff0c;也可以适用于其他服务&#xff1a;nginx、lighthttp、tomcat、IIS等其它Web服务器的压力 采用平台&#xf…...

P4学习(一) 环境搭建

系列文章目录 第一章 P4学习入门之虚拟机环境搭建 文章目录 系列文章目录前言一、P4是什么&#xff1f;二、搭建步骤1.下载虚拟机镜像2.虚拟机管理软件载入镜像2.1 找到你镜像的所在位置2.2 打开VMware Workstation2.3 载入镜像 3.检验环境是否配置成功 P4 的真机环境搭建 前言…...

openssl3.2 - 官方demo学习 - server-arg.c

文章目录 openssl3.2 - 官方demo学习 - server-arg.c概述笔记备注END openssl3.2 - 官方demo学习 - server-arg.c 概述 TLS服务器, 等客户端来连接; 如果客户端断开了, 通过释放bio来释放客户端socket, 然后继续通过bio读来aceept. 笔记 对于开源工程, 不可能有作者那么熟悉…...

Windows RPC运行时漏洞事后总结

2022年4月前后&#xff0c;Windows RPC运行时被曝出存在远程代码执行漏洞&#xff0c;当时曾引起很多人广泛关注。微软很快做出反应&#xff0c;发布补丁程序进行修补。这次事件中&#xff0c;Windows远程过程调用&#xff08;RPC&#xff09;运行时共出现三个关键漏洞&#xf…...

运算电路(1)——加法器

一、引言 微处理器是由一片或少数几片大规模集成电路组成的中央处理器。这些电路执行控制部件和算术逻辑部件的功能。微处理器能完成取指令、执行指令&#xff0c;以及与外界存储器和逻辑部件交换信息等操作&#xff0c;是微型计算机的运算控制部分。它可与存储器和外围电路芯片…...

ESP32-WIFI(Arduino)

ESP32-WIFI Wi-Fi是一种基于IEEE 802.11标准的无线局域网技术&#xff0c;是Wi-Fi联盟制造商的商标作为产品的品牌认证。它可以让电脑、手机、平板电脑等设备通过无线信号连接到互联网 。 在无线网络中&#xff0c;AP&#xff08;Access Point&#xff09;和 STA&#xff08;St…...

【网络虚拟化】网络设备常见冗余方式——堆叠、M-Lag、DRNI

网络设备常见冗余设计——堆叠、M-Lag、DRNI 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 网络设备常见冗余设计——堆叠、M-Lag、DRNI 网络设备常见冗余设计——堆叠、M-Lag、DRNI前言一、网络设备虚拟化二、堆叠技术1.技术原理2.…...

arm的侏罗纪二 cache学习

个人觉得inner shareable和outer shareable&#xff1b;POU和POC 是难点&#xff0c;慢慢学习吧。 inner shareable是cluster内 outer shareable是cluster之间 参考文献&#xff1a; 深入学习Cache系列 1: 带着几个疑问&#xff0c;从Cache的应用场景学起 https://www.eet-c…...

Protecting Intellectual Property of Deep NeuralNetworks with Watermarking

保护深度神经网络的知识产权与数字水印技术 ABSTRACT 深度学习是当今人工智能服务的关键组成部分&#xff0c;在视觉分析、语音识别、自然语言处理等多个任务方面表现出色&#xff0c;为人类提供了接近人类水平的能力。构建一个生产级别的深度学习模型是一项非常复杂的任务&a…...

c++学习笔记-STL案例-机房预约系统1-准备工作

前言 准备工作包括&#xff1a;需求分析、项目创建、主菜单实现、退出功能实现 目录 1 机房预约系统需求 1.1 简单介绍 1.2 身份介绍 1.3 机房介绍 1.4 申请介绍 1.5 系统具体要求 1.6 预约系统-主界面思维导图 2 创建项目 2.1 创建项目 2.2 添加文件 ​编辑 3 创建…...

AnnData:单细胞和空间组学分析的数据基石

AnnData&#xff1a;单细胞和空间组学分析的数据基石 今天我们来系统学习一下单细胞分析的标准数据类型——AnnData&#xff01; AnnData就是有注释的数据&#xff0c;全称是Annotated Data。 AnnData是为了矩阵类型数据设计的&#xff0c;也就是长得和表格一样的数据。比如…...

C语言中的 `string.h` 头文件包含的函数

C语言中的 string.h 头文件包含了许多与字符串或数字相关的函数。这些函数可以用于字符串的复制、连接、搜索、比较等操作。 常用字符串函数 函数名功能strlen()返回字符串的长度strcpy()将一个字符串复制到另一个字符串中strncpy()将最多 n 个字符从一个字符串复制到另一个字…...

kotlin的抽象类和抽象方法

在 Kotlin 中&#xff0c;抽象类和抽象方法是面向对象编程中的概念&#xff0c;用于实现抽象和多态性。抽象类无法实例化&#xff0c;这意味着我们无法创建抽象类的对象。与其他类不同&#xff0c;抽象类总是打开的&#xff0c;因此我们不需要使用open关键字。 抽象类&#xff…...

2022年面经记录(base杭州)

duandian科技&#xff08;笔试未通过&#xff09; 笔试题&#xff1a;leetCode热题第20题有效的括号 面后感&#xff1a;没怎么刷算法题&#xff0c;js 基础不扎实 laiweilai&#xff08;三面未通过&#xff09; 一面&#xff1a;笔试题 写一个函数&#xff0c;获取url中的指定…...

安装Docker图形管理界面portainer

安装Docker图形管理界面portainer 映射data文件夹根据自己环境更换 docker run -d --name portainer -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v /yourpath/docker/portainer:/data --restartalways portainer/portainer-ce:latest好好享受吧&#xff01…...

Linux学习记录——사십 高级IO(1)

文章目录 1、IO2、同、异步IO&#xff08;5种IO类型&#xff09;3、其它高级IO4、非阻塞IO 其它IO类型的实现在这篇之后的三篇 1、IO input&#xff0c;output。调用read或recv接口时&#xff0c;如果对方长时间不向我方接收缓冲区拷贝数据&#xff0c;我们的进程就只能阻塞&a…...

【代码随想录】2

数组篇 二分查找 int search(int* nums, int numsSize, int target) { int left0; int rightnumsSize-1; while(left<right) {int mlddle(leftright)/2;if(nums[mlddle]>target){rightmlddle-1;}else if(nums[mlddle]<target){leftmlddle1;}else{return mlddle;}} r…...

TCP性能分析

ref&#xff1a; TCP性能和发送接收窗口、Buffer的关系 | plantegg...

RibbonGroup 添加QRadioButton

RibbonGroup添加QRadioButton&#xff1a; QRadioButton * pRadio new QRadioButton(tr("Radio")); pRadio->setToolTip(tr("Radio")); groupClipboard->addWidget(pRadio); connect(pRadio, SIGNAL(clicked(…...

一篇文章掌握WebService服务、工作原理、核心组件、主流框架

目录 1、WebService定义 解决问题&#xff1a; 2、WebService的工作原理 2.1 实现一个完整的Web服务包括以下步骤 2.2 调用方式 3、Web Service的核心组件 3.1 XML 3.2 SOAP 3.3 WSDL 3.4 UDDI 4、主流框架 4.1 AXIS(已淘汰) 4.2 XFire 4.3 CXF 5、Soap协议详解…...

观成科技-加密C2框架EvilOSX流量分析

工具简介 EvilOSX是一款开源的&#xff0c;由python编写专门为macOS系统设计的C2工具&#xff0c;该工具可以利用自身释放的木马来实现一系列集成功能&#xff0c;如键盘记录、文件捕获、浏览器历史记录爬取、截屏等。EvilOSX主要使用HTTP协议进行通信&#xff0c;通信内容为特…...

PCL 计算异面直线的距离

目录 一、算法原理二、代码实现三、结果展示四、相关链接本文由CSDN点云侠原创,PCL 计算异面直线的距离,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 设置直线 A B AB A...

【数字人】9、DiffTalk | 使用扩散模型基于 audio-driven+对应人物视频 合成说话头(CVPR2023)

论文&#xff1a;DiffTalk: Crafting Diffusion Models for Generalized Audio-Driven Portraits Animation 代码&#xff1a;https://sstzal.github.io/DiffTalk/ 出处&#xff1a;CVPR2023 特点&#xff1a;需要音频对应人物的视频来合成新的说话头视频&#xff0c;嘴部抖…...

完成源示例

本主题演示如何创作和使用自己的完成源类&#xff0c;类似于 .NET 的 TaskCompletionSource。 completion_source 示例的源代码 下面的列表中的代码作为示例提供。 其目的是说明如何编写自己的版本。 例如&#xff0c;支持取消和错误传播不在此示例的范围内。 #include <w…...

业务和流程的关系

背景 概念不清&#xff0c;沟通就容易出现问题&#xff0c;最可怕会出现跑偏情况如何解决&#xff0c;数字化落地过程&#xff0c;程序是死的&#xff0c;最怕灵活&#xff0c;所以在沟通和编码&#xff0c;设计中&#xff0c;很重要的一点就是解决概念&#xff0c;澄清问题&a…...