ApacheShiro源码解读之Subject的创建
Subject是Shiro中十分重要的对象,可以简单的理解为“当前用户”。 首先来看下Subject的继承关系
田东网站制作公司哪家好,找创新互联建站!从网页设计、网站建设、微信开发、APP开发、自适应网站建设等网站项目制作,到程序开发,运营维护。创新互联建站成立与2013年到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联建站。
不论是web应用程序还是普通应用程序,我们在某个方法里面都已通过以下方法来获取Subject对象并使用Session
Subject currentUser = org.apache.shiro.SecurityUtils.getSubject();
if ( !currentUser.isAuthenticated() ) {
UsernamePasswordToken token = new UsernamePasswordToken("user", "password");
token.setRememberMe(true);
try {
currentUser.login( token );
Session session = currentUser.getSession();
session.setAttribute( "key", "value" );
} catch ( UnknownAccountException uae ) {
//用户不存在
} catch ( IncorrectCredentialsException ice ) {
//密码错误
} catch ( LockedAccountException lae ) {
//用户被锁
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}
}
该SecurityUtils位于shiro-core.jar中
简单的两句代码就可以完成登录验证,也可以使用Session,看起来十分简单,可简单的背后可能又隐藏着许多复杂之处,接下来我们就来一探究竟。
/**
* Accesses the currently accessible {@code Subject} for the calling code depending on runtime environment.
*
* @since 0.2
*/
public abstract class SecurityUtils {
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
}
Subject首先直接从TreadContext里面直接获取,如果没有获取到则使用Subject的内部内来创建,然后再绑定到ThreadContext上。那么我们接着看看ThreadContext的定义
public abstract class ThreadContext {
public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
private static final ThreadLocal
ThreadContext里面维持着一个LocalThread对象,可见Subject是与当前线程相绑定的。
public interface Subject {
public static class Builder {
private final SubjectContext subjectContext;
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;
this.subjectContext = newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
"cannot be null.");
}
this.subjectContext.setSecurityManager(securityManager);
}
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
}
}
至此,Builder创建Subject的时候是委托给SecurityManager来创建的,而SecurityManager又是从SecurityUtils从返回。那么还得追溯下SecurityManager是如何被创建的才能进一步得知Subject的创建。而根据Subject的继承关系图可知,它本身只是个接口,那么其实现类又该对应的哪个,如何判定应该使用哪个?
对于在web环境中集成shrio时,一般是在web.xml文件中添加以下的配置
org.apache.shiro.web.env.EnvironmentLoaderListener
ShiroFilter
org.apache.shiro.web.servlet.ShiroFilter
ShiroFilter
/*
REQUEST
FORWARD
INCLUDE
ERROR
既然是在请求的过程中获取并使用的Subject, 那我们就来看看ShiroFilter类都包含了哪些内容,首先看看ShiroFilter的继承关系
public class ShiroFilter extends AbstractShiroFilter {
@Override
public void init() throws Exception {
WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());
setSecurityManager(env.getWebSecurityManager());
FilterChainResolver resolver = env.getFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
}
在这里之看到了init方法,看名字应该是初始化给Filter时候运行的,那么在何处调用的,我们继续看看他的父类
public abstract class AbstractFilter extends ServletContextSupport implements Filter {
public final void init(FilterConfig filterConfig) throws ServletException {
setFilterConfig(filterConfig);
try {
onFilterConfigSet();
} catch (Exception e) {
if (e instanceof ServletException) {
throw (ServletException) e;
} else {
if (log.isErrorEnabled()) {
log.error("Unable to start Filter: [" + e.getMessage() + "].", e);
}
throw new ServletException(e);
}
}
}
}
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
protected final void onFilterConfigSet() throws Exception {
//added in 1.2 for SHIRO-287:
applyStaticSecurityManagerEnabledConfig();
init(); //调用了子类【ShiroFilter】的init()方法(开始得到WebEnvironment等对象)
ensureSecurityManager(); //确认SecurityManager是否存在,不存在则创建默认的DefaultWebSecurityManager对象
//added in 1.2 for SHIRO-287:
if (isStaticSecurityManagerEnabled()) {
/*
注意:这里很重要,在WEB环境是不建议将SecurityManager对象保存在静态变量中的。。。
根据Filter配置的初始化参数判断是否要将SecurityManager通过SecurityUtils当做静态变量进行保存
*/
SecurityUtils.setSecurityManager(getSecurityManager());
}
}
}
可知AbstractFilter调用了AbstractShiroFilter,然后再调用了ShiroFilter的init方法。 init方法的目的就是为了获得WebEnvironment对象,其WebUtils里的代码就简单了,就是从ServletContext中直接获取WebEnvironment对象,如果为空,则会抛出异常。
public class WebUtils {
public static WebEnvironment getRequiredWebEnvironment(ServletContext sc)
throws IllegalStateException {
WebEnvironment we = getWebEnvironment(sc);
if (we == null) {
throw new IllegalStateException("No WebEnvironment found: no EnvironmentLoaderListener registered?");
}
return we;
}
public static WebEnvironment getWebEnvironment(ServletContext sc) {
return getWebEnvironment(sc, EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY);
}
public static WebEnvironment getWebEnvironment(ServletContext sc, String attrName) {
if (sc == null) {
throw new IllegalArgumentException("ServletContext argument must not be null.");
}
Object attr = sc.getAttribute(attrName);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (attr instanceof Exception) {
throw new IllegalStateException((Exception) attr);
}
if (!(attr instanceof WebEnvironment)) {
throw new IllegalStateException("Context attribute is not of type WebEnvironment: " + attr);
}
return (WebEnvironment) attr;
}
}
接着我们看下WebEnvironment的定义:
public interface Environment {
/**
* Returns the application's {@code SecurityManager} instance.
*
* @return the application's {@code SecurityManager} instance.
*/
SecurityManager getSecurityManager();
}
public interface WebEnvironment extends Environment {
/**
* Returns the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
* is not available.
*
* @return the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
* is not available.
*/
FilterChainResolver getFilterChainResolver();
/**
* Returns the {@code ServletContext} associated with this {@code WebEnvironment} instance. A web application
* typically only has a single {@code WebEnvironment} associated with its {@code ServletContext}.
*
* @return the {@code ServletContext} associated with this {@code WebEnvironment} instance.
*/
ServletContext getServletContext();
/**
* Returns the web application's security manager instance.
*
* @return the web application's security manager instance.
*/
WebSecurityManager getWebSecurityManager();
}
在WebEnvironment里面直接保存了全局唯一的SecurityManager对象。接下来我们需要追踪SecurityManager对象的创建过程。我们就得回到 到以下对象上
org.apache.shiro.web.env.EnvironmentLoaderListener
public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener {
/**
* Initializes the Shiro {@code WebEnvironment} and binds it to the {@code ServletContext} at application
* startup for future reference.
*
* @param sce the ServletContextEvent triggered upon application startup
*/
public void contextInitialized(ServletContextEvent sce) {
initEnvironment(sce.getServletContext());
}
/**
* Destroys any previously created/bound {@code WebEnvironment} instance created by
* the {@link #contextInitialized(javax.servlet.ServletContextEvent)} method.
*
* @param sce the ServletContextEvent triggered upon application shutdown
*/
public void contextDestroyed(ServletContextEvent sce) {
destroyEnvironment(sce.getServletContext());
}
}
public class EnvironmentLoader {
public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY";
public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) {
String msg = "There is already a Shiro environment associated with the current ServletContext. " +
"Check if you have multiple EnvironmentLoader* definitions in your web.xml!";
throw new IllegalStateException(msg);
}
servletContext.log("Initializing Shiro environment");
log.info("Starting Shiro environment initialization.");
long startTime = System.currentTimeMillis();
try {
WebEnvironment environment = createEnvironment(servletContext);
servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY,environment);
log.debug("Published WebEnvironment as ServletContext attribute with name [{}]",
ENVIRONMENT_ATTRIBUTE_KEY);
if (log.isInfoEnabled()) {
long elapsed = System.currentTimeMillis() - startTime;
log.info("Shiro environment initialized in {} ms.", elapsed);
}
return environment;
} catch (RuntimeException ex) {
log.error("Shiro environment initialization failed", ex);
servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex);
throw ex;
} catch (Error err) {
log.error("Shiro environment initialization failed", err);
servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err);
throw err;
}
}
protected WebEnvironment createEnvironment(ServletContext sc) {
WebEnvironment webEnvironment = determineWebEnvironment(sc);
if (!MutableWebEnvironment.class.isInstance(webEnvironment)) {
throw new ConfigurationException("Custom WebEnvironment class [" + webEnvironment.getClass().getName() +
"] is not of required type [" + MutableWebEnvironment.class.getName() + "]");
}
String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
boolean configSpecified = StringUtils.hasText(configLocations);
if (configSpecified && !(ResourceConfigurable.class.isInstance(webEnvironment))) {
String msg = "WebEnvironment class [" + webEnvironment.getClass().getName() + "] does not implement the " +
ResourceConfigurable.class.getName() + "interface. This is required to accept any " +
"configured " + CONFIG_LOCATIONS_PARAM + "value(s).";
throw new ConfigurationException(msg);
}
MutableWebEnvironment environment = (MutableWebEnvironment) webEnvironment;
environment.setServletContext(sc);
if (configSpecified && (environment instanceof ResourceConfigurable)) {
((ResourceConfigurable) environment).setConfigLocations(configLocations);
}
customizeEnvironment(environment);
LifecycleUtils.init(environment); //注意:这了会调用environment的init方法来初始化environment
return environment;
}
protected WebEnvironment determineWebEnvironment(ServletContext servletContext) {
Class extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext);
WebEnvironment webEnvironment = null;
// try service loader next
if (webEnvironmentClass == null) {
webEnvironment = webEnvironmentFromServiceLoader();
}
// if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default
if (webEnvironmentClass == null && webEnvironment == null) {
webEnvironmentClass = getDefaultWebEnvironmentClass();
}
// at this point, we anything is set for the webEnvironmentClass, load it.
if (webEnvironmentClass != null) {
webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass);
}
return webEnvironment;
}
protected Class extends WebEnvironment> getDefaultWebEnvironmentClass() {
return IniWebEnvironment.class;
}
}
经过一路的奔波,最终创建了默认的Environment对象IniWebEnvironment。
接着我们再看看IniWebEnvironment对象初始化都做了些啥事
public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
public static final String FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver";
private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class);
/**
* The Ini that configures this WebEnvironment instance.
*/
private Ini ini;
private WebIniSecurityManagerFactory factory;
public IniWebEnvironment() {
factory = new WebIniSecurityManagerFactory();
}
public void init() {
//解析指定或默认位置的配置文件并生成对应的Ini对象
setIni(parseConfig());
configure();
}
protected void configure() {
this.objects.clear();
WebSecurityManager securityManager = createWebSecurityManager();
setWebSecurityManager(securityManager);
FilterChainResolver resolver = createFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
/*
将Ini对象传递给WebIniSecurityManagerFactory,并构建SecurityManager对象
*/
protected WebSecurityManager createWebSecurityManager() {
Ini ini = getIni();
if (!CollectionUtils.isEmpty(ini)) {
factory.setIni(ini);
}
Map defaults = getDefaults();
if (!CollectionUtils.isEmpty(defaults)) {
factory.setDefaults(defaults);
}
WebSecurityManager wsm = (WebSecurityManager)factory.getInstance();
//SHIRO-306 - get beans after they've been created (the call was before the factory.getInstance() call,
//which always returned null.
Map beans = factory.getBeans();
if (!CollectionUtils.isEmpty(beans)) {
this.objects.putAll(beans);
}
return wsm;
}
protected Map getDefaults() {
Map defaults = new HashMap();
defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory());
return defaults;
}
}
接着继续跟踪WebIniSecurityManagerFactory的执行
public class WebIniSecurityManagerFactory extends IniSecurityManagerFactory {
protected SecurityManager createDefaultInstance() {
return new DefaultWebSecurityManager();
}
}
附SecurityManager继承关系,后面再详细解析SecurityManager
HTTP请求处理过程
1,每个http请求都被ShoriFilter拦截进行处理
2,将SecurityManager对象和包装后的Request和Response作为构造参数创建WebSubject.Builder实例,并调用buildWebSubject方法创建Subject
3,在构造方法中创新新的SubjectContext实例,并将SecurityManager保存到SubjectContext实例中
4,将Request和Response也添加到SubjectContext中保存
5,将subjectContext作为参数,调用SecurityManager的createSubject方法创建Subject对象
6,将SubjectContext作为参数,调用SubjectFactory【DefaultSubjectFactory】的createSubject方法创建Subject
7,接着取出SubjectContext一路收集来的数据来构建DelegatingSubject对象并返回。
8,当调用Subject的getSession方法的时候,如果Session不存在,则首先创建一个新的DefaultSessionContext实例并设置host值【可能是空】
9,将sessionContext对象作为参数调用securityManager的start方法来创建Session
10,从SessionContext中取出HttpServletRequest,并调用HttpServletRequest的getSession方法来获取HttpSession,同时从SessionContext中取出host,使用这两个值作为构造函数的参数实例化HttpServletSession类。
11,到此,Session的创建过程结束,此时的HttpServletSession纯粹只是HttpSession的代理一样。
标题名称:ApacheShiro源码解读之Subject的创建
文章转载:http://pwwzsj.com/article/ggoipc.html