Authentication源码解析

Posted xiaoliangup

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Authentication源码解析相关的知识,希望对你有一定的参考价值。

1、获取当前的 Subject. 调用 SecurityUtils.getSubject();

从当前线程的threadLocals属性中获取Subject对象

SecurityUtils
public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }
ThreadContext
public static Subject getSubject() {
        return (Subject) get(SUBJECT_KEY);
    }

public static Object get(Object key) {
        if (log.isTraceEnabled()) {
            String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

        Object value = getValue(key);
        if ((value != null) && log.isTraceEnabled()) {
            String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }
        return value;
    }

private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
private static Object getValue(Object key) {
        Map<Object, Object> perThreadResources = resources.get();
        return perThreadResources != null ? perThreadResources.get(key) : null;
    }
ThreadLocal<T>
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

 

2、Subject 的 login(AuthenticationToken) 

登录验证成功后将Subject.authenticated置位true,登录后再登陆时,当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()

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

        this.principals = principals;
        this.authenticated = true;

AuthenticatingSecurityManager中调用authenticator.authenticate(),其中authenticator初始化是org.apache.shiro.authc.pam.ModularRealmAuthenticator.ModularRealmAuthenticator

this.authenticator = new ModularRealmAuthenticator();
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

ModularRealmAuthenticator中循环Realms判断

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

那ModularRealmAuthenticator中循环Realms是何时赋值的呢?Spring创建DefaultSecurityManager对象及属性赋值之后,调用afterRealmsSet() 

AuthenticatingSecurityManager
protected void afterRealmsSet() {
        super.afterRealmsSet();
        if (this.authenticator instanceof ModularRealmAuthenticator) {
            ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
        }
    }

下面以单Realm为例,进行doSingleRealmAuthentication解析。

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

realm.getAuthenticationInfo()中主要实现了:

1、调用Realm.doGetAuthenticationInfo(),从数据源中获取用户、密码及相应的密码处理

2、进行数据源获取的密码和登录页面获取密码的比对

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

开发中一般继承Realm,重写doGetAuthenticationInfo。

最后分析下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.");
        }
    }

根据credentialsMatcher 属性来进行的密码的比对。在定义Realm时根据加密方式定义相应的CredentialsMatcher,默认为SimpleCredentialsMatcher

<bean id="myRealm" class="org.tarena.shiro.realm.MyRealm">
        <property name="credentialsMatcher" >
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="MD5"></property>
                <property name="hashIterations" value="1024"></property>
            </bean>
        </property>
    </bean>

以上是关于Authentication源码解析的主要内容,如果未能解决你的问题,请参考以下文章

APIView之dispatch中认证与权限源码刨析:

Spring Security---用户名密码登录流程源码解析

Github:git克隆源码不成功:remote: Support for password authentication was removed on August 13, 2021.

解释器在解析JavaScript代码时对于这两种方式

片段(Java) | 机试题+算法思路+考点+代码解析 2023

Android 逆向类加载器 ClassLoader ( 类加载器源码简介 | BaseDexClassLoader | DexClassLoader | PathClassLoader )(代码片段