详解shrio的认证(登录)过程
shrio是一个比较轻量级的安全框架,主要的作用是在后端承担认证和授权的工作。今天就讲一下shrio进行认证的一个过程。
首先先介绍一下在认证过程中的几个关键的对象:
- Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
- Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(PrimaryPrincipal)。
- credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
接着我们就进入认证的具体过程:
首先是从前端的登录表单中接收到用户输入的token(username+password):
@RequestMapping("/login")
publicStringlogin(@RequestBodyMapuser){
Subjectsubject=SecurityUtils.getSubject();
UsernamePasswordTokenusernamePasswordToken=newUsernamePasswordToken(user.get("email").toString(),user.get("password").toString());
try{
subject.login(usernamePasswordToken);
}catch(UnknownAccountExceptione){
return"邮箱不存在!";
}catch(AuthenticationExceptione){
return"账号或密码错误!";
}
return"登录成功!";
}
这里的usernamePasswordToken(以下简称token)就是用户名和密码的一个结合对象,然后调用subject的login方法将token传入开始认证过程。
接着会发现subject的login方法调用的其实是securityManager的login方法:
Subjectsubject=securityManager.login(this,token);
再往下看securityManager的login方法内部:
publicSubjectlogin(Subjectsubject,AuthenticationTokentoken)throwsAuthenticationException{
AuthenticationInfoinfo;
try{
info=authenticate(token);
}catch(AuthenticationExceptionae){
try{
onFailedLogin(token,ae,subject);
}catch(Exceptione){
if(log.isInfoEnabled()){
log.info("onFailedLoginmethodthrewan"+
"exception.LoggingandpropagatingoriginalAuthenticationException.",e);
}
}
throwae;//propagate
}
SubjectloggedIn=createSubject(token,info,subject);
onSuccessfulLogin(token,info,loggedIn);
returnloggedIn;
}
上面代码的关键在于:
info=authenticate(token);
即将token传入authenticate方法中得到一个AuthenticationInfo类型的认证信息。
以下是authenticate方法的具体内容:
publicfinalAuthenticationInfoauthenticate(AuthenticationTokentoken)throwsAuthenticationException{
if(token==null){
thrownewIllegalArgumentException("Methodargument(authenticationtoken)cannotbenull.");
}
log.trace("Authenticationattemptreceivedfortoken[{}]",token);
AuthenticationInfoinfo;
try{
info=doAuthenticate(token);
if(info==null){
Stringmsg="Noaccountinformationfoundforauthenticationtoken["+token+"]bythis"+
"Authenticatorinstance.Pleasecheckthatitisconfiguredcorrectly.";
thrownewAuthenticationException(msg);
}
}catch(Throwablet){
AuthenticationExceptionae=null;
if(tinstanceofAuthenticationException){
ae=(AuthenticationException)t;
}
if(ae==null){
//ExceptionthrownwasnotanexpectedAuthenticationException.Thereforeitisprobablyalittlemore
//severeorunexpected.So,wrapinanAuthenticationException,logtowarn,andpropagate:Stringmsg="Authenticationfailedfortokensubmission["+token+"].Possibleunexpected"+
"error?(TypicalorexpectedloginexceptionsshouldextendfromAuthenticationException).";
ae=newAuthenticationException(msg,t);
if(log.isWarnEnabled())
log.warn(msg,t);
}
try{
notifyFailure(token,ae);
}catch(Throwablet2){
if(log.isWarnEnabled()){
Stringmsg="Unabletosendnotificationforfailedauthenticationattempt-listenererror?."+
"PleasecheckyourAuthenticationListenerimplementation(s).Loggingsendingexception"+
"andpropagatingoriginalAuthenticationExceptioninstead...";
log.warn(msg,t2);
}
}
throwae;
}
log.debug("Authenticationsuccessfulfortoken[{}].Returnedaccount[{}]",token,info);
notifySuccess(token,info);
returninfo;
}
首先就是判断token是否为空,不为空再将token传入doAuthenticate方法中:
protectedAuthenticationInfodoAuthenticate(AuthenticationTokenauthenticationToken)throwsAuthenticationException{
assertRealmsConfigured();
Collectionrealms=getRealms();
if(realms.size()==1){
returndoSingleRealmAuthentication(realms.iterator().next(),authenticationToken);
}else{
returndoMultiRealmAuthentication(realms,authenticationToken);
}
}
这一步是判断是有单个Reaml验证还是多个Reaml验证,单个就执行doSingleRealmAuthentication()方法,多个就执行doMultiRealmAuthentication()方法。
一般情况下是单个验证:
protectedAuthenticationInfodoSingleRealmAuthentication(Realmrealm,AuthenticationTokentoken){
if(!realm.supports(token)){
Stringmsg="Realm["+realm+"]doesnotsupportauthenticationtoken["+
token+"].PleaseensurethattheappropriateRealmimplementationis"+
"configuredcorrectlyorthattherealmacceptsAuthenticationTokensofthistype.";
thrownewUnsupportedTokenException(msg);
}
AuthenticationInfoinfo=realm.getAuthenticationInfo(token);
if(info==null){
Stringmsg="Realm["+realm+"]wasunabletofindaccountdataforthe"+
"submittedAuthenticationToken["+token+"].";
thrownewUnknownAccountException(msg);
}
returninfo;
}
这一步中首先判断是否支持Realm,只有支持Realm才调用realm.getAuthenticationInfo(token)获取info。
publicfinalAuthenticationInfogetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{
AuthenticationInfoinfo=getCachedAuthenticationInfo(token);
if(info==null){
//otherwisenotcached,performthelookup:
info=doGetAuthenticationInfo(token);
log.debug("LookedupAuthenticationInfo[{}]fromdoGetAuthenticationInfo",info);
if(token!=null&&info!=null){
cacheAuthenticationInfoIfPossible(token,info);
}
}else{
log.debug("Usingcachedauthenticationinfo[{}]toperformcredentialsmatching.",info);
}
if(info!=null){
assertCredentialsMatch(token,info);
}else{
log.debug("NoAuthenticationInfofoundforsubmittedAuthenticationToken[{}].Returningnull.",token);
}
returninfo;
}
首先查看Cache中是否有该token的info,如果有,则直接从Cache中去即可。如果是第一次登录,则Cache中不会有该token的info,需要调用doGetAuthenticationInfo(token)方法获取,并将结果加入到Cache中,方便下次使用。而这里调用的doGetAuthenticationInfo()方法就是我们在自己重写的方法,具体的内容是自定义了对拿到的这个token的一个处理的过程:
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{
if(authenticationToken.getPrincipal()==null)
returnnull;
Stringemail=authenticationToken.getPrincipal().toString();
Useruser=userService.findByEmail(email);
if(user==null)
returnnull;
elsereturnnewSimpleAuthenticationInfo(email,user.getPassword(),getName());
}
这其中进行了几步判断:首先是判断传入的用户名是否为空,在判断传入的用户名在本地的数据库中是否存在,不存在则返回一个用户名不存在的Exception。以上两部通过之后生成一个包括传入用户名和密码的info,注意此时关于用户名的验证已经完成,接下来进入对密码的验证。
将这一步得到的info返回给getAuthenticationInfo方法中的
assertCredentialsMatch(token,info);
此时的info是正确的用户名和密码的信息,token是输入的用户名和密码的信息,经过前面步骤的验证过程,用户名此时已经是真是存在的了,这一步就是验证输入的用户名和密码的对应关系是否正确。
protectedvoidassertCredentialsMatch(AuthenticationTokentoken,AuthenticationInfoinfo)throwsAuthenticationException{
CredentialsMatchercm=getCredentialsMatcher();
if(cm!=null){
if(!cm.doCredentialsMatch(token,info)){
//notsuccessful-throwanexceptiontoindicatethis:
Stringmsg="Submittedcredentialsfortoken["+token+"]didnotmatchtheexpectedcredentials.";
thrownewIncorrectCredentialsException(msg);
}
}
else{
thrownewAuthenticationException("ACredentialsMatchermustbeconfiguredinordertoverify"+
"credentialsduringauthentication.Ifyoudonotwishforcredentialstobeexamined,you"+
"canconfigurean"+AllowAllCredentialsMatcher.class.getName()+"instance.");
}
}
上面步骤就是验证token中的密码的和info中的密码是否对应的代码。这一步验证完成之后,整个shrio认证的过程就结束了。
以上就是详解shrio的认证(登录)过程的详细内容,更多关于shrio的认证(登录)过程的资料请关注毛票票其它相关文章!