Shiro登錄中遇到了問題
記錄二次開發中遇到的問題, 如果系統學習Shiro, 推薦跟我學Shrio.
問題
- 項目是要將驗證從本地改為LDAP驗證, 但是因為jeecms的驗證和授權中, 用戶和角色以及權限的信息都來自本地, 大量的改造不適合.
- 域名的攔截, 因為IP被重置
HTTP 302
HTTP 302
Found
重定向狀態碼表明請求的資源被暫時的移動到了由Location
頭部指定的 URL 上。就是請求的資源被重定向到了新的地址。
要查看Filter是否對關鍵詞路徑進行了攔截。
Shiro
項目中用的jeecms, 安全驗證用的shiro,架構是Spring+SpringMVC,查看了一下web.xml,有關於shiro的filter,用的DelegatingFilterProxy類做代理,在SpringContext中配置Shiro的bean。
DelegatingFilterProxy
是對於servlet filter的代理,通過spring容器管理filter的生命周期,可以通過Spring容器注入需要的bean,以及讀取需要的配置文件。
【web.xml】
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
【shiroContext.xml】
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> </bean>
攔截器鏈
- 先執行Shiro自己的Filter鏈
- 執行Servlet的Filter鏈
自定義攔截器
- 可以根據自己的需求擴展攔截器,繼承要擴展的攔截器
- 對相應的方法進行擴展
- 在配置文件中修改對應的Filter類名
// 代碼參考<跟我學shiro>
public class MyAccessControlFilter extends AccessControlFilter { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { System.out.println("access allowed"); return true; } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { System.out.println("訪問拒絕也不自己處理,繼續攔截器鏈的執行"); return true; } }
[filters]
myFilter4=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAccessControlFilter [urls] /** 代碼參考<跟我學shiro>
執行過程
- ShiroFilter提供Shiro的入口
- AdviceFilter提供AOP的風格,分為preHandler,postHandle,afterCompletion三個方法針對AOP的前后增強(預處理和進行后處理)
- PathMatchingFilter匹配ANT風格的請求路徑,解析攔截器參數
- onPreHandle方法將路徑綁定參數配置傳給mappedValue,然后進行一些驗證
- AccessControlFilter訪問控制功能,如是否允許訪問,拒絕后如何處理等
- isAccessAllowed表示是否允許訪問
- onAccessDenied表示當拒絕訪問時是否處理了
- 如果擴展訪問控制可以繼承AccessControlFilter,如果添加通用數據可以繼承PathMatchingFilter
DelegatingSubject
shiro通過FormAuthenticationFilter來進行表單驗證,如果在驗證前要進行其他比如驗證碼的驗證,可以自定義一個繼承的子類.擴展對應的方法.
這里是對訪問控制的擴展,繼承關系為:FormAuthenticationFilter --> AuthenticatingFilter --> AuthenticationFilter --> AccessControlFilter.
可以在擴展的攔截器中重寫executeLogin方法,在里面擴展對訪問的控制
// 源碼
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response); if (token == null) { String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " + "must be created in order to execute a login attempt."; throw new IllegalStateException(msg); } try { Subject subject = getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } }
這里的執行是獲取當前的subject --> 根據請求創建token -->執行subject.login()方法 --> 返回成功或者失敗的處理
login()方法執行中,會進行一系列的處理,首先是交給DelegatingSubject,后面的沒有詳細研究,最后調用realm的doGetAuthenticationInfo方法獲取身份驗證的相關信息,通過SimpleAuthenticationInf返回AuthenticationInfo.
SimpleAuthenticationInfo
用來返回AuthenticationInfo信息,而AuthenticationInfo有兩個作用
- 如果Realm是AuthenticatingRealm子類,則提供給AuthenticatingRealm內部使用的CredentialsMatcher進行憑據驗證
- 提供給SecurityManager來創建Subject(提供身份信息)
credentialsMatcher
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher(); if (cm != null) { if (!cm.doCredentialsMatch(token, info)) { //not successful - throw an exception to indicate this: String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; throw new IncorrectCredentialsException(msg); } } else { throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " + "credentials during authentication. If you do not wish for credentials to be examined, you " + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); } }
CredentialsMatcher實現了token和info的憑證驗證.
授權問題
關於授權, 一開始進入了一個誤區, 認為在通過驗證之后會走授權的部分, 當頁面出現授權問題的時候, 發現打過的斷點沒有進去, 仔細看了下開濤大神的講解, 是當要訪問對應的資源時, 才會驗證當前用戶的授權情況.
如果想對資源設置權限, 可以應用Spring的AOP注解, 在對應的AnnotationController控制器(你的Controller)上加@RequiresRoles("admin"), 如果拋出異常, 可以用Spring的ExceptionHandler攔截處理.
判斷是否已通過驗證
// 獲取到當前subject Subject subject = SecurityUtils.getSubject(); // 判斷是否已通過驗證或者已經記住 if (subject.isAuthenticated()|| subject.isRemembered()) { ... }
問題1解決方式
不改造本地驗證和授權方式, 讓ldap用戶落地, 但是不包括密碼, 每次根據user和password去判斷驗證的方式和是否新增用戶.
在繼承了AuthorizingRealm的CmsAuthorizingRealm類中修改doGetAuthenticationInfo方法即可.
問題2解決方式
原因
因為登陸后Cookie里面設置了域名,如果當前訪問的域名和這個Cookie的域名不一致,將無法登陸成功。
但是域名和IP本地做了映射,並且在系統中做了配置,應該不是因為域名和IP而導致的創建了不同的session,更何況session的唯一標示是Jsessionid。
但是cookie是根據域名匹配的,如果域名被修改,那么就會導致后台拿不到JSESSIONID,也就會顯示當前未登錄。
而現在系統的部署,域名是虛擬生成的,任何訪問的客戶端,本地域名都不會被看到,推斷是因為這個原因導致。