Subject几乎代理了所有有关用户的Security的操作。如:验证、授权、session管理..,它也可以说是用户安全相关安全操作的门面。通常我们可以通过SecurityUtils.getSubject()来获取一个Subject。
我们知道在基于WEB的应用中,shiro在生成Session后,然后把SessionID保存在客户端浏览器的cookie value中。当请求再次过来时,根据cookie的名字就能找到cookie然后获取shiro所设置的sessionID来找到对应的session了。
在基于WEB的应用中,那么Subject呢?为什么Subject可以代表一个特定用户的安全操作?shiro是如何确定哪个Subject对应哪个用户的?看下面..
1.每次请求过来时,AbstractShiroFilter的doFilterInternal中,会调用createSubject方法根据参数sessionManager、request、response获取一个Subject
2.然后调用DefaultSecurityManager的createSubject方法构造一个Subject
1)准备WebSubjectContext所需的一些数据。如:session(根据上面的方法找到对应的session)、securityManager、principle..
2)调用DefaultWebSubjectFactory根据WebSubjectContext来创建一个Subject。
3)保存当前subject的最新状态。如:是否验证,和principle
1.Subject
public interface Subject {
//获取principle
Object getPrincipal();
//获取PrincipalCollection (多个realm的principle集合)
PrincipalCollection getPrincipals();
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;
//执行登录操作,如果登录失败,则抛出异常,如果无任何返回则认为登录成功
void login(AuthenticationToken token) throws AuthenticationException;
//判断是否已登录成功,如果当前session中该用户已登录则认为已登录,记住密码不代表登录成功
boolean isAuthenticated();
/**
如果当前用户的principle不为null且isAuthenticated()为false,则返回true,否则返回false。
即isRemembered()为true,并不代表该用户的真实身份,只是它曾经登录过,选择了记住我功能把身份保存在了cookie中。
但是他并不存活在当前的session中。如果有攻击者伪造了用户的cookie身份。那么shiro会认为他伪造的用户的isRemembered为true。
isRemembered VS isAuthenticated
如果只是访问一些非敏感类的信息,那么我们可以设置isRemembered 就可以了。如果涉及敏感信息则设置权限要求为isAuthenticated
**/
boolean isRemembered();
//获取一个Session,如果不存在则创建
Session getSession();
//如果session不存在是否创建session,true创建,false不创建
Session getSession(boolean create);
//用户执行退出,且删除和该用户绑定的相关的信息
void logout();
//绑定Runnable到当前的Subject中,在当前执行
void execute(Runnable runnable);
//绑定Callable到当前的Subject中,在当前执行
<V> V execute(Callable<V> callable) throws ExecutionException;
//返回一个封装过的Callable,然后用户可以选择开启一个新线程执行
<V> Callable<V> associateWith(Callable<V> callable);
//返回一个封装过的Runnable,然后用户可以选择开启一个新线程执行
Runnable associateWith(Runnable runnable);
//如果该用户的principle存在,允许该用户假定为其他用户的身份进行操作。
void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;
//判断用户是否是假定身份
boolean isRunAs();
//获取假定身先前的真实身份
PrincipalCollection getPreviousPrincipals();
//释放假定身份改为真实身份
PrincipalCollection releaseRunAs();
/**
该Builder使用了建造者模式(Builder design pattern),很容易的构造了Subject。
如果要构造一个匿名的Subject(即不指向特定的用户),可通过new Subject.Builder().buildSubject().该方法默认绑定当前的SecurityManager。
如果要构造一个根据SessionID来指向用户的Subject,则通过new Subject.Builder().sessionId(sessionId).buildSubject()
如果要构造一个根据principle来指向用户的Subject,则通过new Subject.Builder().principals(principals).buildSubject()
通过上面几个构造Subject的方法,大家也可以知道建造者模式的好处了
1.当构造复杂的对象的算法独立于对象的组成部分以及他们的装配时(SecurityManager.createSubject(this.subjectContext)是真正构造Subject的算法,
SecurityManager的实现类DefaultWebSecurityManager可根据SubjectContext的子类WebSubjectContext来根据特定需要的数据构造Subject)
2.当构造过程必须允许构造的对象有不同的表现时(上面几个构造方法的体现)
关于建造者模式,这里不多说,有需要的自己去百度
**/
public static class Builder {
//构造Subject需要的数据 -》SubjectContext
private final SubjectContext subjectContext;
//SecurityManager.createSubject(SubjectContext)真正创建Subject的方法
private final SecurityManager securityManager;
public Builder() {
this(SecurityUtils.getSecurityManager());
}
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
}
this.securityManager = securityManager;
//获取SubjectContext
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
this.subjectContext.setSecurityManager(securityManager);
}
//获取一个新创建的DefaultSubjectContext,该方法的提供有利于子类可以重写,提供不同SubjectContext实现类
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
protected SubjectContext getSubjectContext() {
return this.subjectContext;
}
//设置subjectContext的属性SessionId,该方法有利于shiro根据SessionID查找该用户对应的session
public Builder sessionId(Serializable sessionId) {
if (sessionId != null) {
this.subjectContext.setSessionId(sessionId);
}
return this;
}
public Builder host(String host) {
if (StringUtils.hasText(host)) {
this.subjectContext.setHost(host);
}
return this;
}
//通常使用sessionId()比较多
public Builder session(Session session) {
if (session != null) {
this.subjectContext.setSession(session);
}
return this;
}
public Builder principals(PrincipalCollection principals) {
if (!CollectionUtils.isEmpty(principals)) {
this.subjectContext.setPrincipals(principals);
}
return this;
}
//如果获取不到Session,该参数决定是否要创建个新的Session
public Builder sessionCreationEnabled(boolean enabled) {
this.subjectContext.setSessionCreationEnabled(enabled);
return this;
}
//该参数决定Subject是否已验证登陆
public Builder authenticated(boolean authenticated) {
this.subjectContext.setAuthenticated(authenticated);
return this;
}
public Builder contextAttribute(String attributeKey, Object attributeValue) {
if (attributeKey == null) {
String msg = "Subject context map key cannot be null.";
throw new IllegalArgumentException(msg);
}
if (attributeValue == null) {
this.subjectContext.remove(attributeKey);
} else {
this.subjectContext.put(attributeKey, attributeValue);
}
return this;
}
//securityManager根据subjectContext来构造Subject返回
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
}
}
2.DelegatingSubject
/**
DelegatingSubject实际上相当于SecurityManager的代理,因为它的权限相关的方法都是调用SecurityManager的相关方法的。
且该实现类除了并没有真正存储任何关于特定用户的状态,这有利于application实现无状态架构。
/**
public class DelegatingSubject implements Subject {
private static final Logger log = LoggerFactory.getLogger(DelegatingSubject.class);
private static final String RUN_AS_PRINCIPALS_SESSION_KEY =
DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";
protected PrincipalCollection principals;
//是否已验证登陆
protected boolean authenticated;
protected String host;
protected Session session;
/**
* @since 1.2
*/
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);
}
//since 1.2
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内的Session
protected Session decorate(Session session) {
if (session == null) {
throw new IllegalArgumentException("session cannot be null");
}
return new StoppingAwareProxiedSession(session, this);
}
public SecurityManager getSecurityManager() {
return securityManager;
}
protected boolean hasPrincipals() {
return !CollectionUtils.isEmpty(getPrincipals());
}
public String getHost() {
return this.host;
}
private Object getPrimaryPrincipal(PrincipalCollection principals) {
if (!CollectionUtils.isEmpty(principals)) {
return principals.getPrimaryPrincipal();
}
return null;
}
public Object getPrincipal() {
return getPrimaryPrincipal(getPrincipals());
}
//如果假定身份不为空,则获取假定身份的principle,否则的话RunAs还有什么意义?
public PrincipalCollection getPrincipals() {
List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
}
public boolean isPermitted(String permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
public boolean isPermitted(Permission permission) {
return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
public boolean[] isPermitted(String... permissions) {
if (hasPrincipals()) {
return securityManager.isPermitted(getPrincipals(), permissions);
} else {
return new boolean[permissions.length];
}
}
public boolean[] isPermitted(List<Permission> permissions) {
if (hasPrincipals()) {
return securityManager.isPermitted(getPrincipals(), permissions);
} else {
return new boolean[permissions.size()];
}
}
public boolean isPermittedAll(String... permissions) {
return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
}
public boolean isPermittedAll(Collection<Permission> permissions) {
return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
}
//判断principle是否为空,为空则抛出异常
protected void assertAuthzCheckPossible() throws AuthorizationException {
if (!hasPrincipals()) {
String msg = "This subject is anonymous - it does not have any identifying principals and " +
"authorization operations require an identity to check against. A Subject instance will " +
"acquire these identifying principals automatically after a successful login is performed " +
"be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
"functionality is enabled by the SecurityManager. This exception can also occur when a " +
"previously logged-in Subject has logged out which " +
"makes it anonymous again. Because an identity is currently not known due to any of these " +
"conditions, authorization is denied.";
throw new UnauthenticatedException(msg);
}
}
public void checkPermission(String permission) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkPermission(getPrincipals(), permission);
}
public void checkPermission(Permission permission) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkPermission(getPrincipals(), permission);
}
public void checkPermissions(String... permissions) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkPermissions(getPrincipals(), permissions);
}
public void checkPermissions(Collection<Permission> permissions) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkPermissions(getPrincipals(), permissions);
}
public boolean hasRole(String roleIdentifier) {
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
public boolean[] hasRoles(List<String> roleIdentifiers) {
if (hasPrincipals()) {
return securityManager.hasRoles(getPrincipals(), roleIdentifiers);
} else {
return new boolean[roleIdentifiers.size()];
}
}
public boolean hasAllRoles(Collection<String> roleIdentifiers) {
return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers);
}
public void checkRole(String role) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkRole(getPrincipals(), role);
}
public void checkRoles(String... roleIdentifiers) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkRoles(getPrincipals(), roleIdentifiers);
}
public void checkRoles(Collection<String> roles) throws AuthorizationException {
assertAuthzCheckPossible();
securityManager.checkRoles(getPrincipals(), roles);
}
//用户登陆验证方法
public void login(AuthenticationToken token) throws AuthenticationException {
//先删除先前的假定身份,否则的话,当用户获取当前身份时,获取到的还是假定身份
clearRunAsIdentitiesInternal();
//调用securityManager.login执行真正的登陆验证方法,且返回个subject
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
//如果subject是DelegatingSubject的子类,则设置host和principals
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) {
//装饰session
this.session = decorate(session);
} else {
this.session = null;
}
}
public boolean isAuthenticated() {
return authenticated;
}
public boolean isRemembered() {
PrincipalCollection principals = getPrincipals();
return principals != null && !principals.isEmpty() && !isAuthenticated();
}
protected boolean isSessionCreationEnabled() {
return this.sessionCreationEnabled;
}
public Session getSession() {
return getSession(true);
}
public Session getSession(boolean create) {
if (log.isTraceEnabled()) {
log.trace("attempting to get session; create = " + create +
"; session is null = " + (this.session == null) +
"; session has id = " + (this.session != null && session.getId() != null));
}
if (this.session == null && create) {
//added in 1.2:
if (!isSessionCreationEnabled()) {
String msg = "Session creation has been disabled for the current subject. This exception indicates " +
"that there is either a programming error (using a session when it should never be " +
"used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
"for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " +
"for more.";
throw new DisabledSessionException(msg);
}
log.trace("Starting session for host {}", getHost());
SessionContext sessionContext = createSessionContext();
Session session = this.securityManager.start(sessionContext);
this.session = decorate(session);
}
return this.session;
}
protected SessionContext createSessionContext() {
SessionContext sessionContext = new DefaultSessionContext();
if (StringUtils.hasText(host)) {
sessionContext.setHost(host);
}
return sessionContext;
}
private void clearRunAsIdentitiesInternal() {
//try/catch added for SHIRO-298
try {
clearRunAsIdentities();
} catch (SessionException se) {
log.debug("Encountered session exception trying to clear 'runAs' identities during logout. This " +
"can generally safely be ignored.", se);
}
}
//用户退出
public void logout() {
try {
clearRunAsIdentitiesInternal();
this.securityManager.logout(this);
} finally {
this.session = null;
this.principals = null;
this.authenticated = false;
}
}
private void sessionStopped() {
this.session = null;
}
public <V> V execute(Callable<V> callable) throws ExecutionException {
Callable<V> associated = associateWith(callable);
try {
return associated.call();
} catch (Throwable t) {
throw new ExecutionException(t);
}
}
public void execute(Runnable runnable) {
Runnable associated = associateWith(runnable);
associated.run();
}
public <V> Callable<V> associateWith(Callable<V> callable) {
return new SubjectCallable<V>(this, callable);
}
public Runnable associateWith(Runnable runnable) {
if (runnable instanceof Thread) {
String msg = "This implementation does not support Thread arguments because of JDK ThreadLocal " +
"inheritance mechanisms required by Shiro. Instead, the method argument should be a non-Thread " +
"Runnable and the return value from this method can then be given to an ExecutorService or " +
"another Thread.";
throw new UnsupportedOperationException(msg);
}
return new SubjectRunnable(this, runnable);
}
//装饰Session的stop方法
private class StoppingAwareProxiedSession extends ProxiedSession {
private final DelegatingSubject owner;
private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
super(target);
owner = owningSubject;
}
public void stop() throws InvalidSessionException {
super.stop();
owner.sessionStopped();
}
}
//让该用户假设principals的身份
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);
}
//判断当前subject是否使用的是假设身份
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 {
//always get the one behind the current:
assert stack != null;
previousPrincipals = stack.get(1);
}
}
return previousPrincipals;
}
//释放当前的假设身份
public PrincipalCollection releaseRunAs() {
return popIdentity();
}
@SuppressWarnings("unchecked")
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);
}
}
//压入假设的身份principals到session的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);
}
//session的RUN_AS_PRINCIPALS_SESSION_KEY弹栈假设身份
private PrincipalCollection popIdentity() {
PrincipalCollection popped = null;
List<PrincipalCollection> stack = getRunAsPrincipalsStack();
if (!CollectionUtils.isEmpty(stack)) {
popped = stack.remove(0);
Session session;
if (!CollectionUtils.isEmpty(stack)) {
//persist the changed stack to the session
session = getSession();
session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
} else {
//stack is empty, remove it from the session:
clearRunAsIdentities();
}
}
return popped;
}
}
3.WebDelegatingSubject
//WebDelegatingSubject实现了servlet request\response的支持。
public class WebDelegatingSubject extends DelegatingSubject implements WebSubject {
private static final long serialVersionUID = -1655724323350159250L;
private final ServletRequest servletRequest;
private final ServletResponse servletResponse;
public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
String host, Session session,
ServletRequest request, ServletResponse response,
SecurityManager securityManager) {
this(principals, authenticated, host, session, true, request, response, securityManager);
}
//since 1.2
public WebDelegatingSubject(PrincipalCollection principals, boolean authenticated,
String host, Session session, boolean sessionEnabled,
ServletRequest request, ServletResponse response,
SecurityManager securityManager) {
super(principals, authenticated, host, session, sessionEnabled, securityManager);
this.servletRequest = request;
this.servletResponse = response;
}
public ServletRequest getServletRequest() {
return servletRequest;
}
public ServletResponse getServletResponse() {
return servletResponse;
}
//重写父类的是否支持创建新session,如果当前session不存在
@Override
protected boolean isSessionCreationEnabled() {
boolean enabled = super.isSessionCreationEnabled();
return enabled && WebUtils._isSessionCreationEnabled(this);
}
//重写父类的createSessionContext,返回一个DefaultWebSessionContext
@Override
protected SessionContext createSessionContext() {
WebSessionContext wsc = new DefaultWebSessionContext();
String host = getHost();
if (StringUtils.hasText(host)) {
wsc.setHost(host);
}
wsc.setServletRequest(this.servletRequest);
wsc.setServletResponse(this.servletResponse);
return wsc;
}
}