shiro certification process analysis

0 Preface

shiro authentication process can start with subject.login(token).

This section only analyzes the basic authentication process. Various authentication strategies of the authentication process and the credentialsMatcher of realm are not the focus of this section. Interested children's shoes can be analyzed by themselves.

First, the test demo is prepared:

    @Test
    public void testCustomRealm() {

        //establish SecurityManager Instance, configuration realm
        org.apache.shiro.mgt.SecurityManager securityManager = new DefaultSecurityManager();
        Realm realm = new MyRealm1();
        ((DefaultSecurityManager) securityManager).setRealm(realm);

        //binding SecurityManager to SecurityUtils
        SecurityUtils.setSecurityManager(securityManager);

        //obtain Subject
        Subject subject = SecurityUtils.getSubject();

        //Create user name/Password authentication Token(User identity/(voucher)
        UsernamePasswordToken token = new UsernamePasswordToken("test", "123");

        try {
            //Login, i.e. authentication (analysis portal)
            subject.login(token);
        } catch (AuthenticationException e) {
            //Authentication failure handling
            e.printStackTrace();
        }

        //Assert that the user is logged in
        Assert.assertEquals(true, subject.isAuthenticated());

        //sign out
        subject.logout();
    }

The test program creates a SecurityManager, configures the realm for it, and binds the SecurityManager to SecurityUtils, which is globally unique and can be used to obtain the currently accessed subject. Then create a token and simulate a login with subject, that is, call the login method. Myrealm1 is defined as follows:

public class MyRealm1 implements AuthenticatingRealm {

    @Override
    public String getName() {
        return "myrealm1";
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken; //Only supported UsernamePasswordToken Type Token
    }

    @Override
    public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String)token.getPrincipal();  //Get user name
        String password = new String((char[])token.getCredentials()); //Get password
        if(!"test".equals(username)) {
            throw new UnknownAccountException(); //If the user name is wrong
        }
        if(!"123".equals(password)) {
            throw new IncorrectCredentialsException(); //If the password is wrong
        }
        //If the authentication is successful, a AuthenticationInfo realization;
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

Here Myrealm1 implements authenticating Realm for authentication   Interface. In practice, the sub interface AuthorizingRealm (including relevant methods of authentication and authorization) that generally implements Realm is used

 

1 certification analysis

subject implements the login method of the DelegatingSubject class (line 254 of the DelegatingSubject source code)

    public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        Subject subject = securityManager.login(this, token);

        //Omit other contents...
    }

DelegatingSubject calls the login of the securityManager (DefaultSecurityManager) to authenticate.

//DefaultSecurityManager Line 267 of the source code
    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
           //Call parent class AuthenticatingSecurityManager of authenticate
            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;
    }
//AuthenticatingSecurityManager Source code line 105
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
//Pass authenticator
Authenticator authentication
return this.authenticator.authenticate(token); }
//AbstractAuthenticator Line 188

    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

        if (token == null) {
            throw new IllegalArgumentException("Method argumet (authentication token) cannot be null.");
        }

        log.trace("Authentication attempt received for token [{}]", token);

        AuthenticationInfo info;
        try {
            //Subclass ModularRealmAuthenticator realization
            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);
            }
            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;
    }
//ModularRealmAuthenticator Line 263
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        //By individual realm And multiple realm Execute different processing logic respectively
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

Next, deal with the authentication of a single realm

//ModularRealmAuthenticator Line 173
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        //call realm Implementation method of supports Determine whether to support token type
        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);
        }
        //AuthenticatingRealm of getAuthenticationInfo method
        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;
    }
//AuthenticatingRealm Line 563
    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //Have a look first cache Is there anything in it
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            //Custom is called here realm Implementation method (at the beginning of this article) MyRealm1)
            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) {
           //This is used CredentialsMatch function
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

The doGetAuthenticationInfo method of MyRealm1 will return the user information info saved in advance through the user name (generally, it can be read from the database, including the user name, encrypted password, salt, etc.).

CredentialsMatch is used to match the entered token password with the user's encrypted password after encrypting it through the configured encryption algorithm and salt in the user's info. (MyRealm1 example in this article does not reflect this encryption function, directly compare the password, and throw exceptions if it fails)

If the matching is successful, info information will be returned, otherwise an exception related to authentication failure will be thrown. The single realm authentication process has been roughly completed. The following multi realm authentication process

//ModularRealmAuthenticator Line 198
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

        AuthenticationStrategy strategy = getAuthenticationStrategy();

        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        if (log.isTraceEnabled()) {
            log.trace("Iterating through {} realms for PAM authentication", realms.size());
        }

        for (Realm realm : realms) {

            aggregate = strategy.beforeAttempt(realm, token, aggregate);

            if (realm.supports(token)) {

                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);

                AuthenticationInfo info = null;
                Throwable t = null;
                try {
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable throwable) {
                    t = throwable;
                    if (log.isDebugEnabled()) {
                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                        log.debug(msg, t);
                    }
                }

                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }

        aggregate = strategy.afterAllAttempts(token, aggregate);

        return aggregate;
    }

The multi realm authentication process adds the authentication strategy policy before and after calling each realm in a loop. shiro provides three strategies

Atleast one successful strategy: if there is a realm authentication, it will succeed

All successful strategy: all realm authentication is successful

First successful strategy: only the first realm authentication is successful

If the above three strategies are not satisfied, users can define and implement the authentication strategy for extension.

 

Summarize the certification process

(1) subject.login(token) starts authentication

(2) subject will call the login(token) method of SecuretyManager

(3) SecurityManager calls authenticate (token) of Authenticator

(4) Authenticator is divided into single realm and multi realm. Multi realm carries out authentication process according to different authentication strategies

(5) Finally, it calls the doGetAuthenticationInfo of real to obtain the info information, and then compares the passwords through CredentialsMatch. If the authentication fails, an exception will be thrown

  

Keywords: Shiro

Added by msound on Mon, 29 Nov 2021 05:24:27 +0200