您好,登錄后才能下訂單哦!
Subject是Shiro中十分重要的對象,可以簡單的理解為“當前用戶”。 首先來看下Subject的繼承關系
不論是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<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
public static Subject getSubject() {
return (Subject) get(SUBJECT_KEY);
}
public static Object get(Object key) {
if (log.isTraceEnabled()) {
String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
Object value = getValue(key);
if ((value != null) && log.isTraceEnabled()) {
String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
private static Object getValue(Object key) {
Map<Object, Object> perThreadResources = resources.get();
return perThreadResources != null ? perThreadResources.get(key) : null;
}
}
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文件中添加以下的配置
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
既然是在請求的過程中獲取并使用的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<String, Object> 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<String, ?> beans = factory.getBeans();
if (!CollectionUtils.isEmpty(beans)) {
this.objects.putAll(beans);
}
return wsm;
}
protected Map<String, Object> getDefaults() {
Map<String, Object> defaults = new HashMap<String, Object>();
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的代理一樣。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。