shiro源码分析之Realm调用过程
1、首先看使用shiro(1.3.0)框架要使用Realm的配置。
- 配置Spring.xml
<!-- 自定义Realm实现 -->
<bean id="shiroRealm" class="com.lcl.shiro.filter.realmManage"/>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<!-- <property name="cacheManager" ref="cacheManager"/> -->
</bean>
此时,我们把自定义的realm配置在securityManager中,然后看登录时候代码:
//得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, pwd); try { //登录,即身份验证 if (!subject.isAuthenticated()) {//判断时候已经登录 subject.login(token); } } catch (UnknownAccountException e) { } catch (IncorrectCredentialsException e) { } catch (ShiroException e) { } return oMap;
注:这段代码是创建Subject和UsernamePasswordToken对象,然后执行subject.login(token)方法,系统就自动调用realm中的登录验证。
下面我们看realm中的代码实现:
public class realmManage extends AuthorizingRealm implements Serializable{ /** * Logger日志 */ private static final Logger LOGGER = LoggerFactory.getLogger(realmManage.class); @Autowired private LoginService loginService; /** * 权限授权函数,查詢用戶的所擁有的權限 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { String userName = (String) principal.getPrimaryPrincipal(); // 取得用户的所有权限 Set<String> permissions = new HashSet<String>(); Set<String> roleNames = new HashSet<String>(); //查詢用戶角色集合 List<String> roleList = loginService.selectRolesByName(userName); for(String role : roleList){ roleNames.add(role); } //查詢用戶權限集合 List<String> permissionList = loginService.selectHasPermissionsByName(userName); for(String permissionUnion : permissionList){ permissions.add(permissionUnion); } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames); info.setStringPermissions(permissions); return info; } /** * 身份认证函数 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authctoken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authctoken; String userName = (String) token.getPrincipal(); // 得到用户名 String pwd = new String((char[]) token.getCredentials()); // 得到密码 String password =""; try { password = loginService.selectPwdByName(userName); } catch (Exception e) { throw new ShiroException();//账号异常 } if (password == null || "".equals(password)) { throw new UnknownAccountException(); //如果用户名错误 } if(!pwd.equals(password)) { throw new IncorrectCredentialsException(); //如果密码错误 } //如果身份认证验证成功,返回一个AuthenticationInfo实现; return new SimpleAuthenticationInfo(userName, pwd, getName()); } }
这段代码中的realmManage继承了AuthorizingRealm对象,重写了doGetAuthenticationInfo(身份认证函数)和doGetAuthorizationInfo(权限授权函数)两个方法,然后在执行subject.login(token)方法,系统就自动调用realm中的登录验证。现在开始看subject.login(token)(org.apache.shiro.subject.support.DelegatingSubject)方法中的实现:
public void login(AuthenticationToken token) throws AuthenticationException { this.clearRunAsIdentitiesInternal(); Subject subject = this.securityManager.login(this, token); String host = null; PrincipalCollection principals; if (subject instanceof DelegatingSubject) { DelegatingSubject session = (DelegatingSubject) subject; principals = session.principals; host = session.host; } else { principals = subject.getPrincipals(); } if (principals != null && !principals.isEmpty()) { this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session2 = subject.getSession(false); if (session2 != null) { this.session = this.decorate(session2); } else { this.session = null; } } else { String session1 = "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(session1); } }
注:在方法内我们看到参数进入了this.securityManager.login(this, token)中,然后我们在看看这securityManager(org.apache.shiro.mgt.DefaultSecurityManager)对象中的login(this, token)方法:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = this.authenticate(token); } catch (AuthenticationException arg6) { AuthenticationException loggedIn = arg6; try { this.onFailedLogin(token, loggedIn, subject); } catch (Exception arg5) { if (log.isInfoEnabled()) { log.info( "onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", arg5); } } throw arg6; } Subject loggedIn1 = this.createSubject(token, info, subject); this.onSuccessfulLogin(token, info, loggedIn1); return loggedIn1; }
注:在这个方法中我们可以看到参数进入 this.authenticate(token)方法中,而且在这个方法中已经抓取了AuthenticationException异常并且做了处理。在DefaultSecurityManager的父类中(org.apache.shiro.mgt.AuthenticatingSecurityManager),我们进入authenticate(token)方法,继续深入查看:
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { return this.authenticator.authenticate(token); }
然后继续进入方法,在AbstractAuthenticator(org.apache.shiro.authc.AbstractAuthenticator)中,我们看到此时方法实现:
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException { if (token == null) { throw new IllegalArgumentException("Method argument (authentication token) cannot be null."); } else { log.trace("Authentication attempt received for token [{}]", token); AuthenticationInfo info; try { info = this.doAuthenticate(token); if (info == null) { String t = "No account information found for authentication token [" + token + "] by this Authenticator instance. Please check that it is configured correctly."; throw new AuthenticationException(t); } } catch (Throwable arg7) { AuthenticationException ae = null; if (arg7 instanceof AuthenticationException) { ae = (AuthenticationException) arg7; } if (ae == null) { String t2 = "Authentication failed for token submission [" + token + "]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException)."; ae = new AuthenticationException(t2, arg7); if (log.isWarnEnabled()) { log.warn(t2, arg7); } } try { this.notifyFailure(token, ae); } catch (Throwable arg6) { 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, arg6); } } throw ae; } log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info); this.notifySuccess(token, info); return info; } }
然后继续进入this.doAuthenticate(token)方法,在其子类ModularRealmAuthenticator(org.apache.shiro.authc.pam.ModularRealmAuthenticator)中我们找到其实现:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { this.assertRealmsConfigured(); Collection realms = this.getRealms(); return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm) realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken); }
此时,我们终于看到目标realm,这时候我们可以发现,该方法中通过Collection大小的判断,是否存在多个realm,然后分别走不通的方法进行验证,下面在贴出这两个方法的分别实现:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) { if (!realm.supports(token)) { String info1 = "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(info1); } else { 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); } else { return info; } } } protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) { AuthenticationStrategy strategy = this.getAuthenticationStrategy(); AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token); if (log.isTraceEnabled()) { log.trace("Iterating through {} realms for PAM authentication", Integer.valueOf(realms.size())); } Iterator arg4 = realms.iterator(); while (arg4.hasNext()) { Realm realm = (Realm) arg4.next(); aggregate = strategy.beforeAttempt(realm, token, aggregate); if (realm.supports(token)) { log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm); AuthenticationInfo info = null; Throwable t = null; try { info = realm.getAuthenticationInfo(token); } catch (Throwable arg10) { t = arg10; if (log.isWarnEnabled()) { String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:"; log.warn(msg, arg10); } } aggregate = strategy.afterAttempt(realm, token, info, aggregate, t); } else { log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token); } } aggregate = strategy.afterAllAttempts(token, aggregate); return aggregate; }
可以看到,两个方法都是调用了realm.getAuthenticationInfo(token)来验证登录信息的。
个人见解,不喜勿喷,若有错误,欢迎提出。