Analyse du code source Shiro ③: processus de certification

Introduction

Comme je n'avais jamais utilisé Shiro auparavant, j'ai commencé à l'utiliser récemment, j'ai donc lu certains de ses processus et son code source, j'ai résumé une partie du contenu et je l'ai enregistré. Cette série n'analysera pas complètement tout le code de Shiro, mais analysera seulement brièvement le principal (j'ai) besoin (utiliser) le processus (de) flux (de) . Puisque la majeure partie de cette série est destinée à la compréhension personnelle et que les compétences d'apprentissage personnelles ne sont pas bonnes, il est inévitable qu'il y ait «confusion injuste et fausse». Si vous trouvez quelque chose, merci pour vos corrections, je vous en suis très reconnaissant.


Analyse complète du code source Shiro:

  1. Analyse du code source Shiro①: Construction de projet simple
  2. Analyse du code source Shiro ②: AbstractShiroFilter
  3. Analyse du code source Shiro ③: processus de certification
  4. Analyse du code source Shiro ④: processus d'authentification

Lorsque nous http://localhost:8081/shiro/login?userName=张三&password=123456demandons, nous passerons d'abord par le filtre AbstractShiroFilter, après le filtre AbstractShiroFilter, nous atteindrons la demande de connexion de la substance de la demande.

Deuxièmement, le processus de certification

Comme suit, l'interface de connexion est la suivante:

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

Jetons un coup d'oeil directement subject.login(usernamePasswordToken);. Le code est implémenté dans DelegatingSubject#login, et le code détaillé est le suivant:

    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 ();

Avant d'exécuter la méthode, effacez les informations utilisateur précédemment mises en cache car elles doivent être revérifiées. Si le cache existe, effacez-le, sinon ne faites rien.

	// 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 (ceci, jeton);

Voici la logique de base de l'ensemble de la vérification de la connexion. Si cette étape se termine, la vérification est réussie. securityManager.login(this, token);La réalisation concrète DefaultSecurityManager#loginen. Le code détaillé est le suivant:

    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;
    }

Le code ci-dessus est assez concis, analysons le contenu étape par étape

2.1 authentifier (jeton);

authenticate(token);Appellera this.authenticator.authenticate(token);, nous sommes ici pour regarder la this.authenticator.authenticate(token);réalisation directe 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;
    }

Ici, nous pouvons voir que Shiro utilise différentes méthodes de traitement pour un seul royaume et plusieurs royaumes.

Dans le code ci-dessus, nous voyons que la phrase clé principale est realm.getAuthenticationInfo(token)que son implémentation est AuthenticatingRealm#getAuthenticationInfola suivante:

	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 (jeton, info, sujet);

Lorsque la vérification de connexion est terminée, AuthenticationInfo sera retourné et nous devons créer un nouveau sujet basé sur AuthenticationInfo. Lors de la création de ce sujet, une session sera créée et les principaux et authentifiés (en enregistrant que l'état de session de la session en cours a été authentifié, ce qui est vrai) seront enregistrés dans la session.


createSubject(token, info, subject);L'implémentation détaillée est DefaultSecurityManager#createSubject(org.apache.shiro.authc.AuthenticationToken, org.apache.shiro.authc.AuthenticationInfo, org.apache.shiro.subject.Subject)dans, le code est le suivant:

	// 创建 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;
    }

Dans AbstractShiroFilterl'appel sera createSubject(SubjectContext subjectContext)de créer le sujet.
La différence entre les deux est la suivante: AbstractShiroFilter est appelé pour créer un objet pour lier le thread actuel. S'il y a une session, il extraira les informations mises en cache dans la session et les remplira dans l'objet. S'il n'y a pas de session, il sera ne pas créer activement de session, mais revenir directement au sujet.
Lors du processus d'authentification, lorsque l'authentification est réussie, les informations authentifiées seront mises en cache dans la session. Si la session existe, les informations seront fusionnées. Si la session n'existe pas, une session sera créée et les informations seront mises en cache. .
En général, AbstractShiroFilter lira les données de la session et le processus d'authentification écrira les données dans la session.

Pour une explication détaillée du contenu de createSubject (context) ;, veuillez vous référer à l' analyse du code source de Shiro ②: AbstractShiroFilter

À ce stade, l'ensemble du processus d'authentification de connexion est terminé.


Un bref résumé de l'ensemble du processus de connexion:

  1. http://localhost:8081/shiro/login?userName=张三&password=123456Lorsqu'une demande est envoyée, elle sera traitée en premier AbstractShiroFilter. AbstractShiroFilter créera un Subject pour lier le thread actuel, et en même temps distribuera la requête au filtre approprié. Ici, nous la distribuons à AnonymousFilter, c'est-à-dire, laissons aller directement.
  2. Après cela AbstractShiroFilter, nous sommes venus directement ShiroDemoController#loginméthode, il y a un appel direct subject.login(usernamePasswordToken);. Parmi eux, la méthode d'authentification dans notre royaume personnalisé sera appelée pour l'authentification. Si l'authentification est créée Session, sera principals, et authenticatedl'état sera enregistré dans la Session.

Ci-dessus: La partie contenu fait référence au réseau
https://blog.csdn.net/dgh112233/article/details/100083287
https://www.zhihu.com/pin/1105962164963282944
http://www.muzhuangnet.com/show / 771.
Si le code HTML est intrusif, veuillez contacter pour le supprimer. Le contenu n'est utilisé que pour l'auto-enregistrement et l'apprentissage. S'il y a une erreur, veuillez me corriger

Je suppose que tu aimes

Origine blog.csdn.net/qq_36882793/article/details/113344923
conseillé
Classement