关于java:shrio的认证登录过程

6次阅读

共计 6408 个字符,预计需要花费 17 分钟才能阅读完成。

shrio 是一个比拟轻量级的平安框架,次要的作用是在后端承当认证和受权的工作。明天就讲一下 shrio 进行认证的一个过程。
首先先介绍一下在认证过程中的几个要害的对象:

  • Subject:主体
    拜访零碎的用户,主体能够是用户、程序等,进行认证的都称为主体;
  • Principal:身份信息
    是主体(subject)进行身份认证的标识,标识必须具备唯一性,如用户名、手机号、邮箱地址等,一个主体能够有多个身份,然而必须有一个主身份(Primary Principal)。
  • credential:凭证信息
    是只有主体本人晓得的平安信息,如明码、证书等。

接着咱们就进入认证的具体过程:
首先是从前端的登录表单中接管到用户输出的 token(username + password):

@RequestMapping("/login")
public String login(@RequestBody Map user){Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.get("email").toString(), user.get("password").toString());
     try {subject.login(usernamePasswordToken);
     } catch (UnknownAccountException e) {return "邮箱不存在!";} catch (AuthenticationException e) {return "账号或明码谬误!";}
        return "登录胜利!";
    }

这里的 usernamePasswordToken(以下简称 token)就是用户名和明码的一个联合对象,而后调用 subject 的 login 办法将 token 传入开始认证过程。
接着会发现 subject 的 login 办法调用的其实是 securityManager 的 login 办法:

Subject subject = securityManager.login(this, token);

再往下看 securityManager 的 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;
}

下面代码的关键在于:

info = authenticate(token);

行将 token 传入 authenticate 办法中失去一个 AuthenticationInfo 类型的认证信息。
以下是 authenticate 办法的具体内容:

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

首先就是判断 token 是否为空,不为空再将 token 传入 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);
    }
}

这一步是判断是有单个 Reaml 验证还是多个 Reaml 验证,单个就执行 doSingleRealmAuthentication() 办法,多个就执行 doMultiRealmAuthentication() 办法。
个别状况下是单个验证:

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,只有反对 Realm 才调用 realm.getAuthenticationInfo(token) 获取 info。

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

首先查看 Cache 中是否有该 token 的 info,如果有,则间接从 Cache 中去即可。如果是第一次登录,则 Cache 中不会有该 token 的 info,须要调用 doGetAuthenticationInfo(token) 办法获取,并将后果退出到 Cache 中,不便下次应用。而这里调用的 doGetAuthenticationInfo() 办法就是咱们在本人重写的办法,具体的内容是自定义了对拿到的这个 token 的一个解决的过程:

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {if (authenticationToken.getPrincipal() == null)
        return null;
    String email = authenticationToken.getPrincipal().toString();
    User user = userService.findByEmail(email);
    if (user == null)
        return null;
    else return new SimpleAuthenticationInfo(email, user.getPassword(), getName());
}

这其中进行了几步判断:首先是判断传入的用户名是否为空,在判断传入的用户名在本地的数据库中是否存在,不存在则返回一个用户名不存在的 Exception。以上两部通过之后生成一个包含传入用户名和明码的 info,留神此时对于用户名的验证曾经实现,接下来进入对明码的验证。
将这一步失去的 info 返回给 getAuthenticationInfo 办法中的

assertCredentialsMatch(token, info);

此时的 info 是正确的用户名和明码的信息,token 是输出的用户名和明码的信息,通过后面步骤的验证过程,用户名此时曾经是真是存在的了,这一步就是验证输出的用户名和明码的对应关系是否正确。

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

下面步骤就是验证 token 中的明码的和 info 中的明码是否对应的代码。这一步验证实现之后,整个 shrio 认证的过程就完结了。

正文完
 0