準備maven shiro架包:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
1.一:首先創建spring的配置文件,位置都在resource中(非maven的項目可以放到classpath或者是WEB-INF下面,只要保證最後編譯之後能在classpath下即可),配置文件爲spring-context.xml.
二:創建Apache Shiro的配置文件,名字是spring-context-shiro.xml,我們只需要和spring的配置文件放在同一級就可以了。
三:還有一個配置文件是springmvc的,配置文件是spring-mvc。前面兩個文件都是以spring-context*開頭是有原因的,因爲這樣我們就可以在web.xml中設置配置文件的時候,直接使用通配符掃描前兩個但是又可以不掃描springmvc的配置文件
這是在web.xml裏面配置:
<!-- 配置spring容器的路徑 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:/spring-context-*.xml</param-value>
</context-param>
<!-- 對spring開始監聽 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
除了spring的配置,還有一個配置是非常重要的:shiroFilter。對於初次配置shiro的同學經常遇到一個問題:問題大概講的是shiroFilter找不到,但是我們明明在web.xml和spring-context-shiro配置文件裏面配置了呀,怎麼回事?這是因爲這個shiroFilter名字兩邊需要一致!!!(是不是很坑,但是其實是可以配置的,只是一般人不知道,這個後面講)
<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>
除了在web.xml中設置spring和spring-shiro配置文件位置之外,我們還需要在web.xml中設置spring-mvc的位置:
<!-- MVC Servlet
設置springmvc的Servlet
-->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在spring-context配置文件中,還有一個是需要配置-cacheManager,因爲shiro的session是自己實現的,所以我們還需要一個緩存框架,所以在spring的配置文件一定要注意配置哦,用的是ehcache
<!-- 緩存 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:${ehcache.file}"></property>
</bean>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.9</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"
default-lazy-init="true">
<description>Shiro Configuration</description>
<!-- 加載配置屬性文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:jeesite.properties" />
<!-- Shiro權限過濾過濾器定義 -->
<bean name="shiroFilterChainDefinitions" class="java.lang.String">
<constructor-arg>
<value>
/static/** = anon
/userfiles/** = anon
${adminPath}/cas = cas
${adminPath}/login = authc
${adminPath}/logout = logout
${adminPath}/** = user
/act/editor/** = user
/ReportServer/** = user
</value>
</constructor-arg>
</bean>
<!-- 安全認證過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" /><!--
<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->
<property name="loginUrl" value="${adminPath}/login" />
<property name="successUrl" value="${adminPath}?login" />
<property name="filters">
<map>
<entry key="cas" value-ref="casFilter"/>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<ref bean="shiroFilterChainDefinitions"/>
</property>
</bean>
<!-- CAS認證過濾器 -->
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<property name="failureUrl" value="${adminPath}/login"/>
</bean>
<!-- 定義Shiro安全管理配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="systemAuthorizingRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<!-- 自定義會話管理配置 -->
<bean id="sessionManager" class="com.thinkgem.jeesite.common.security.shiro.session.SessionManager">
<property name="sessionDAO" ref="sessionDAO"/>
<!-- 會話超時時間,單位:毫秒 -->
<property name="globalSessionTimeout" value="${session.sessionTimeout}"/>
<!-- 定時清理失效會話, 清理用戶直接關閉瀏覽器造成的孤立會話 -->
<property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/>
<!-- <property name="sessionValidationSchedulerEnabled" value="false"/> -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<property name="sessionIdCookieEnabled" value="true"/>
</bean>
<!-- 指定本系統SESSIONID, 默認爲: JSESSIONID 問題: 與SERVLET容器名衝突, 如JETTY, TOMCAT 等默認JSESSIONID,
當跳出SHIRO SERVLET時如ERROR-PAGE容器會爲JSESSIONID重新分配值導致登錄會話丟失! -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg name="name" value="jeesite.session.id"/>
</bean>
<!-- 自定義Session存儲容器 -->
<!-- <bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.JedisSessionDAO"> -->
<!-- <property name="sessionIdGenerator" ref="idGen" /> -->
<!-- <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" /> -->
<!-- </bean> -->
<bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.CacheSessionDAO">
<property name="sessionIdGenerator" ref="idGen" />
<property name="activeSessionsCacheName" value="activeSessionsCache" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<!-- 自定義系統緩存管理器-->
<!-- <bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.JedisCacheManager"> -->
<!-- <property name="cacheKeyPrefix" value="${redis.keyPrefix}_cache_" /> -->
<!-- </bean> -->
<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 保證實現了Shiro內部lifecycle函數的bean執行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- AOP式方法級權限檢查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
這裏從上往下進行解釋:
1.shiroFilterChainDefinitions
可以看到類型是String,String內部的各個字符串是使用"\n\t"進行換行。這裏的每一行代表了一個路由,而後面的anno,user等等,也就是相對應的Filter(這塊我們是可以自己定義的,後面會講,${adminPath} 是我在配置文件裏面配置的路徑而已,完全可以根據自己的路由進行設置。shiroFilterChainDefinitions最主要是在shiroFilter中作爲一個參數注入。
===============權限過濾器及配置釋義=======================
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數
roles:例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method] ,其中method爲post,get,delete等。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString
是你訪問的url裏的?後面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查
2.重點來了:shiroFilter(ShiroFilterFactoryBean),這裏要非常小心!!
這裏的bean的名字一定要和web.xml裏面的那個Filter名字相同,具體可以見下面的源碼:
DelegatingFilterProxy.java:
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
還記得我們web.xml裏面配置的那個Filter嗎, 其實我們配置的Filter只不過是起到一個代理的作用,那麼它代理誰呢? 它也不能知道,它所能做的就是根據targetBeanName去容器中獲取bean(這個bean是實現了Filter接口的),其中的targetBeanName就是bean的名稱,如果沒有設置的話,那麼就默認使用的Filter名稱。所以說前面說過的必須相同是不正確的,你只需要在Filter中設置targetBeanName和spring-context-shiro配置文件中ShiroFilterFactoryBean的bean名稱一樣即可。
除了上面需要注意的幾個點之外,ShiroFilterFactoryBean還有一些屬性:unauthorizedUrl,系統未認證時跳轉的頁面,loginUrl登錄頁面,successUrl登錄成功的頁面,filter屬性就是和前面的shiroFilterChainDefinitions對應的。同時支持自定義,並且配置路由:像<entry key="outdate" value-ref="sessionOutDateFilter"/>這樣的。最底層是過濾器,下面是我實現的一個filter:
package com.yonyou.kms.common.security.shiro.session;
import java.io.PrintWriter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.web.servlet.AdviceFilter;
import com.yonyou.kms.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.yonyou.kms.modules.sys.utils.UserUtils;
/**
*
* 自定義filter
* @author Hotusm
*
*/
public class SessionOutDateFilter extends AdviceFilter{
private String redirectUrl="http://url/portal";//session 失效之後需要跳轉的頁面
private String platformUrl="http://url/kms/a/login";
//排除這個鏈接 其他的鏈接都會進行攔截
private String loginUrl="/kms/a/login";
private String frontUrl="cms/f";
private String uploadUrl="cms/article/plupload";
private String appUrl="a/app";
protected boolean preHandle(ServletRequest request, ServletResponse response){
Principal principal = UserUtils.getPrincipal();
HttpServletRequest req=(HttpServletRequest) request;
String uri=req.getRequestURI();
if(checkUrl(uri, loginUrl,frontUrl,uploadUrl,appUrl)|(principal!=null&&!principal.isMobileLogin())){
return true;
}
try {
issueRedirect(request,response,redirectUrl);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
protected void issueRedirect(ServletRequest request, ServletResponse response, String redirectUrl)
throws Exception
{
String url="<a href="+redirectUrl+" target=\"_blank\" οnclick=\"custom_close()\">重新登錄<a/> ";
String platform="<a href="+platformUrl+" target=\"_blank\" οnclick=\"custom_close()\">直接登錄<a/> ";
HttpServletResponse resp=(HttpServletResponse) response;
HttpServletRequest req=(HttpServletRequest) request;
response.setContentType("text/html;charset=UTF-8");
PrintWriter out=resp.getWriter();
out.print("<script language='javascript'>");
out.print("function custom_close(){" +
"self.opener=null;" +
"self.close();}");
out.print("</script>");
out.print("沒有權限或者驗證信息過期,請點擊"+url+"登錄portal<br/>");
out.print("直接登錄"+platform);
}
public String getRedirectUrl() {
return redirectUrl;
}
public void setRedirectUrl(String redirectUrl) {
this.redirectUrl = redirectUrl;
}
public String getLoginUrl() {
return loginUrl;
}
public void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
/**
* 排除一些url不進行攔截
* @param targetUrl
* @param urls
* @return
*/
private boolean checkUrl(String targetUrl,String ...urls){
for(int i=0;i<urls.length;i++){
if(targetUrl.contains(urls[i])){
return true;
}
}
return false;
}
}
這個和springmvc的攔截器是相同的用法,返回true則表示驗證通過(後面的邏輯繼續執行),返回false就表示驗證不通過。
最後在shiroFilter的filters進行配置我們自定義的bean:
<property name="filters">
<map>
<entry key="outdate" value-ref="sessionOutDateFilter"/>
</map>
</property>
這個sessionOutDateFilter我們需要注入(這裏省略)。最後我們就將可以將這些東西加到shiroFilterChainDefinitions中去:
<bean name="shiroFilterChainDefinitions" class="java.lang.String">
<constructor-arg>
<value>
......
${adminPath}/** = outdate
.....
</value>
</constructor-arg>
</bean>
這樣我們自己定義的叫做outdata的路由會攔截${adminPath}下的所以路徑,並且進行驗證。
3.
SecurityManager
它和我們前面講的ShiroFilterFactoryBean的關係形象的將就是ShiroFilterFactoryBean是一個路由規則配置倉庫和代理類,其實真正的邏輯都是在SecurityManager中進行的,下面來進行詳講SecurityManager的依賴類。
一:realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源,下面是jeesite重寫的realm:
//@DependsOn({"userDao","roleDao","menuDao"})
//@Service Spring自動注入。
public class SystemAuthorizingRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(getClass());
private SystemService systemService;
/**
* 認證回調函數, 登錄時調用
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
……
}
/**
* 授權查詢回調函數, 進行鑑權但緩存中無用戶的授權信息時調用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
……
}
參考資料:http://www.cnblogs.com/zr520/p/5009790.html
任何請求首先會再web攔截,跳轉到
1.FormAuthenticationFilter攔截器,創建一個UsernamePasswordToken 令牌。
2.如果未登錄跳轉到登錄界面,如果首次登陸,會進行SystemAuthorizingRealm,認證和授權。