详解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的认证(登录)过程的资料请关注毛票票其它相关文章!