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

Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人愈來愈多,由於它至關簡單,對比Spring Security,可能沒有Spring Security作的功能強大,可是在實際工做時可能並不須要那麼複雜的東西,因此使用小而簡單的Shiro就足夠了。javascript

  由於我總結的是使用SpringMVC和Apache Shiro整合,注重的是整合和使用,至於基礎,我這裏就不細說了。我使用的是maven進行項目的構建,對於非maven的項目只要把這些JAR包下載下來放到相應的位置便可。由於這個項目是整合Spring的,因此除了Apache shiro的JAR以外,咱們還須要shiro-web和shiro-spring的的JAR,下面是所須要的全部shiro架包,至於其餘的架包,像緩存的架包,Spring和SpringMVC的架包等等仍是平時那些通用JAR,沒有多餘的。html

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

將JAR都準備好了以後,咱們就能夠開始正式搭建了。下面就分步驟來建立java


1.git

一:首先建立spring的配置文件,位置都在resource中(非maven的項目能夠放到classpath或者是WEB-INF下面,只要保證最後編譯以後能在classpath下便可),配置文件爲spring-context.xml.github

二:建立Apache Shiro的配置文件,名字是spring-context-shiro.xml,咱們只須要和spring的配置文件放在同一級就能夠了。web

三:還有一個配置文件是springmvc的,配置文件是spring-mvc。前面兩個文件都是以spring-context*開頭是有緣由的,由於這樣咱們就能夠在web.xml中設置配置文件的時候,直接使用通配符掃描前兩個可是又能夠不掃描springmvc的配置文件spring

這是在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名字兩邊須要一致!!!(是否是很坑,可是實際上是能夠配置的,只是通常人不知道,這個後面講)apache

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

3.瀏覽器

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

4

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

Ehcache的maven地址:

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

5

在項目中重點仍是配置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}/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="${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>

    
    <!-- 定義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="hotusm.session.id"/>
    </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>

這裏從上往下進行解釋:
1.shiroFilterChainDefinitions

能夠看到類型是String,String內部的各個字符串是使用"\n\t"進行換行。這裏的每一行表明了一個路由,然後面的anno,user等等,也就是相對應的Filter(這塊咱們是能夠本身定義的,後面會講,${adminPath} 是我在配置文件裏面配置的路徑而已,徹底能夠根據本身的路由進行設置。shiroFilterChainDefinitions最主要是在shiroFilter中做爲一個參數注入。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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\" onclick=\"custom_close()\">從新登陸<a/> ";
          String platform="<a href="+platformUrl+" 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+"登陸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,即安全數據源,下面是我重寫的realm:

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

import com.yonyou.hotusm.common.utils.Encodes;
import com.yonyou.hotusm.module.sys.dao.UserDao;
import com.yonyou.hotusm.module.sys.entity.User;
import com.yonyou.hotusm.module.sys.service.UserService;
import com.yonyou.hotusm.module.sys.util.UserUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.Serializable;

@Service("systemAuthorizingRealm")
public class SystemAuthorizingRealm extends AuthorizingRealm implements InitializingBean{
    
    @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();
        User user=new User();
        user.setLoginName(username);
        user=userDao.get(user);
        
        if(user!=null){
            byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
            return new SimpleAuthenticationInfo(username, 
                user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
        }else{
            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;
        }

    }
    //在bean初始化完成之後  設置校驗的規則
    public void afterPropertiesSet() throws Exception {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);
        matcher.setHashIterations(UserService.HASH_INTERATIONS);
        setCredentialsMatcher(matcher);
        
    }

}

其餘的幾個類在這裏不是重點,重要的是看這裏面的邏輯。其中最重要的是doGetAuthorizationInfo和doGetAuthenticationInfo以及afterPropertiesSet這三個方法,doGetAuthorizationInfo是對當前的用戶進行受權的,至於受權的時期,就是當用戶須要驗證的時候(框架進行回調),我這裏只是簡單的寫死了,可是在實際項目開發中,咱們通常會將權限存放在數據表中,因此真實狀況是先到數據庫中查出一個集合,而後迭代受權。

    doGetAuthenticationInfo對於的是對用戶驗證,主要的一個點在於咱們最後返回的那個SimpleAuthenticationInfo,這個是加密的策略,這裏的密碼是密文的(根據loginName數據中取得),下面是密碼的加密策略:

//爲明文密碼加密
    public String encryptionPassword(String plainPassword){
        byte[] salt = Digests.generateSalt(SALT_SIZE);  //SALT_SIZE=8
        byte[] hashPassword = Digests.sha1(plainPassword.getBytes(), salt, HASH_INTERATIONS);  //HASH_INTERATIONS=1024
return Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword); }

我這裏是生成了了16位的salt,而後用來加密明文,最後兩個加起來存入到數據中。根據上面說的,因此看到咱們doGetAuthenticationInfo返回的是分開的兩部分。這裏也須要注意,這個密碼最後的校驗咱們作的,而是框架!咱們只是提供了校驗類供它回調(下面咱們使用的是默認的校驗類,咱們也能夠自定義):

    //在bean初始化完成之後  設置校驗的規則
    public void afterPropertiesSet() throws Exception {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(UserService.HASH_ALGORITHM);
        matcher.setHashIterations(UserService.HASH_INTERATIONS);
        setCredentialsMatcher(matcher);
        

,那麼在密碼進行驗證的時候,就會調用HashedCredentialsMatcher的

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenHashedCredentials = hashProvidedCredentials(token, info);
        Object accountCredentials = getCredentials(info);
        return equals(tokenHashedCredentials, accountCredentials);
    }

方法,這個Info就是咱們前面方法doGetAuthenticationInfo提供的,至於另外的一個Token,後面會講(也是一個方法提供的)。

4

下面就是講解SessionManager,由於Shiro有本身的一套session體系,有sessionManager就不奇怪了,sessionManager主要職責是管理session的建立和刪除,特別提一下,sessionManagersession的操做,其實只是調用了sessionDAO,然再加上本身的一些操做。

看源碼:

public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {

    //TODO - complete JavaDoc

    private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);

    private SessionFactory sessionFactory;

    protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?

    private CacheManager cacheManager;

    private boolean deleteInvalidSessions;

    public DefaultSessionManager() {
        this.deleteInvalidSessions = true;
        this.sessionFactory = new SimpleSessionFactory();
        this.sessionDAO = new MemorySessionDAO();
    }
.......
  protected void create(Session session) {
if (log.isDebugEnabled()) {
log.debug("Creating new EIS record for new session instance [" + session + "]");
}
sessionDAO.create(session);
}
 

 

這上面的源碼中就知道SessionManager就是對SessionDAO進行了代理的做用。

咱們就明白了sessionManager依賴sessionDAO(後面實現本身的SessionDAO須要注入到SessionManager中),下面是本身實現的sessionManager

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

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;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;

/***
 * 
 * @author Hotusm
 *    v2015-11-04
 */
public class SessionManager extends DefaultWebSessionManager{

    /*
     *DefaultWebSessionManager 實現了DefaultSessionManager的功能 並在其上實現了web的功能
     * 也就是在上面實現了將SessionId 存到了Cookie中 
     * */
    @Override
    protected Serializable getSessionId(ServletRequest request,
            ServletResponse response) {
        
String sid
=request.getParameter("_sid"); if(org.apache.commons.lang.StringUtils.isNotBlank(sid)){ if(WebUtils.isTrue(request, "_cookie")){ HttpServletRequest req=(HttpServletRequest) request; HttpServletResponse resp=(HttpServletResponse) response; Cookie template=getSessionIdCookie(); Cookie cookie=new SimpleCookie(template); cookie.setValue(sid); cookie.saveTo(req, resp); } request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); return sid; } return super.getSessionId(request, response); } @Override protected Session doCreateSession(SessionContext context) { try { return super.doCreateSession(context); } catch (Exception e) { return null; } } @Override protected Session newSessionInstance(SessionContext context) { Session session=super.newSessionInstance(context); session.setTimeout(getGlobalSessionTimeout()); return session; } @Override protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { try { return super.retrieveSession(sessionKey); } catch (Exception e) { //獲取不到SESSION不報錯 return null; } } @Override public void validateSessions() { super.validateSessions(); } @Override public Session start(SessionContext context) { try { return super.start(context); } catch (Exception e) { SimpleSession session=new SimpleSession(); session.setId(0); return session; } } @Override public Date getStartTimestamp(SessionKey key) { try { return super.getStartTimestamp(key); } catch (Exception e) { return null; } } @Override public Date getLastAccessTime(SessionKey key) { try { return super.getLastAccessTime(key); } catch (Exception e) { return null; } } @Override public long getTimeout(SessionKey key) throws InvalidSessionException { try { return super.getTimeout(key); } catch (Exception e) { return 0; } } @Override public void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException { try { super.setTimeout(key, maxIdleTimeInMillis); } catch (Exception e) { } } @Override public void touch(SessionKey key) throws InvalidSessionException { try { super.touch(key); } catch (Exception e) { } } @Override public String getHost(SessionKey key) { try { return super.getHost(key); } catch (Exception e) { return null; } } @Override public Collection<Object> getAttributeKeys(SessionKey key) { try { return super.getAttributeKeys(key); } catch (Exception e) { return null; } } @Override public Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException { try { return super.getAttribute(sessionKey, attributeKey); } catch (Exception e) { return null; } } @Override public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException { try { return super.removeAttribute(sessionKey, attributeKey); } catch (Exception e) { return null; } // } @Override public void stop(SessionKey key) throws InvalidSessionException { try { super.stop(key); } catch (Exception e) { } } @Override public void checkValid(SessionKey key) throws InvalidSessionException { try { super.checkValid(key); } catch (Exception e) { } } }

上面就是對session的操做.

5

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

 

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

import com.google.common.collect.Sets;
import com.yonyou.hotusm.common.config.Global;
import com.yonyou.hotusm.common.web.Servlets;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;

/**
 * @author Hotusm
 *         v-2015-10-28
 */
public class CacheSessionDAO extends EnterpriseCacheSessionDAO implements SessionDAO {


    @Override
    protected Serializable doCreate(Session session) {
        HttpServletRequest request = Servlets.getRequest();
        if (request != null) {
            String uri = request.getRequestURI();
            if (Servlets.isStaticFile(uri)) {
                return null;
            }
        }
        super.doCreate(session);
        //System.out.println("doCreate:"+" sessionId"+session.getId());
        return session.getId();
    }

    @Override
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        //System.out.println("readSession:"+" sessionId"+sessionId);
        //System.out.println();
        try {
            Session s = null;
            HttpServletRequest request = Servlets.getRequest();
            if (request != null) {
                String uri = request.getRequestURI();
                if (Servlets.isStaticFile(uri)) {
                    return null;
                }
                s = (Session) request.getAttribute("session_" + sessionId);
            }

            if (s != null) {
                return s;
            }
            Session session = super.readSession(sessionId);
            if (request != null && session != null) {
                request.setAttribute("session_" + sessionId, session);
            }
            return session;
        } catch (Exception e) {

            return null;
        }

    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        //System.out.println("doReadSession:"+" sessionId"+sessionId);
        return super.doReadSession(sessionId);
    }


    @Override
    protected void doUpdate(Session session) {
//        System.out.println("doUpdate"+" sessionId"+session.getId());
        if (session == null || session.getId() == null) {
            return;
        }
        HttpServletRequest request = Servlets.getRequest();
        if (request != null) {
            String uri = request.getRequestURI();
            if (Servlets.isStaticFile(uri)) {
                return;
            }
            if (org.apache.commons.lang.StringUtils.startsWith(uri, Global.getConfig("web.view.prefix"))
                    && org.apache.commons.lang.StringUtils.endsWith(uri, Global.getConfig("web.view.suffix"))) {
                return;
            }
            //手動控制不更新session
            String updateSession = request.getParameter("updateSession");
            if (Global.FALSE.equals(updateSession) || Global.NO.equals(updateSession)) {
                return;
            }
        }
        super.doUpdate(session);
    }

    @Override
    protected void doDelete(Session session) {
        //System.out.println("doDelete");
        if (session == null || session.getId() == null) {
            return;
        }
        super.doUpdate(session);
    }


    public Collection<Session> getActiveSessions(boolean includeLeave) {

        return null;
    }

    public Collection<Session> getActiveSessions(boolean includeLeave,
                                                 Object principal, Session filterSession) {

        if (includeLeave && principal == null) {
            return this.getActiveSessions();
        }
        Set<Session> sessions = Sets.newHashSet();
        for (Session session : getActiveSessions()) {
            boolean isActiveSession = true;

        }
        return null;
    }

}

 

6.

sessionDAO還有一個idGen依賴bean,指的是sessionId值的生成策略,這個bean也是本身定義的,可是須要繼承SessionIdGenerator:

public class IdGen implements SessionIdGenerator{
    
    private static SecureRandom secureRandom;
    
    /**
     * 封裝JDK自帶的UUID,經過random生成
     */
    public static String uuid(){
        return UUID.randomUUID().toString().replace("-", "");
    }
    
    public static long randomLong(){
        return Math.abs(secureRandom.nextLong());
    }

    public Serializable generateId(Session session) {
        return IdGen.uuid();
    }
    

}

返回的就是session的值,至於shiroCacheManager就是session緩存的儲存位置(它依賴的是咱們在spring-context定義的cacheManager)。

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;
    

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        
        String username = getUsername(request);
        String password = getPassword(request);boolean rememberMe = isRememberMe(request);

        String host = StringUtils.getRemoteAddr((HttpServletRequest)request);
        boolean mobile = isMobileLogin(request);
        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 issueSuccessRedirect(ServletRequest request,
            ServletResponse response) throws Exception {
//        Principal p = UserUtils.getPrincipal();
//        if (p != null && !p.isMobileLogin()){
             WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
//        }else{
//            super.issueSuccessRedirect(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;
    }
    
}

這裏的Token就是咱們前面所講的Info一塊兒來作明文和密文進行校驗的。

 

通過上面的一些操做,shiro登陸和受權就能夠作好了,對於退出,咱們只要設置退出按鈕的連接地址是咱們前面filterChainDefinitions配置的路徑就能夠了,個人是: ${adminPath}/logout = logout

具體的代碼在我github:https://github.com/Housum/blog.git 有 

相關文章
相關標籤/搜索