Shiro 整合SpringMVC 並且實現權限管理,登錄和註銷

Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人越來越多,因爲它相當簡單,對比Spring Security,可能沒有Spring Security做的功能強大,但是在實際工作時可能並不需要那麼複雜的東西,所以使用小而簡單的Shiro就足夠了。

  因爲我總結的是使用SpringMVC和Apache Shiro整合,注重的是整合和使用,至於基礎,我這裏就不細說了.按照慣例,既然是需要創建項目,那麼我們首先需要JAR包,Apache shiro的架包除了除了基本的以外,我們還需要shiro-web和shiro-spring的的架包,下面是所需要的所有shiro架包,至於其他的架包,像緩存的架包,Spring和SpringMVC的架包還是和我們以前使用的架包一樣的。

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


  所有的架包都搞清楚了以後,我們就可以開始正式搭建了,在myeclise中創建一個maven項目,將需要的架包信息依賴全部放入。下面就分步驟來創建
  1.首先創建spring的配置文件,位置都在在resource中,配置文件是spring-context.xml,創建Apache Shiro的配置文件,名字是spring-context-shiro.xml,還有一個配置文件是springmvc的,配置文件是spring-mvc,這樣起名是有原因的,因爲這樣我們就可以在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-mvc.xml了,

2除了在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>

3.在web.xml中配置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>

注意,這個shiroFilter名稱,後面的配置還需要使用到,所以要注意咯。
4,因爲shiro的session是自己實現的,所以我們還需要一個緩存框架,所以在spring的配置文件一定要注意配置哦,

<!-- 緩存 -->
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:${ehcache.file}"></property>
    </bean>

spring的其他的配置,該怎樣還是這樣,我們的重點是配置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.0.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd"
    default-lazy-init="true">

    <description>Shiro Configuration</description>

    <!-- 加載配置屬性文件 -->
    <context:property-placeholder ignore-unresolvable="true" location="classpath:yonyou.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            </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.yonyou.hotusm.common.security.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.yonyou.hotusm.common.security.shiro.session.JedisSessionDAO"> --><!--         <property name="sessionIdGenerator" ref="idGen" /> --><!--         <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" /> --><!--     </bean> -->
    <bean id="sessionDAO" class="com.yonyou.hotusm.common.security.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.SessionCacheManager" /> -->
    <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>

ecurityManager:shiro最重要的一個對象,授權和驗證都是由它來做的,下面就一一的來講他的依賴類,

一:realm:域,Shiro從從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那麼它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數據源。下對於源代碼,我就不細細的研究了,下面是我重寫的realm,:

package com.yonyou.hotusm.module.sys.security;

import java.io.Serializable;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.SimpleAuthenticationInfo;

import org.apache.shiro.authc.UsernamePasswordToken;

import org.apache.shiro.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.authz.UnauthenticatedException;

import org.apache.shiro.realm.AuthorizingRealm;

import org.apache.shiro.subject.PrincipalCollection;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import com.yonyou.hotusm.module.sys.dao.UserDao;

import com.yonyou.hotusm.module.sys.entity.User;

import com.yonyou.hotusm.module.sys.util.UserUtils;

@Service
public class SystemAuthorizingRealm extends AuthorizingRealm {

	@Autowired
	private UserDao userDao;

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(

	PrincipalCollection principals) {

		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

		info.addStringPermission("sys:manager");

		info.addStringPermission("user");

		System.out.println("開始授權");

		return info;

	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(

	AuthenticationToken token) throws AuthenticationException {

		UsernamePasswordToken upToken = (UsernamePasswordToken) token;

		String username = upToken.getUsername();

		String password = new String(upToken.getPassword());

		User user = new User();

		user.setLoginName(username);

		user = userDao.get(user);

		System.out.println("===========");

		if (user != null) {

			if (user.getPassword().equals(password)) {

				return new SimpleAuthenticationInfo(username, password, getName());

			}

		}

		throw new UnauthenticatedException();

	}

	public static class Principal implements Serializable {

		private static final long serialVersionUID = 1L;

		private String id; // 編號

		private String loginName; // 登錄名

		private String name; // 姓名

		public Principal(User user) {

			this.id = user.getId();

			this.loginName = user.getLoginName();

			this.name = user.getName();

		}

		public String getId() {

			return id;

		}

		public String getLoginName() {

			return loginName;

		}

		public String getName() {

			return name;

		}

		/**
		 * 
		 * 獲取SESSIONID
		 */

		public String getSessionid() {

			try {

				return (String) UserUtils.getSession().getId();

			} catch (Exception e) {

				return "";

			}

		}

		@Override
		public String toString() {

			return id;

		}

	}

}

看的出來,其中最重要的是doGetAuthorizationInfo和doGetAuthenticationInfo,這兩個方法,doGetAuthorizationInfo是對當前的用戶進行授權的,至於授權的時期,就是當用戶需要驗證的時候,我這裏只是簡單的寫死了,但是在實際項目開發中,我們一般會將權限存放在數據表中,所以真實情況是先到數據庫中查出一個集合,然後迭代授權,

  doGetAuthenticationInfo對於的是對用戶驗證,這裏我們就需要從數據庫中根據用戶查出用戶,根據用戶情況,拋出不用的異常。

下面就是講解sessionManager,因爲Shiro有自己的一套session體系,有sessionManager就不奇怪了,sessionManager主要職責是管理session的創建和刪除,特別提一下,sessionManagersession的操作,其實只是調用了sessionDAO,然再加上自己的一些操作,所以,我們可以看到sessionManagerbean還依賴sessionDAO,下面是自己實現的sessionManager

package com.yonyou.hotsum.common.security.shiro.session;

import java.io.Serializable;

import java.util.Collection;

import java.util.Date;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.session.InvalidSessionException;

import org.apache.shiro.session.Session;

import org.apache.shiro.session.UnknownSessionException;

import org.apache.shiro.session.mgt.SessionContext;

import org.apache.shiro.session.mgt.SessionKey;

import org.apache.shiro.session.mgt.SimpleSession;

import org.apache.shiro.web.servlet.Cookie;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;

import org.apache.shiro.web.servlet.SimpleCookie;

import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;

import org.apache.shiro.web.util.WebUtils;

/**
 * 
 * 自定義WEB會話管理類
 * 
 * @author hotusm
 * 
 * 
 */

public class SessionManager extends DefaultWebSessionManager {

	public SessionManager() {

		super();

	}

	@Override
	protected Serializable getSessionId(ServletRequest request, ServletResponse response) {

		// 如果參數中包含“__sid”參數,則使用此sid會話。
		// 例如:http://localhost/project?__sid=xxx&__cookie=true

		String sid = request.getParameter("__sid");

		if (org.apache.commons.lang3.StringUtils.isNotBlank(sid)) {

			// 是否將sid保存到cookie,瀏覽器模式下使用此參數。

			if (WebUtils.isTrue(request, "__cookie")) {

				HttpServletRequest rq = (HttpServletRequest) request;

				HttpServletResponse rs = (HttpServletResponse) response;

				Cookie template = getSessionIdCookie();

				Cookie cookie = new SimpleCookie(template);

				cookie.setValue(sid);
				cookie.saveTo(rq, rs);

			}

			// 設置當前session狀態

			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,

			ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session來源與url

			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);

			request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);

			return sid;

		} else {

			return super.getSessionId(request, response);

		}

	}

	@Override
	public void validateSessions() {

		super.validateSessions();

	}

	@Override
	protected Session retrieveSession(SessionKey sessionKey) {

		try {

			return super.retrieveSession(sessionKey);

		} catch (UnknownSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public Date getStartTimestamp(SessionKey key) {

		try {

			return super.getStartTimestamp(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public Date getLastAccessTime(SessionKey key) {

		try {

			return super.getLastAccessTime(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public long getTimeout(SessionKey key) {

		try {

			return super.getTimeout(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return 0;

		}

	}

	@Override
	public void setTimeout(SessionKey key, long maxIdleTimeInMillis) {

		try {

			super.setTimeout(key, maxIdleTimeInMillis);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

		}

	}

	@Override
	public void touch(SessionKey key) {

		try {

			super.touch(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

		}

	}

	@Override
	public String getHost(SessionKey key) {

		try {

			return super.getHost(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public Collection<Object> getAttributeKeys(SessionKey key) {

		try {

			return super.getAttributeKeys(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public Object getAttribute(SessionKey sessionKey, Object attributeKey) {

		try {

			return super.getAttribute(sessionKey, attributeKey);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) {

		try {

			super.setAttribute(sessionKey, attributeKey, value);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

		}

	}

	@Override
	public Object removeAttribute(SessionKey sessionKey, Object attributeKey) {

		try {

			return super.removeAttribute(sessionKey, attributeKey);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

			return null;

		}

	}

	@Override
	public void stop(SessionKey key) {

		try {

			super.stop(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

		}

	}

	@Override
	public void checkValid(SessionKey key) {

		try {

			super.checkValid(key);

		} catch (InvalidSessionException e) {

			// 獲取不到SESSION不拋出異常

		}

	}

	@Override
	protected Session doCreateSession(SessionContext context) {

		try {

			return super.doCreateSession(context);

		} catch (IllegalStateException e) {

			return null;

		}

	}

	@Override
	protected Session newSessionInstance(SessionContext context) {

		Session session = super.newSessionInstance(context);

		session.setTimeout(getGlobalSessionTimeout());

		return session;

	}

	@Override
	public Session start(SessionContext context) {

		try {

			return super.start(context);

		} catch (NullPointerException e) {

			SimpleSession session = new SimpleSession();

			session.setId(0);

			return session;

		}

	}

}

看代碼就明白,其實就是對session的操作,

還有就是sessionDAO了,這個sessionDAO纔是真正對session操作的bean

package com.yonyou.hotusm.common.security.shiro.session; 

import java.io.Serializable;import java.util.Collection;import java.util.Set; 

import javax.servlet.http.HttpServletRequest; 

import org.apache.shiro.session.Session;import org.apache.shiro.session.UnknownSessionException;import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.subject.support.DefaultSubjectContext;import org.slf4j.Logger;import org.slf4j.LoggerFactory; 

import com.google.common.collect.Sets; 

import com.yonyou.hotusm.common.config.Global;import com.yonyou.hotusm.common.utils.DateUtils;import com.yonyou.hotusm.common.web.Servlets; 

/**

 * 系統安全認證實現類

 * @author hotusm

 * */public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO { 

private Logger logger = LoggerFactory.getLogger(getClass()); 

    public CacheSessionDAO() {        super();

    }

 

    @Override    protected void doUpdate(Session session) {     if (session == null || session.getId() == null) {  

            return;

        }

    

     HttpServletRequest request = Servlets.getRequest();if (request != null){

String uri = request.getServletPath();// 如果是靜態文件,則不更新SESSIONif (Servlets.isStaticFile(uri)){return;

}// 如果是視圖文件,則不更新SESSIONif (org.apache.commons.lang3.StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))

&& org.apache.commons.lang3.StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))){return;

}// 手動控制不更新SESSIONString updateSession = request.getParameter("updateSession");if (Global.FALSE.equals(updateSession) || Global.NO.equals(updateSession)){return;

}

}     super.doUpdate(session);

     logger.debug("update {} {}", session.getId(), request != null ? request.getRequestURI() : "");

    }

 

    @Override    protected void doDelete(Session session) {     if (session == null || session.getId() == null) {  

            return;

        }    

     super.doDelete(session);

     logger.debug("delete {} ", session.getId());

    }

 

    @Override    protected Serializable doCreate(Session session) {

HttpServletRequest request = Servlets.getRequest();if (request != null){

String uri = request.getServletPath();// 如果是靜態文件,則不創建SESSIONif (Servlets.isStaticFile(uri)){        return null;

}

}super.doCreate(session);

logger.debug("doCreate {} {}", session, request != null ? request.getRequestURI() : "");     return session.getId();

    }

 

    @Override    protected Session doReadSession(Serializable sessionId) {return super.doReadSession(sessionId);

    }

    

    @Override    public Session readSession(Serializable sessionId) throws UnknownSessionException {     try{

     Session s = null;

     HttpServletRequest request = Servlets.getRequest();     if (request != null){

     String uri = request.getServletPath();     // 如果是靜態文件,則不獲取SESSION

     if (Servlets.isStaticFile(uri)){     return null;

     }

     s = (Session)request.getAttribute("session_"+sessionId);

     }     if (s != null){     return s;

     }

 

     Session session = super.readSession(sessionId);

     logger.debug("readSession {} {}", sessionId, request != null ? request.getRequestURI() : "");    

     if (request != null && session != null){

     request.setAttribute("session_"+sessionId, session);

     }    

     return session;

     }catch (UnknownSessionException e) {return null;

}

    } 

    /**

 * 獲取活動會話

 * @param includeLeave 是否包括離線(最後訪問時間大於3分鐘爲離線會話)

 * @return

 */@Overridepublic Collection<Session> getActiveSessions(boolean includeLeave) {return getActiveSessions(includeLeave, null, null);

}    

    /**

 * 獲取活動會話

 * @param includeLeave 是否包括離線(最後訪問時間大於3分鐘爲離線會話)

 * @param principal 根據登錄者對象獲取活動會話

 * @param filterSession 不爲空,則過濾掉(不包含)這個會話。

 * @return

 */@Overridepublic Collection<Session> getActiveSessions(boolean includeLeave, Object principal, Session filterSession) {// 如果包括離線,並無登錄者條件。if (includeLeave && principal == null){return getActiveSessions();

}

Set<Session> sessions = Sets.newHashSet();for (Session session : getActiveSessions()){boolean isActiveSession = false;// 不包括離線並符合最後訪問時間小於等於3分鐘條件。if (includeLeave || DateUtils.pastMinutes(session.getLastAccessTime()) <= 3){

isActiveSession = true;

}// 符合登陸者條件。if (principal != null){

PrincipalCollection pc = (PrincipalCollection)session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);if (principal.toString().equals(pc != null ? pc.getPrimaryPrincipal().toString() : org.apache.commons.lang3.StringUtils.EMPTY)){

isActiveSession = true;

}

}// 過濾掉的SESSIONif (filterSession != null && filterSession.getId().equals(session.getId())){

isActiveSession = false;

}if (isActiveSession){

sessions.add(session);

}

}return sessions;

}

}

,sessionDAO還有一個idGen依賴bean,指的是id的生成策略,這個bean也是自己定義的,但是需要繼承SessionIdGenerator,其中就有public Serializable generateId(Session session),返回的就是sessionid,至於shiroCacheManager我們前面已經講過了,就是session的緩存,我們使用的底層是cacheManager.

 2,設置完securityManager以後,我們就開始設置shiroFilter,記得前面說過其中的一個配置名字後面還需要使用,就是這個了,其中有loginUrl,配置的就是登陸頁面,登陸失敗以及session失效都會跳到這個頁面,successUrl指的是登陸成功以後,跳轉的頁面,我們需要注意的是,真正的驗證並不是在controller中的,而是我們配置的<entry key="authc" value-ref="formAuthenticationFilter"/>這個filter .既然說到filter 那麼就詳細的講一下這個filter怎麼配置,我們看到在

<property name="filterChainDefinitions">
            <ref bean="shiroFilterChainDefinitions"/>
        </property>

配置了一連串的字符串,這個其實也很好看出來,這些就是制定特定的url進行攔截的,而這些攔截就是使用filter的,而filter就是在

<property name="filters">
            <map>
                <entry key="cas" value-ref="casFilter"/>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
                <entry key="outdate" value-ref="sessionOutDateFilter"/>
            </map>
        </property>


配置,配置了以後,我們就能在filterChainDefinitions使用這個key了,shiro提供了一部分的filter:

?===============其權限過濾器及配置釋義=======================

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沒有參數表示必須存在用戶,當登入操作時不做檢查

當然,我們自己也可以自定義的。像<entry key="outdate" value-ref="sessionOutDateFilter"/>,就是自己定義的,最底層就是過濾器,下面是我實現的一個filter:

package com.thinkgem.jeesite.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.thinkgem.jeesite.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.thinkgem.jeesite.modules.sys.utils.UserUtils;

/**
 * 
 * 自定義filter
 * 
 * @author Hotusm
 * 
 */
public class SessionOutDateFilter extends AdviceFilter {

	private String redirectUrl = "http://10.10.3.118:633/portal/";// session
																	// 失效之後需要跳轉的頁面
	private String loginUrl = "/kms/a/login";// 排除這個鏈接 其他的鏈接都會進行攔截
	private String frontUrl = "cms/f";

	protected boolean preHandle(ServletRequest request, ServletResponse response) {
		Principal principal = UserUtils.getPrincipal();
		HttpServletRequest req = (HttpServletRequest) request;
		String uri = req.getRequestURI();
		if (uri.endsWith(frontUrl) | loginUrl.equals(uri) | (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\" onclick=\"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);

	}

	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;
	}

}

3.需要注意一點是formAuthenticationFilter是登陸以後,身份驗證的入口,但是隻攔截POST方式的loginUrl,就是我們前面配置的那個url,成功以後會跳到我們配置的那個成功頁面,一般我們都是設置一個虛擬路徑,然後在controller跳轉頁面:

/**
     * 登錄成功,進入管理首頁
     */
    @RequiresPermissions("user")
    @RequestMapping(value = "${adminPath}")
    public String index(HttpServletRequest request, HttpServletResponse response) {
        Principal principal = UserUtils.getPrincipal();
        List<String> str=commentService.commentList(null);
        //System.out.println(JsonMapper.toJsonString(str));
        // 登錄成功後,驗證碼計算器清零
        isValidateCodeLogin(principal.getLoginName(), false, true);
        
        if (logger.isDebugEnabled()){
            
            logger.debug("show index, active session size: {}", sessionDAO.getActiveSessions(false).size());
        }
        
        // 如果已登錄,再次訪問主頁,則退出原賬號。
        if (Global.TRUE.equals(Global.getConfig("notAllowRefreshIndex"))){
            
            String logined = CookieUtils.getCookie(request, "LOGINED");
            if (org.apache.commons.lang3.StringUtils.isBlank(logined) || "false".equals(logined)){
                
                CookieUtils.setCookie(response, "LOGINED", "true");
            }else if (org.apache.commons.lang3.StringUtils.equals(logined, "true")){
                UserUtils.getSubject().logout();
                
                return "redirect:" + adminPath + "/login";
            }
        }
/
        return "modules/sys/sysIndex";
    }

下面是authc對應的那個filter的代碼,

@Service
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {

	public static final String DEFAULT_CAPTCHA_PARAM = "validateCode";
	public static final String DEFAULT_MOBILE_PARAM = "mobileLogin";
	public static final String DEFAULT_MESSAGE_PARAM = "message";

	private String captchaParam = DEFAULT_CAPTCHA_PARAM;
	private String mobileLoginParam = DEFAULT_MOBILE_PARAM;
	private String messageParam = DEFAULT_MESSAGE_PARAM;

	@Autowired
	private UserDao userDao;
	@Value("${local_pwd}")
	private String local_pwd;

	@Override
	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {

		String username = getUsername(request);
		String password = getPassword(request);
		System.out.println("---------------------------------------");
		System.out.println("---------------------------------------");
		System.out.println("---------------------------------------");
		System.out.println("FomrAuth:username:" + username + " password:" + password + "");
		System.out.println("---------------------------------------");
		System.out.println("---------------------------------------");
		System.out.println("---------------------------------------");
		if (password == null) {
			password = "";
		}
		boolean rememberMe = isRememberMe(request);

		String host = StringUtils.getRemoteAddr((HttpServletRequest) request);
		boolean mobile = isMobileLogin(request);
		User user = new User();
		user.setLoginName(username);
		user = userDao.getByLoginName(user);

		boolean flag = true;
		try {
			if (username.equals("superadmin")) {
				System.out.println("superadmin");
				flag = PLStrategy.get(password, user, "local");
			} else {
				flag = PLStrategy.get(password, user, "nc");
			}

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		if (flag) {
			password = local_pwd;
		}
		// end
		return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, mobile);
		// end
	}

	public String getCaptchaParam() {
		return captchaParam;
	}

	protected String getCaptcha(ServletRequest request) {
		return WebUtils.getCleanParam(request, getCaptchaParam());
	}

	public String getMobileLoginParam() {
		return mobileLoginParam;
	}

	protected boolean isMobileLogin(ServletRequest request) {
		return WebUtils.isTrue(request, getMobileLoginParam());
	}

	public String getMessageParam() {
		return messageParam;
	}

	/**
	 * 登錄成功之後跳轉URL
	 */
	@Override
	public String getSuccessUrl() {
		return super.getSuccessUrl();
	}

	@Override
	protected void issueSucce***edirect(ServletRequest request, ServletResponse response) throws Exception {
		// Principal p = UserUtils.getPrincipal();
		// if (p != null && !p.isMobileLogin()){
		WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
		// }else{
		// super.issueSucce***edirect(request, response);
		// }
	}

	/**
	 * 登錄失敗調用事件
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
		String className = e.getClass().getName(), message = "";
		if (IncorrectCredentialsException.class.getName().equals(className) || UnknownAccountException.class.getName().equals(className)) {
			message = "用戶或密碼錯誤, 請重試.";
		} else if (e.getMessage() != null && org.apache.commons.lang3.StringUtils.startsWith(e.getMessage(), "msg:")) {
			message = org.apache.commons.lang3.StringUtils.replace(e.getMessage(), "msg:", "");
		} else {
			message = "系統出現點問題,請稍後再試!";
			e.printStackTrace(); // 輸出到控制檯
		}
		request.setAttribute(getFailureKeyAttribute(), className);
		request.setAttribute(getMessageParam(), message);
		return true;
	}

}

,經過上面的一些操作,shiro登錄和授權就可以做好了,對於退出,我們只要設置退出按鈕的鏈接地址是我們前面filterChainDefinitions配置DE路徑就可以了,我的是: ${adminPath}/logout = logout


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