Análisis del código fuente de Shiro ③: proceso de certificación

I. Introducción

Como no he usado Shiro antes, comencé a usarlo recientemente, así que leí algunos de sus procesos y código fuente, resumí parte del contenido y lo grabé. Esta serie no analizará completamente todo el código de Shiro, sino que solo analizará brevemente el principal (I) necesito (uso) flujo (a) proceso (de) . Dado que la mayor parte de esta serie es para la comprensión personal y las habilidades de aprendizaje personal no son buenas, es inevitable que haya una "confusión injusta y falsa". Si encuentra algo, gracias por sus correcciones, estoy muy agradecido.


Trabajos completos de análisis de código fuente de Shiro:

  1. Análisis del código fuente de Shiro①: Construcción simple del proyecto
  2. Análisis del código fuente de Shiro ②: AbstractShiroFilter
  3. Análisis del código fuente de Shiro ③: proceso de certificación
  4. Análisis del código fuente de Shiro ④: proceso de autenticación

Cuando http://localhost:8081/shiro/login?userName=张三&password=123456solicitemos, primero pasaremos por el filtro AbstractShiroFilter, después del filtro AbstractShiroFilter, llegaremos a la solicitud de inicio de sesión de la sustancia de la solicitud.

En segundo lugar, el proceso de certificación

De la siguiente manera, la interfaz de inicio de sesión es la siguiente:

    @PostMapping("login")
    public String login() {
    
    
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("张三", "123456");
        Subject subject = SecurityUtils.getSubject();
        subject.login(usernamePasswordToken);
        return "login";
    }

Echemos un vistazo directamente subject.login(usernamePasswordToken);. El código está implementado DelegatingSubject#loginy el código detallado es el siguiente:

    public void login(AuthenticationToken token) throws AuthenticationException {
    
    
        // 1. 清空之前会话缓存
        clearRunAsIdentitiesInternal();
        // 2. 交由安全管理器来进行验证操作,通过这一步,则说明验证通过了
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;
		// 获取 Subject 中的的 principals 
        if (subject instanceof DelegatingSubject) {
    
    
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
    
    
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
    
    
          //  	... 抛出异常
        }
        // 记录下 principals, 并将 authenticated  = true,表示当前 Subject 验证通过
     
        this.principals = principals;
        this.authenticated = true;
        // 针对 HostAuthenticationToken 保存其 host
        if (token instanceof HostAuthenticationToken) {
    
    
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
    
    
            this.host = host;
        }
        // 获取session
        Session session = subject.getSession(false);
        if (session != null) {
    
    
        	// 装饰 session, 将 session 包装成  StoppingAwareProxiedSession 类型
            this.session = decorate(session);
        } else {
    
    
            this.session = null;
        }
    }

1. clearRunAsIdentitiesInternal ();

Antes de ejecutar el método, borre la información del usuario previamente almacenada en caché porque debe volver a verificarse. Si el caché existe, bórrelo; de lo contrario, no haga nada.

	// org.apache.shiro.subject.support.DelegatingSubject.RUN_AS_PRINCIPALS_SESSION_KEY
    private static final String RUN_AS_PRINCIPALS_SESSION_KEY =
            DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";
            
    private void clearRunAsIdentities() {
    
    
        Session session = getSession(false);
        if (session != null) {
    
    
        	// 清除 session 中的 org.apache.shiro.subject.support.DelegatingSubject.RUN_AS_PRINCIPALS_SESSION_KEY 属性
            session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
        }
    }

2. securityManager.login (este, token);

Esta es la lógica central de toda la verificación de inicio de sesión. Si este paso finaliza, se pasa la verificación. securityManager.login(this, token);La realización concreta DefaultSecurityManager#loginen. El código detallado es el siguiente:

    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    
    
        AuthenticationInfo info;
        try {
    
    
        	// 1. 进行验证
            info = authenticate(token);
        } catch (AuthenticationException ae) {
    
    
            try {
    
    
            	// 登录错误时的回调。可供扩展。默认是针对 RememberMeManager 的操作,登录失败则使之前的记住状态失效
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
    
    
               // ... 异常日志
            }
            throw ae; //propagate
        }
		// 2. 创建 subject
        Subject loggedIn = createSubject(token, info, subject);
		// 登录成功时的回调。基本同错误时相同
        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }

El código anterior es bastante conciso, analicemos el contenido paso a paso

2.1 autenticar (token);

authenticate(token);Llamará this.authenticator.authenticate(token);, estamos aquí para ver la this.authenticator.authenticate(token);realización directa de AbstractAuthenticator#authenticate:

 	public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
    
    
		// ... 日志异常校验
        AuthenticationInfo info;
        try {
    
    
        	// 带do的方法才是真正做事的方法,这里开始了验证工作
            info = doAuthenticate(token);
            // 验证返回的是null则说明验证失败
            if (info == null) {
    
    
        	// ...抛出异常
            }
        } catch (Throwable t) {
    
    
            AuthenticationException ae = null;
            if (t instanceof AuthenticationException) {
    
    
                ae = (AuthenticationException) t;
            }
            if (ae == null) {
    
    
             // ...抛出异常
            }
            try {
    
    
            	// 通知 AuthenticationListener 监听器验证失败
                notifyFailure(token, ae);
            } catch (Throwable t2) {
    
    
              	// ...抛出异常
            }
            throw ae;
        }

		... 
		// 通知 AuthenticationListener 监听器验证成功
        notifySuccess(token, info);

        return info;
    }

	...
	
	// doAuthenticate 的实现在其子类 ModularRealmAuthenticator#doAuthenticate
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        assertRealmsConfigured();
        // 获取 所有的 Realm
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
    
    
        	// 如果只有一个,单独处理即可
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
    
    
        	// 多个则按照多个处理的策略
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
	
	...    
		// 单一Realm处理 
	protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
    
    
		// 如果当前Realm不支持 当前token,则抛出异常
        if (!realm.supports(token)) {
    
    
         	//  ... 抛出异常
        }
        // 调用 realm 的 getAuthenticationInfo 方法,这里可以看到,如果 getAuthenticationInfo 方法返回为null会直接抛出异常,下面详解
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
    
    
          	//  ... 抛出异常
        }
        return info;
    }

...
	// 多Realm的处理
	 protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
    
    
		// 获取认证策略
        AuthenticationStrategy strategy = getAuthenticationStrategy();
		// 在所有Realm进行前 进行回调
        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        for (Realm realm : realms) {
    
    
			// 在每个Realm进行前 进行回调
            aggregate = strategy.beforeAttempt(realm, token, aggregate);
			
            if (realm.supports(token)) {
    
    
                AuthenticationInfo info = null;
                Throwable t = null;
                try {
    
    
					// 调用 realm 的 getAuthenticationInfo 方法,下面详解
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable throwable) {
    
    
                    t = throwable;
                   
                }
				// 在每个Realm进行后 进行回调
                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
    
    
               // ... 打印日志
            }
        }
        // 在所有Realm进行后 进行回调
        aggregate = strategy.afterAllAttempts(token, aggregate);
        return aggregate;
    }

Aquí podemos ver que Shiro usa diferentes métodos de procesamiento para un solo reino y para varios.

En el código anterior, vemos que la oración central clave es realm.getAuthenticationInfo(token)que su implementación es la AuthenticatingRealm#getAuthenticationInfosiguiente:

	public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
		// 1. 获取缓存的 AuthenticationInfo  信息,如果没有缓存,则返回null。解析成功将在第三步将其缓存
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
    
    
            //otherwise not cached, perform the lookup:
            // 2. 调用我们自己定义的逻辑处理 重新处理一遍
            info = doGetAuthenticationInfo(token);
            if (token != null && info != null) {
    
    
            	// 3. 解析结束如果 AuthenticationInfo 不为空则进行结果缓存
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
    
    
      		// ... 日志打印
        }

        if (info != null) {
    
    
        	// 调用 CredentialsMatcher 进行密码校验
            assertCredentialsMatch(token, info);
        } else {
    
    
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

	... 
	// 进行凭证验证
 	protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
    
    
 		// 获取指定的 CredentialsMatcher (凭证匹配器)
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
    
    
        	// 调用 指定的凭证匹配器进行匹配
            if (!cm.doCredentialsMatch(token, info)) {
    
    
              // 抛出异常
            }
        } else {
    
    
          // ... 抛出异常
        }
    }


2.2 createSubject (token, información, asunto);

Cuando finalice la verificación de inicio de sesión, se devolverá AuthenticationInfo y debemos crear un nuevo Asunto basado en AuthenticationInfo. Al crear este Asunto, se creará una Sesión y los principales y autenticados (registrando que el estado de la sesión de la Sesión actual ha sido autenticado, lo cual es cierto) se guardarán en la Sesión.


createSubject(token, info, subject);La implementación detallada está DefaultSecurityManager#createSubject(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo, org.apache.shiro.subject.Subject)en, el código es el siguiente:

	// 创建 Subject
 	protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
    
    
 		// 根据不同的 SecurityManager 可能创建 DefaultSubjectContext 和  DefaultWebSubjectContext 。
 		// 这里由于是  DefaultWebSecurityManager 所以创建的类型是 DefaultWebSubjectContext
        SubjectContext context = createSubjectContext();
        // 设置标识,表明已经认证通过,在  save(subject); 中 mergeAuthenticationState(subject); 保存的就是该状态,表明验证通过,在之后的请求中会使用到
        context.setAuthenticated(true);
        // 将token保存到上下文中
        context.setAuthenticationToken(token);
        // 将认证的返回信息保存到上下文中
        context.setAuthenticationInfo(info);
        if (existing != null) {
    
    
        	// 将 Subject 信息保存到上下文中
            context.setSubject(existing);
        }
        // 开始创建Subject
        return createSubject(context);
    }

	...	
	// 这里的方法和 AbstractShiroFilter 文章中介绍的相同,这里就不会详细介绍了,会着重说明两处创建Subject的不同
    public Subject createSubject(SubjectContext subjectContext) {
    
    
        //create a copy so we don't modify the argument's backing map:
        // 拷贝一个  SubjectContext  副本
        SubjectContext context = copy(subjectContext);

        //ensure that the context has a SecurityManager instance, and if not, add one:
        // 1. 确保上下文具有SecurityManager实例,如果没有,添加一个
        context = ensureSecurityManager(context);
		// 2. 解析session。
        context = resolveSession(context);
		// 3. 解析 Principals
        context = resolvePrincipals(context);
		// 4. 创建 一个全新的 Subject
        Subject subject = doCreateSubject(context);
		// 5. 保存(缓存) subject 。与AbstractShiroFilter 不同的是,这里会去创建Session,并会写入Cookies 中
        save(subject);

        return subject;
    }

En AbstractShiroFilterla convocatoria estará la createSubject(SubjectContext subjectContext)de crear el Asunto.
La diferencia entre los dos es: AbstractShiroFilter se llama para crear un Asunto para vincular el hilo actual. Si hay una Sesión, extraerá la información almacenada en caché en la Sesión y la completará en el Asunto. Si no hay Sesión, lo hará no crear Sesión de forma activa, sino volver al Asunto directamente.
En el proceso de autenticación, cuando la autenticación es exitosa, la información autenticada se almacenará en caché en la Sesión. Si la Sesión existe, la información se fusionará. Si la Sesión no existe, se creará una Sesión y la información se almacenará en caché. .
En general, AbstractShiroFilter leerá datos de la sesión y el proceso de autenticación escribirá datos en la sesión.

Para obtener una explicación detallada del contenido de createSubject (contexto); consulte el análisis del código fuente de Shiro ②: AbstractShiroFilter

En este punto, todo el proceso de autenticación de inicio de sesión ha finalizado.


Un breve resumen de todo el proceso de inicio de sesión:

  1. http://localhost:8081/shiro/login?userName=张三&password=123456Cuando se envía una solicitud, se procesará primero AbstractShiroFilter. AbstractShiroFilter creará un Subject para vincular el hilo actual, y al mismo tiempo distribuirá la solicitud al filtro correspondiente, aquí lo distribuimos a AnonymousFilter, es decir, lo dejamos ir directamente.
  2. Después de AbstractShiroFiltereso, vinimos directamente al ShiroDemoController#loginmétodo, hay una llamada directa subject.login(usernamePasswordToken);. Entre ellos, se llamará al método de autenticación en nuestro Reino personalizado para la autenticación. Si se crea la autenticación Session, will principalsy authenticatedel estado se guardarán en la Session.

Arriba: la parte de contenido se refiere a la red
https://blog.csdn.net/dgh112233/article/details/100083287
https://www.zhihu.com/pin/1105962164963282944
http://www.muzhuangnet.com/show / 771.
Si el html es intrusivo, comuníquese para eliminarlo. El contenido solo se utiliza para la autograbación y el aprendizaje. Si hay algún error, corrígeme

Supongo que te gusta

Origin blog.csdn.net/qq_36882793/article/details/113344923
Recomendado
Clasificación