摘要: 原创出处 https://my.oschina.net/xiaoqiyiye/blog/1619214 「xiaoqiyiye」欢迎转载,保留摘要,谢谢!
本文在于分析Shiro源码,对于新学习的朋友可以参考 [开涛博客](http://jinnianshilongnian.iteye.com/blog/2018398)进行学习。
Subject接口
在Shiro中Subject表示系统进行交互的用户或某一个第三方服务,所有Subject实例都被绑定到(且这是必须的)一个SecurityManager上。Subject接口提供了很多方法,主要包括进行认证的登录登出方法、进行授权判断的方法和Session访问的方法。在Shiro中获取当前运行的Subject要使用SecurityUtils.getSubject()方法来获取一个Subject实例。
下面将Subject接口方法进行分类:
和认证的相关的方法
Object getPrincipal () ;PrincipalCollection getPrincipals () ;boolean isAuthenticated () ;void login (AuthenticationToken token) throws AuthenticationException ;void logout () ;
和授权相关的方法
boolean isPermitted (String permission) ;boolean isPermitted (Permission permission) ;boolean [] isPermitted(String... permissions);boolean [] isPermitted(List<Permission> permissions);boolean isPermittedAll (String... permissions) ;boolean isPermittedAll (Collection<Permission> permissions) ;void checkPermission (String permission) throws AuthorizationException ;void checkPermission (Permission permission) throws AuthorizationException ;void checkPermissions (String... permissions) throws AuthorizationException ;void checkPermissions (Collection<Permission> permissions) throws AuthorizationException ;boolean hasRole (String roleIdentifier) ;boolean [] hasRoles(List<String> roleIdentifiers);boolean hasAllRoles (Collection<String> roleIdentifiers) ;void checkRole (String roleIdentifier) throws AuthorizationException ;void checkRoles (Collection<String> roleIdentifiers) throws AuthorizationException ;void checkRoles (String... roleIdentifiers) throws AuthorizationException ;
和Session相关方法
Session getSession () ;Session getSession (boolean create) ;
runAs相关的方法
void runAs (PrincipalCollection principals) throws NullPointerException, IllegalStateException ;boolean isRunAs () ;PrincipalCollection getPreviousPrincipals () ;PrincipalCollection releaseRunAs () ;
在Subject接口中还提供了一个静态内部类Builder,这个类辅助创建Subject实例。在Builder中需要引用SubjectContext和SecurityManager,SubjectContext负责收集Subject的上下文信息,SecurityManger才是真实创建Subject的对象,通过createSubject(SubjectContext subjectContext)方法来创建,SubjectContext和SessionContext类是,我们可以将它看作一个Map对象。创建Subject已经在Shiro源码分析(1) - Shiro开篇 中分析过了,这里不再赘述。
Shiro登录
在Shiro源码分析(1) - Shiro开篇 中我们已经了解了Subject是如何创建的,这里我们将分析Shiro是如何处理登录和登出的。为了方便大家看明白些,还是贴出之前使用的代码块。
Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang" , "123" ); subject.login(token);
在下面的分析中我们以DelegatingSubject类来说明,DelegatingSubject是Subject接口的直接实现类。我们先看看DelegatingSubject的属性和构造方法。
protected PrincipalCollection principals;protected boolean authenticated;protected String host;protected Session session;protected boolean sessionCreationEnabled;protected transient SecurityManager securityManager;public DelegatingSubject (SecurityManager securityManager) { this (null , false , null , null , securityManager); } public DelegatingSubject (PrincipalCollection principals, boolean authenticated, String host, Session session, SecurityManager securityManager) { this (principals, authenticated, host, session, true , securityManager); } public DelegatingSubject (PrincipalCollection principals, boolean authenticated, String host, Session session, boolean sessionCreationEnabled, SecurityManager securityManager) { if (securityManager == null ) { throw new IllegalArgumentException("SecurityManager argument cannot be null." ); } this .securityManager = securityManager; this .principals = principals; this .authenticated = authenticated; this .host = host; if (session != null ) { this .session = decorate(session); } this .sessionCreationEnabled = sessionCreationEnabled; }
在创建好Subject实例后,就可以调用login(AuthenticationToken token)方法操作登录了。具体分析如下:
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; 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 ; } }
整理一下Subject的登录流程:
需要清除runAs身份
委派给SecurityManager做登录认证操作
将认证成功的Subject信息设置到当前的Subject中去,标记已经被认证。
Shiro登出
Shiro登出过程很简单,代码如下,不用详细分析。
public void logout () { try { clearRunAsIdentitiesInternal(); this .securityManager.logout(this ); } finally { this .session = null ; this .principals = null ; this .authenticated = false ; } }
从上面的分析看,登录登出都是由SecurityManager来做的,后续我们会对SecurityManager进行分析。以及对授权的分析也在SecurityManager中进行说明。
runAs
runAs是指当前用户以一个给定的身份进行认证。给定的身份信息是存放在Session中的,也就是是如果要进行runAs操作,必须开启创建Session参数sessionCreationEnabled=true,否则会抛异常。
public void runAs (PrincipalCollection principals) { if (!hasPrincipals()) { String msg = "This subject does not yet have an identity. Assuming the identity of another " + "Subject is only allowed for Subjects with an existing identity. Try logging this subject in " + "first, or using the " + Subject.Builder.class.getName() + " to build ad hoc Subject instances " + "with identities as necessary." ; throw new IllegalStateException(msg); } pushIdentity(principals); } public boolean isRunAs () { List<PrincipalCollection> stack = getRunAsPrincipalsStack(); return !CollectionUtils.isEmpty(stack); } public PrincipalCollection getPreviousPrincipals () { PrincipalCollection previousPrincipals = null ; List<PrincipalCollection> stack = getRunAsPrincipalsStack(); int stackSize = stack != null ? stack.size() : 0 ; if (stackSize > 0 ) { if (stackSize == 1 ) { previousPrincipals = this .principals; } else { assert stack != null ; previousPrincipals = stack.get(1 ); } } return previousPrincipals; } public PrincipalCollection releaseRunAs () { return popIdentity(); } private List<PrincipalCollection> getRunAsPrincipalsStack () { Session session = getSession(false ); if (session != null ) { return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY); } return null ; } private void clearRunAsIdentities () { Session session = getSession(false ); if (session != null ) { session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY); } } private void pushIdentity (PrincipalCollection principals) throws NullPointerException { if (CollectionUtils.isEmpty(principals)) { String msg = "Specified Subject principals cannot be null or empty for 'run as' functionality." ; throw new NullPointerException(msg); } List<PrincipalCollection> stack = getRunAsPrincipalsStack(); if (stack == null ) { stack = new CopyOnWriteArrayList<PrincipalCollection>(); } stack.add(0 , principals); Session session = getSession(); session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack); } private PrincipalCollection popIdentity () { PrincipalCollection popped = null ; List<PrincipalCollection> stack = getRunAsPrincipalsStack(); if (!CollectionUtils.isEmpty(stack)) { popped = stack.remove(0 ); Session session; if (!CollectionUtils.isEmpty(stack)) { session = getSession(); session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack); } else { clearRunAsIdentities(); } } return popped; }