jeesite框架學習—shiro權限。




準備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>


在項目中重點還是配置spring-context-shiro.xml:先把配置的貼出來,然後講一下這幾個配置的意義:


<?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,認證和授權。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章