shiro安全控制目錄
一般在登錄網(wǎng)站上含有這么一個按鈕(X天自動登錄),在一次驗證成功之后南蓬。用戶后續(xù)X天內(nèi)在同一個瀏覽器繼續(xù)訪問該頁面時纺非,不用經(jīng)過認證,便可訪問數(shù)據(jù)赘方。
代碼配置
SecurityManager作為Shiro核心烧颖,協(xié)調(diào)管理著rememberManager組件進行安全控制,認證流程如圖1所示:
1.1 securityManager配置
在SecurityManager中配置rememberManager窄陡,如代碼1所示炕淮。
代碼1:Spring中rememberManager的配置
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--作用:授權認證,此處為單個realm泳梆,若想配置多個,可使用realms參數(shù)-->
<property name="realm" ref="myRealm"/>
<!--建議關閉榜掌,配合/**=user使用時优妙,可未經(jīng)授權登錄系統(tǒng)-->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<!-- 默認記住7天(單位:秒) -->
<property name="maxAge" value="60"/>
</bean>
<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('haskjagsjazhjfdsajasdkjsa==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>
1.2 業(yè)務代碼
設置了token.setRememberMe(true);
屬性,如代碼2所示憎账。
代碼2:業(yè)務代碼中開啟remember功能
@RequestMapping("/login")
public String login(@Param("username") String username,@Param("password") String password) {
UsernamePasswordToken token = new UsernamePasswordToken();
token.setUsername(username);
token.setPassword(password.toCharArray());
//開啟rememberMe功能
token.setRememberMe(true);
//調(diào)用驗證
Subject subject = SecurityUtils.getSubject();
subject.login(token);
}
1.3 shiro Filter配置
shiro覺得不能把rememberMe等同于完全認證套硼,這樣是很不安全的,即若用戶的cookie中rememberMe信息泄露胞皱,那么可能會造成安全漏洞邪意。所有針對于rememberMe這個功能,shiro提供了兩種Filter級別反砌,代碼3所示雾鬼。
代碼3:Shiro Filter的兩種配置
//只有經(jīng)過Realm認證后才不會被攔截
/** = authc
//Realm認證或開啟RememberMe均不會被攔截
/** = user
文章參考:shiro(7)-shiroFilter(url層次認證/權限控制)
若想使用rememberMe功能,shiro Filter必須設置為/** = user宴树。
經(jīng)過認證后策菜,請求中的cookie信息,如圖2所示:
源碼分析
2.1 將rememberMe保存到cookie中
在rememberManager中對principals進行加密(這個值就是自定義Realm返回的SimpleAuthenticationInfo
對象的屬性)
源碼:org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = serialize(principals);
if (getCipherService() != null) {
//配置文件傳入的私鑰和principals進行加密
bytes = encrypt(bytes);
}
return bytes;
}
將加密串設置到cookie中
源碼:org.apache.shiro.web.mgt.CookieRememberMeManager#rememberSerializedIdentity
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
//省略部分代碼
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
//設置cookie中的rememberMe參數(shù)
cookie.setValue(base64);
cookie.saveTo(request, response);
}
2.2 驗證cookie中的rememberMe字段
獲取cookie的字段酒贬,構建subject對象
請求上送時又憨,在Shiro Filter中創(chuàng)建Subject對象,創(chuàng)建Subject對象后锭吨,將其加入到ThreadLocal中蠢莺,而后再獲取subject,只需要在線程上下文獲取即可零如。
源碼:org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
//創(chuàng)建Subject對象(此時躏将,已經(jīng)獲取到rememberMe上送的原始的principal對象)
final Subject subject = createSubject(request, response);
}
//省略部分源碼
}
在createSubject(request, response)方法實現(xiàn)中锄弱,實際上獲取cookie中上送的rememberMe字段(源碼參看org.apache.shiro.web.servlet.SimpleCookie#readValue
),然后將其解密獲取到原始principal對象耸携。
源碼:org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals
public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
PrincipalCollection principals = null;
try {
//在SimpleCookie中獲取到rememberMe字段
byte[] bytes = getRememberedSerializedIdentity(subjectContext);
//SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
if (bytes != null && bytes.length > 0) {
//進行解密棵癣,獲取到原生的principal對象
principals = convertBytesToPrincipals(bytes, subjectContext);
}
} catch (RuntimeException re) {
principals = onRememberedPrincipalFailure(re, subjectContext);
}
return principals;
}
此時之后,執(zhí)行完畢createSubject
方法夺衍,將subject放入到線程的上下文中狈谊。
shiro Filter過濾請求
使用org.apache.shiro.web.filter.authc.UserFilter
過濾器,若是含有subject對象中含有principal對象沟沙,則放行河劝。那么我們只要cookie中上送正確的rememberMe字段,便可以通過認證矛紫。
源碼:org.apache.shiro.web.filter.authc.UserFilter#isAccessAllowed
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (isLoginRequest(request, response)) {
return true;
} else {
Subject subject = getSubject(request, response);
// If principal is not null, then the user is known and should be allowed access.
return subject.getPrincipal() != null;
}
}
總結
shiro開啟rememberMe功能后赎瞎,登錄成功后,會單獨返回客戶端一個cookie字段(rememberMe)颊咬,保存著用戶信息(principals對象)务甥。當服務器shiro Filter認證過濾器為/**=user,用戶請求的cookie中攜帶rememberMe字段喳篇。服務器并且可以解析rememberMe的加密串敞临,那么則允許請求訪問服務器數(shù)據(jù)。
這樣可能會導致安全問題麸澜。故針對一些重要的數(shù)據(jù)挺尿,shiro Filter認證過濾器可以設置為/**=authc。