shiro登录过程分析
来源:互联网 发布:mac开机咚一声怎么关 编辑:程序博客网 时间:2024/05/22 00:23
关于shiro就不用做过多介绍了,今天主要分析下登录过程
首先我大致画了个流程图(可能不够详细):
第一步:用户登录,根据用户登录名密码生产Token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);Subject subject = SecurityUtils.getSubject();subject.login(token);这里调用了代理subject的login方法,代码如下:
public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); Subject subject = securityManager.login(this, token); PrincipalCollection principals; String host = null; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals: principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals == null || principals.isEmpty()) { String msg = "Principals returned from securityManager.login( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = decorate(session); } else { this.session = null; } }可以看到第二行,实际是调用securityManager的login方法
第二步:调用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; }第三步:调用securityManager的 authenticate方法 该方法在 其上级类 AuthenticatingSecurityManager中,代码如下:
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { return this.authenticator.authenticate(token); }实际调用了authenticator的authenticate方法,而AuthenticatingSecurityManager的无参构造函数中
public AuthenticatingSecurityManager() { super(); this.authenticator = new ModularRealmAuthenticator(); }而ModularRealmAuthenticator类继承了AbstractAuthenticator类
第四步:调用AbstractAuthenticator的authenticate方法
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 { 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; }看try语句中的 doAuthenticate()方法 则是在其子类ModularRealmAuthenticator中实现,所以
第五步:调用ModularRealmAuthenticator的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); } }第二行获取realms,但我们记得只配置过realm,realms是什么时候赋值的呢,其实很简单 spring对bean属性的赋值是通过反射 实际调用的是set方法,即我们配置了
一个property 为realm的属性 对属性注入的时候调用的setRealm方法
public void setRealm(Realm realm) { if (realm == null) { throw new IllegalArgumentException("Realm argument cannot be null"); } Collection<Realm> realms = new ArrayList<Realm>(1); realms.add(realm); setRealms(realms); }所以这里我们的realms实际就是配置的realm,当然前提是我们只配置了单个
第六步:调用ModularRealmAuthenticator的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方法
第七步:调用AuthenticatingRealm的getAuthenticationInfo方法
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; }第一行代码,通过缓存获取AuthenticationInfo,说到这里正好看看缓存是怎么实现的,同样代码全在这,跟着走就行
而我们的cacheManager哪来的呢,我们发现在setRealm方法中调用了setRealms
public void setRealms(Collection<Realm> realms) { if (realms == null) { throw new IllegalArgumentException("Realms collection argument cannot be null."); } if (realms.isEmpty()) { throw new IllegalArgumentException("Realms collection argument cannot be empty."); } this.realms = realms; afterRealmsSet(); } protected void afterRealmsSet() { applyCacheManagerToRealms(); applyEventBusToRealms(); }可以看到在设置完realms以后调用了一个后续处理方法,在afterRealmsSet中 有个调用 applyCacheManagerToRealms方法 ,字面意思也是很好理解 应用缓存管理器
到realms中,而这种方法代码为:
protected void applyCacheManagerToRealms() { CacheManager cacheManager = getCacheManager(); Collection<Realm> realms = getRealms(); if (cacheManager != null && realms != null && !realms.isEmpty()) { for (Realm realm : realms) { if (realm instanceof CacheManagerAware) { ((CacheManagerAware) realm).setCacheManager(cacheManager); } } } }实际就是判断如果cacheManager不为空 就循环realms设置cacheManager
(有点啰嗦,哈哈,自己当时就是这么想的)
在上面getAuthenticationInfo方法中,我们刚才说过第一行是从缓存中取AuthenticationInfo,如果为空
第八步:调用realm的doGetAuthenticationInfo方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// TODO Auto-generated method stubString userName = (String) token.getPrincipal();//通过token获取用户信息,这里我们一般从数据库中查询SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, password, getName());return authenticationInfo;}返回AuthenticationInfo,接着下面代码
if (token != null && info != null) { cacheAuthenticationInfoIfPossible(token, info); }判断 如果token与获取到的AuthenticationInfo都不为空,缓存AuthenticationInfo信息
关于从缓存中查询AuthenticationInfo以及缓存AuthenticationInfo信息的方法 这里就不作分析了,可以看做对一个map的操作吧
当然到这里还没完,同样在上面方法中,
if (info != null) { assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); }如果AuthenticationInfo不为空 即通过登录用户查询到了对应的信息
第九步:调用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,如果不为空
第十步:调用CredentialsMatcher的doCredentialsMatch方法,当然CredentialsMatcher我们可以自定义了
第十一步:上面步骤都通过以后回到DefualtSecurityManager的login方法中
Subject loggedIn = createSubject(token, info, subject);创建Subject
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) { SubjectContext context = createSubjectContext(); context.setAuthenticated(true); context.setAuthenticationToken(token); context.setAuthenticationInfo(info); if (existing != null) { context.setSubject(existing); } return createSubject(context); }接着就是通过SubjectFactory生成subject,这里就不说了,就是从我们查询把我们查询到的用户身份信息关联到对应的subject中
整个过程大致就是这样了,可能有遗漏,后续再慢慢补充咯
- shiro登录过程分析
- shiro登录过程分析
- Shiro 1 , 登录过程
- shiro登录过程
- shiro之登录验证过程
- Shiro源码分析----登录流程
- shiro源码分析篇3:用户登录缓存登录信息
- shiro源码分析之shiroFilter初始化过程
- shiro源码分析之Realm调用过程
- 新浪网站登录过程分析
- 微博登录过程分析
- shiro的认证思路分析(即登录,流程)
- shiro源码分析篇2:请求过滤,登录判断
- shiro学习笔记——从源码角度分析shiro身份验证过程
- springmvc + shiro 登录登出
- springmvc + shiro 登录登出
- spring+apache shiro登录
- shiro-cas 单点登录
- 【Linux】crontab定时执行任务
- Java使用MD5对文件进行签名
- 不能打开到主机的连接,在端口1521:连接失败的解决方法
- 计蒜课--跳跃游戏
- 图片不拉伸方法
- shiro登录过程分析
- 手机无线连接电脑
- Android开发之去掉空格
- finally 中抛出异常处理
- ice环境变量配置
- 2016.11.15——今日收获
- 数据库悲观锁的使用解释
- 关于C/C++中main函数参数中argc和argv的解释
- SIT与UAT的区别