SSM整合shiro

2.3.SSM整合

Shiro 的組件都是 JavaBean/POJO 式的組件,所以非常容易使用 Spring 進行組件管理,可以非常方便的從 ini 配置遷移到 Spring 進行管理,且支持 JavaSE 應用及 Web 應用的集成。

2.3.1.pom文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.wanho</groupId>
    <artifactId>shiroTeaching</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>shiroTeaching Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.2.4.RELEASE</spring.version>
        <mybatis.version>3.0.6</mybatis.version>
        <jackson.version>1.9.10</jackson.version>
        <shiro.version>1.2.2</shiro.version>
        <mybatis.spring.version>1.2.2</mybatis.spring.version>
        <jstl.version>1.2</jstl.version>
        <servlet-api.version>2.5</servlet-api.version>
        <jsp-api.version>2.0</jsp-api.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- log4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-quartz</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.18</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.11</version>
        </dependency>
        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis.spring.version}</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- JSP相關 -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>${servlet-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jsp-api</artifactId>
            <version>${jsp-api.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>${jackson.version}</version>
        </dependency>

    </dependencies>
    <build>
        <finalName>shiroTeaching</finalName>

        <plugins>
            <!-- 配置Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <path>/</path>
                    <port>8080</port>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2.3.2.spring配置文件

applicationContext-shiro

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!-- 加載配置文件
    <context:property-placeholder location="classpath:resource/*.properties"/>-->

    <!-- 緩存管理器
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> -->

    <!-- 緩存管理器 使用Ehcache實現 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro/ehcache.xml"/>
    </bean>
    <!-- 憑證匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5" /><!-- 加密算法的名稱 -->
        <property name="hashIterations" value="2" /><!-- 配置加密的次數 -->
    </bean>
    <!--Realm實現-->
    <bean id="userRealm" class="net.wanho.shiro.UserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher" />
        <property name="cachingEnabled" value="false" />
    </bean>
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="cacheManager" ref="cacheManager"/>
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>
    <!-- 開啓shiro註解支持(切面支持)-->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- 相當於調用SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
        <property name="arguments" ref="securityManager" />
    </bean>
    <!-- rememberMeManager管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie" />
    </bean>

    <!-- 記住我cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe" />
        <!-- 記住我cookie生效時間,默認單位是秒 7*24*60*60-->
        <property name="maxAge" value="604800" />
    </bean>
    <!-- Shiro的Web過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login/toLogin" />
        <property name="unauthorizedUrl" value="/error.html"/>
        <property name="filterChainDefinitions">
            <value>
                <!--
                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沒有參數表示必須存在用戶,當登入操作時不做檢查
                -->
                /login/toLogin = anon
                /login/checkLogin = anon
                /login/logout = authc
                /** = user
            </value>
        </property>
    </bean>
    <!--權限不足-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="org.apache.shiro.authz.UnauthorizedException">/error</prop>
            </props>
        </property>
    </bean>

    <!-- Shiro生命週期處理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

</beans>
  1. shiroFilter:此處使用 ShiroFilterFactoryBean 來創建 ShiroFilter 過濾器;filters 屬性用於定義自己的過濾器,即 ini 配置中的 [filters] 部分;filterChainDefinitions 用於聲明 url 和 filter 的關係,即 ini 配置中的 [urls] 部分。

    applicationContext-dao

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <!-- 加載配置文件 -->
    <context:property-placeholder location="classpath:resource/*.properties"/>
    <!-- 數據庫連接池
    maxActive 最大的存活時間
    minIdle 最小連接數
    -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="maxActive" value="10"></property    >
        <property name="minIdle" value="5"></property>
    </bean>
    <!-- 配置sqlSessionFactory ,關聯數據源
    value只能配置簡單數據類型,ref關聯到複雜類型(對象)
    -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation"  value="classpath:mybatis/SqlMapConfig.xml"/>
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置自動掃描,掃描mapper對象,交給spring代理 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="net.wanho.mapper"></property>
    </bean>

</beans>

applicationContext-trans

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <!-- 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 實際上用的是數據源的事務 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 傳播行爲 -->
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="create*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* net.wanho.service.*.*(..))"/>
    </aop:config>
</beans>

2.3.3.springMVC配置文件

springMVC.xml

<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"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 加載配置文件 -->
    <context:property-placeholder location="classpath:resource/*.properties"/>
    <!-- 默認的註解映射的支持 -->
    <mvc:annotation-driven/>

    <context:component-scan base-package="net.wanho.controller"/>

    <!-- 視圖解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!-- 啓用AOP 自定義註解用 -->
    <aop:config proxy-target-class="true"></aop:config>
</beans>

web.xml

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <display-name>smePlantform</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/applicationContext-*.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 配置一個解決post中文亂碼的過濾器 -->
  <filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!--配置springmvc-->
  <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:spring/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>

  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

2.3.4 實現shiro整合ehcache

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
    <defaultCache maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  />

    <!-- 授權緩存 -->
    <cache name="authorizationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="1"
           overflowToDisk="false"
           statistics="true">
    </cache>
    <!-- 認證緩存 -->
    <cache name="authenticationCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="1"
           overflowToDisk="false"
           statistics="true">
    </cache>
    <!--session緩存-->
    <cache name="shiro-activeSessionCache"
           maxEntriesLocalHeap="10000"
           overflowToDisk="false"
           eternal="false"
           diskPersistent="false"
           timeToIdleSeconds="0"
           timeToLiveSeconds="1"
           statistics="true">
    </cache>
</ehcache>

2.4.密碼比對,MD5&鹽值加密

2.4.1.散列算法

散列算法一般用於生成數據的摘要信息,是一種不可逆的算法,一般適合存儲密碼之類的數據,常見的散列算法如 MD5、SHA 等。一般進行散列時最好提供一個 salt(鹽),比如加密密碼 “admin”,產生的散列值是 “21232f297a57a5a743894a0e4a801fc3”,可以到一些 md5 解密網站很容易的通過散列值得到密碼 “admin”,即如果直接對密碼進行散列相對來說破解更容易,此時我們可以加一些只有系統知道的干擾數據,如用戶名和 ID(即鹽);這樣散列的對象是 “密碼 + 用戶名 +ID”,這樣生成的散列值相對來說更難破解。

String str = "hello";
String salt = "123";
String md5 = new Md5Hash(str, salt).toString();//還可以轉換爲 toBase64()/toHex()&nbsp;

Shiro 還提供了通用的散列支持:

String str = "hello";
String salt = "123";
//內部使用MessageDigest
String simpleHash = new SimpleHash("MD5", str, salt).toString();

通過調用 SimpleHash 時指定散列算法,其內部使用了 Java 的 MessageDigest 實現。

2.4.2.HashedCredentialsMatcher 實現密碼驗證服務

Shiro 提供了 CredentialsMatcher 的散列實現 HashedCredentialsMatcher,和之前的 PasswordMatcher 不同的是,它只用於密碼驗證,且可以提供自己的鹽,而不是隨機生成鹽,且生成密碼散列值的算法需要自己寫,因爲能提供自己的鹽。

1、生成密碼散列值

此處我們使用 MD5 算法,“密碼 + 鹽(用戶名 + 隨機數)” 的方式生成散列值:

String algorithmName = "md5";
String username = "liu";
String password = "123";
String salt1 = username;
String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
int hashIterations = 2;
SimpleHash hash = new SimpleHash(algorithmName, password, salt1 + salt2, hashIterations);
String encodedPassword = hash.toHex()

如果要寫用戶模塊,需要在新增用戶 / 重置密碼時使用如上算法保存密碼,將生成的密碼及 salt2 存入數據庫(因爲我們的散列算法是:md5(md5(密碼 +username+salt2)))。

2、功能實現

User

public class TUser {
    private Integer tUserId;

    private String tUserName;

    private String tUserAccount;

    private String tUserPassword;

    private String tSort;

    private String tUserType;

    private Integer tParentUserId;

    private Integer departmentId;

    private String tStatus;
}
public class UserService implements IUserService{
    /**
     * 獲取所有的角色
     * @param username
     * @return
     */
    @Override
    public Set<String> findRoles(String username) {
        Set<String> set = new HashSet<>();
        set.add("admin");
        return set;
    }

    /**
     * 獲取所有的權限
     * @param username
     * @return
     */
    @Override
    public Set<String> findPermissions(String username) {
        Set<String> set = new HashSet<>();
        set.add("dustin:test");
        return set;
    }

    /**
     * 根據用戶名獲取用戶信息
     * @param username
     * @return
     */
    @Override
    public TUser findByUsername(String username) {
        TUser user = new TUser();
        user.settUserName(username);
        user.settUserPassword(shiroMD5("123456"));
        user.settSort("abcd");
        return user;
    }

    //shiro的密碼加密,參數hashIterations表示加密次數,應該跟配置文件中的相同
    private String shiroMD5(String credentials){
        //加密方式
        String hashAlgorithmName = "MD5";
        //String credentials = "123456";
        //鹽
        ByteSource credentialsSalt = ByteSource.Util.bytes("dustin"+"abcd");
        //加密次數
        int hashIterations = 2;
        Object obj = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations);
        return obj.toString();
    }
}
public class UserRealm extends AuthorizingRealm {

    @Resource
    private IUserService userService;

    /**
     * 獲取角色與權限
     *doGetAuthorizationInfo執行時機有三個,如下:
     *  1、subject.hasRole(“admin”) 或 subject.isPermitted(“admin”):自己去調用這個是否有什麼角色或者是否有什麼權限的時候;
     *  2、@RequiresRoles("admin") :在方法上加註解的時候;
     *  3、@shiro.hasPermission name = "admin"/@shiro.hasPermission:"dustin:test"在頁面上加shiro標籤的時候,即進這個頁面的時候掃描到有這個標籤的時候。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String)principals.getPrimaryPrincipal();

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //獲取用戶所有角色
        authorizationInfo.setRoles(userService.findRoles(username));
        //獲取用戶所有權限
        authorizationInfo.setStringPermissions(userService.findPermissions(username));

        return authorizationInfo;
    }

    /**
     * 登錄信息驗證
     *
     * 1.doGetAuthenticationInfo執行時機如下
     * 當調用Subject currentUser = SecurityUtils.getSubject();
     * currentUser.login(token);
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String username = (String)token.getPrincipal();

        TUser user = userService.findByUsername(username);

        if(user == null) {
            throw new UnknownAccountException();//沒找到帳號
        }

        if(Boolean.TRUE.equals(user.gettStatus())) {
            throw new LockedAccountException(); //帳號鎖定
        }

        //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以自定義實現
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.gettUserName(), //用戶名
                user.gettUserPassword(), //密碼
                ByteSource.Util.bytes(user.gettUserName()+user.gettSort()),//salt=username+salt
                getName()  //realm name
        );
        return authenticationInfo;
    }

    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }

    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }

    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }

    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();

    }
}
public class loginController {

    @RequestMapping("/toLogin")
    public String tologin(HttpServletRequest request, HttpServletResponse response, Model model){
        System.out.println("來自IP[" + request.getRemoteHost() + "]的訪問");
        return "login";
    }

    /**
     * 驗證用戶名和密碼
     * @return
     */
    @RequestMapping("/checkLogin")
    public String login(String username,String password,ModelMap map) {

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        Subject currentUser = SecurityUtils.getSubject();
        try {
            //使用shiro來驗證
            if (!currentUser.isAuthenticated()){
                //這裏是記住我,記cookie
                token.setRememberMe(false);
                currentUser.login(token);//驗證角色和權限
            }

        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            map.put("code","登錄失敗:"+"賬號密碼不正確");
        }
        return JSONUtils.toJSONString(map);
    }

    @RequestMapping("/isLogin")
    @ResponseBody
    public String isLogin() {
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.isAuthenticated();
        return String.valueOf(currentUser.isAuthenticated());
    }

    @RequestMapping("/logout")
    @ResponseBody
    public String logout() {
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
        return "logout success";
    }
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>登錄頁面</title>
    <%@include file="/taglib.jsp" %>
</head>
<body>
<form action="${ctx}/login/checkLogin" method="post">
    username: <input type="text" name="username"><br>
    password: <input type="password" name="password"><br>
    <input type="submit" value="登錄">
</form>
</body>
</html>

此處就是把步驟 1 中生成的相應數據組裝爲 SimpleAuthenticationInfo,通過 SimpleAuthenticationInfo 的 credentialsSalt 設置鹽,HashedCredentialsMatcher 會自動識別這個鹽。

  • saltStyle 表示使用密碼 + 鹽的機制,authenticationQuery 第一列是密碼,第二列是鹽;
  • 通過 authenticationQuery 指定密碼及鹽查詢 SQL;

此處還要注意 Shiro 默認使用了 apache commons BeanUtils,默認是不進行 Enum 類型轉型的,此時需要自己註冊一個 Enum 轉換器 “BeanUtilsBean.getInstance().getConvertUtils().register(new EnumConverter(), JdbcRealm.SaltStyle.class);” 具體請參考示例 “com.github.zhangkaitao.shiro.chapter5.hash.PasswordTest” 中的代碼。

3、spring配置

<!-- 憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <property name="hashAlgorithmName" value="md5" /><!-- 加密算法的名稱 -->
    <property name="hashIterations" value="2" /><!-- 配置加密的次數 -->
</bean>

此處最需要注意的就是 HashedCredentialsMatcher 的算法需要和生成密碼時的算法一樣。另外 HashedCredentialsMatcher 會自動根據 AuthenticationInfo 的類型是否是 SaltedAuthenticationInfo 來獲取 credentialsSalt 鹽。

2.4.3.密碼重試次數限制

如在 1 個小時內密碼最多重試 5 次,如果嘗試次數超過 5 次就鎖定 1 小時,1 小時後可再次重試,如果還是重試失敗,可以鎖定如 1 天,以此類推,防止密碼被暴力破解。我們通過繼承 HashedCredentialsMatcher,且使用 Ehcache 記錄重試次數和超時時間。

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
       String username = (String)token.getPrincipal();
        //retry count + 1
        Element element = passwordRetryCache.get(username);
        if(element == null) {
            element = new Element(username , new AtomicInteger(0));
            passwordRetryCache.put(element);
        }
        AtomicInteger retryCount = (AtomicInteger)element.getObjectValue();
        if(retryCount.incrementAndGet() > 5) {
            //if retry count > 5 throw
            throw new ExcessiveAttemptsException();
        }
        boolean matches = super.doCredentialsMatch(token, info);
        if(matches) {
            //clear retry count
            passwordRetryCache.remove(username);
        }
        return matches;
}

如上代碼邏輯比較簡單,即如果密碼輸入正確清除 cache 中的記錄;否則 cache 中的重試次數 +1,如果超出 5 次那麼拋出異常表示超出重試次數了。

2.5.標籤&權限註解

2.5.1.jsp標籤

Shiro 提供了 JSTL 標籤用於在 JSP/GSP 頁面進行權限控制,如根據登錄用戶顯示相應的頁面按鈕

導入標籤庫

<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

標籤庫定義在 shiro-web.jar 包下的 META-INF/shiro.tld 中定義。

2.5.1.1.guest 標籤

<shiro:guest>
歡迎遊客訪問,<a href="${pageContext.request.contextPath}/login.jsp">登錄</a>
</shiro:guest>      

用戶沒有身份驗證時顯示相應信息,即遊客訪問信息。

2.5.1.2.user 標籤

<shiro:user>
        已認證通過的用戶,setRememberMe = true
</shiro:user>       

用戶已經身份驗證 / 記住我登錄後顯示相應的信息。

2.5.1.3.authenticated 標籤

<shiro:authenticated>
    用戶[<shiro:principal/>]已身份驗證通過
</shiro:authenticated       

用戶已經身份驗證通過,即 Subject.login 登錄成功,不是記住我登錄的。

2.5.1.4.notAuthenticated 標籤

<shiro:notAuthenticated>
    未身份驗證(包括記住我)
</shiro:notAuthenticated>   

用戶已經身份驗證通過,即沒有調用 Subject.login 進行登錄,包括記住我自動登錄的也屬於未進行身份驗證。

2.5.1.5.principal 標籤

&lt;shiro: principal/&gt;

顯示用戶身份信息,默認調用 Subject.getPrincipal() 獲取,即 Primary Principal。

&lt;shiro:principal type="java.lang.String"/&gt;

相當於 Subject.getPrincipals().oneByType(String.class)。

&lt;shiro:principal property="username"/&gt;

相當於 ((User)Subject.getPrincipals()).getUsername()。

2.5.1.6.hasRole 標籤

<shiro:hasRole name="admin">
    用戶[<shiro:principal/>]擁有角色admin<br/>
</shiro:hasRole>

如果當前 Subject 有角色將顯示 body 體內容。

2.5.1.7.hasAnyRoles 標籤

<shiro:hasAnyRoles name="admin,user">
    用戶[<shiro:principal/>]擁有角色admin或user<br/>
</shiro:hasAnyRoles>;

如果當前 Subject 有任意一個角色(或的關係)將顯示 body 體內容。

2.5.1.8.hasPermission 標籤

<shiro:hasPermission name="user:create">
    用戶[<shiro:principal/>]擁有權限user:create<br/>
</shiro:hasPermission>&nbsp;

如果當前 Subject 有權限將顯示 body 體內容。

2.5.1.8.lacksPermission 標籤

<shiro:lacksPermission name="org:create">
    用戶[<shiro:principal/>]沒有權限org:create<br/>
</shiro:lacksPermission>&nbsp;

如果當前 Subject 沒有權限將顯示 body 體內容。

2.5.2.權限註解

Shiro 提供了相應的註解用於權限控制,如果使用這些註解就需要使用 AOP 的功能來進行判斷,如 Spring AOP;Shiro 提供了 Spring AOP 集成用於權限註解的解析和驗證。

@RequiresAuthentication

表示當前 Subject 已經通過 login 進行了身份驗證;即 Subject.isAuthenticated() 返回 true。

@RequiresUser

表示當前 Subject 已經身份驗證或者通過記住我登錄的。

@RequiresGuest

表示當前 Subject 沒有身份驗證或通過記住我登錄過,即是遊客身份。

@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)

表示當前 Subject 需要角色 admin 和 user。

@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)

表示當前 Subject 需要權限 user:a 或 user:b。

此處使用了 Spring MVC 來測試 Shiro 註解,當然 Shiro 註解不僅僅可以在 web 環境使用,在獨立的 JavaSE 中也是可以用的,此處只是以 web 爲例了。

在 spring-mvc.xml 配置文件添加 Shiro Spring AOP 權限註解的支持:

<aop:config proxy-target-class="true"></aop:config>
<bean class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

如上配置用於開啓 Shiro Spring AOP 權限註解的支持;&lt;aop:config proxy-target-class="true"&gt; 表示代理類。

接着就可以在相應的控制器(Controller)中使用如下方式進行註解:

@RequiresRoles("admin")
@RequestMapping("/hello2")
public String hello2() {
    return "success";
}

訪問 hello2 方法的前提是當前用戶有 admin 角色。

2.6.會話管理(瞭解)

Shiro 提供了完整的企業級會話管理功能,不依賴於底層容器(如 web 容器 tomcat),不管 JavaSE 還是 JavaEE 環境都可以使用,提供了會話管理、會話事件監聽、會話存儲 / 持久化、容器無關的集羣、失效 / 過期支持、對 Web 的透明支持、SSO 單點登錄的支持等特性。即直接使用 Shiro 的會話管理可以直接替換如 Web 容器的會話管理。

2.6.1.會話

所謂會話,即用戶訪問應用時保持的連接關係,在多次交互中應用能夠識別出當前訪問的用戶是誰,且可以在多次交互中保存一些數據。如訪問一些網站時登錄成功後,網站可以記住用戶,且在退出之前都可以識別當前用戶是誰。

Shiro 的會話支持不僅可以在普通的 JavaSE 應用中使用,也可以在 JavaEE 應用中使用,如 web 應用。且使用方式是一致的。

login("classpath:shiro.ini", "zhang", "123");
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession()

登錄成功後使用 Subject.getSession() 即可獲取會話;其等價於 Subject.getSession(true),即如果當前沒有創建 Session 對象會創建一個;另外 Subject.getSession(false),如果當前沒有創建 Session 則返回 null(不過默認情況下如果啓用會話存儲功能的話在創建 Subject 時會主動創建一個 Session)。

session.getId();

獲取當前會話的唯一標識。

session.getHost();

獲取當前 Subject 的主機地址,該地址是通過 HostAuthenticationToken.getHost() 提供的。

session.getTimeout();
session.setTimeout(毫秒);&nbsp;

獲取 / 設置當前 Session 的過期時間;如果不設置默認是會話管理器的全局過期時間。

session.getStartTimestamp();
session.getLastAccessTime();

獲取會話的啓動時間及最後訪問時間;如果是 JavaSE 應用需要自己定期調用 session.touch() 去更新最後訪問時間;如果是 Web 應用,每次進入 ShiroFilter 都會自動調用 session.touch() 來更新最後訪問時間。

session.touch();
session.stop();

更新會話最後訪問時間及銷燬會話;當 Subject.logout() 時會自動調用 stop 方法來銷燬會話。如果在 web 中,調用 javax.servlet.http.HttpSession. invalidate() 也會自動調用 Shiro Session.stop 方法進行銷燬 Shiro 的會話。

session.setAttribute("key", "123");
session.removeAttribute("key");

設置 / 獲取 / 刪除會話屬性;在整個會話範圍內都可以對這些屬性進行操作。

Shiro 提供的會話可以用於 JavaSE/JavaEE 環境,不依賴於任何底層容器,可以獨立使用,是完整的會話模塊。

測試

/**
     * 測試會話session
     */

@Test
public void testSession() {
    Subject subject = login("classpath:shiro-realm.ini", "zhang", "123");
    Session session = subject.getSession();
    //獲取當前會話的唯一標識
    System.out.println(session.getId());
    //獲取當前 Subject 的主機地址
    System.out.println(session.getHost());
    //獲取 / 設置當前 Session 的過期時間
    System.out.println(session.getTimeout());
    // 獲取會話的啓動時間及最後訪問時間
    System.out.println(session.getStartTimestamp());
    System.out.println(session.getLastAccessTime());
    // 更新會話最後訪問時間及銷燬會話
    //session.touch();
    //session.stop();
    //設置屬性值
    session.setAttribute("name", "zhangsan");
    System.out.println(session.getAttribute("name"));

}

2.6.2.會話管理器

會話管理器管理着應用中所有 Subject 的會話的創建、維護、刪除、失效、驗證等工作。是 Shiro 的核心組件,頂層組件 SecurityManager 直接繼承了 SessionManager,且提供了SessionsSecurityManager 實現直接把會話管理委託給相應的 SessionManager,DefaultSecurityManager 及 DefaultWebSecurityManager 默認 SecurityManager 都繼承了 SessionsSecurityManager。

SecurityManager 提供瞭如下接口:

Session start(SessionContext context); //啓動會話
Session getSession(SessionKey key) throws SessionException; //根據會話Key獲取會話

另外用於 Web 環境的 WebSessionManager 又提供瞭如下接口:

boolean isServletContainerSessions();// 是否使用 Servlet 容器的會話

Shiro 還提供了 ValidatingSessionManager 用於驗資並過期會話:

void validateSessions();// 驗證所有會話是否過期

7

DefaultSessionManager:DefaultSecurityManager 使用的默認實現,用於 JavaSE 環境;
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默認實現,用於 Web 環境,其直接使用 Servlet 容器的會話;
DefaultWebSessionManager:用於 Web 環境的實現,可以替代 ServletContainerSessionManager,自己維護着會話,直接廢棄了 Servlet 容器的會話管理。

spring中配置

<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
    <property name="globalSessionTimeout" value="1800000"/>
    <property name="deleteInvalidSessions" value="true"/>
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
    <property name="sessionDAO" ref="sessionDAO"/>
</bean>

2.6.3.會話監聽器

會話監聽器用於監聽會話創建、過期及停止事件:

public class MySessionListener1 implements SessionListener {
    @Override
    public void onStart(Session session) {//會話創建時觸發
        System.out.println("會話創建:" + session.getId());
    }
    @Override
    public void onExpiration(Session session) {//會話過期時觸發
        System.out.println("會話過期:" + session.getId());
    }
    @Override
    public void onStop(Session session) {//退出/會話過期時觸發
        System.out.println("會話停止:" + session.getId());
    }  
}

如果只想監聽某一個事件,可以繼承 SessionListenerAdapter 實現:

public class MySessionListener2 extends SessionListenerAdapter {
    @Override
    public void onStart(Session session) {
        System.out.println("會話創建:" + session.getId());
    }
}

2.6.4.會話存儲 / 持久化(sessionDAO)

Shiro 提供 SessionDAO 用於會話的 CRUD,即 DAO(Data Access Object)模式實現:

//如DefaultSessionManager在創建完session後會調用該方法;如保存到關係數據庫/文件系統/NoSQL數據庫;即可以實現會話的持久化;返回會話ID;主要此處返回的ID.equals(session.getId());
Serializable create(Session session);
//根據會話ID獲取會話
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新會話;如更新會話最後訪問時間/停止會話/設置超時時間/設置移除屬性等會調用
void update(Session session) throws UnknownSessionException;
//刪除會話;當會話過期/會話停止(如用戶退出時)會調用
void delete(Session session);
//獲取當前所有活躍用戶,如果用戶量多此方法影響性能
Collection<Session> getActiveSessions();&nbsp;

Shiro 內嵌瞭如下 SessionDAO 實現:

8

AbstractSessionDAO 提供了 SessionDAO 的基礎實現,如生成會話 ID 等;CachingSessionDAO 提供了對開發者透明的會話緩存的功能,只需要設置相應的 CacheManager 即可;MemorySessionDAO 直接在內存中進行會話維護;而 EnterpriseCacheSessionDAO 提供了緩存功能的會話維護,默認情況下使用 MapCache 實現,內部使用 ConcurrentHashMap 保存緩存的會話。

Shiro 提供了使用 Ehcache 進行會話存儲,Ehcache 可以配合 TerraCotta 實現容器無關的分佈式集羣。

首先在 pom.xml 裏添加如下依賴:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.2.2</version>
</dependency>

配置 ehcache.xml

<cache name="shiro-activeSessionCache"
       maxEntriesLocalHeap="10000"
       overflowToDisk="false"
       eternal="false"
       diskPersistent="false"
       timeToLiveSeconds="0"
       timeToIdleSeconds="0"
       statistics="true"/>

Cache 的名字默認爲 shiro-activeSessionCache,即設置的 sessionDAO 的 activeSessionsCacheName 屬性值。

spring中配置

<!-- 會話ID生成器 -->
<bean id="sessionIdGenerator"
      class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 會話DAO -->
<bean id="sessionDAO"
      class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 會話驗證調度器 -->
<bean id="sessionValidationScheduler"
      class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
    <property name="sessionValidationInterval" value="1800000"/>
    <property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
    <property name="globalSessionTimeout" value="1800000"/>
    <property name="deleteInvalidSessions" value="true"/>
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
    <property name="sessionDAO" ref="sessionDAO"/>
</bean>
  • sessionDAO. activeSessionsCacheName:設置 Session 緩存名字,默認就是 shiro-activeSessionCache;
  • cacheManager:緩存管理器,用於管理緩存的,此處使用 Ehcache 實現;
  • cacheManager.cacheManagerConfigFile:設置 ehcache 緩存的配置文件;
  • securityManager.cacheManager:設置 SecurityManager 的 cacheManager,會自動設置實現了 CacheManagerAware 接口的相應對象,如 SessionDAO 的 cacheManager;

如果自定義實現 SessionDAO,繼承 CachingSessionDAO 即可:

public class MySessionDAO extends CachingSessionDAO {
    private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();
     protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        String sql = "insert into sessions(id, session) values(?,?)";
        jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));
        return session.getId();
    }
protected void doUpdate(Session session) {
    if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {
        return; //如果會話過期/停止 沒必要再更新了
    }
        String sql = "update sessions set session=? where id=?";
        jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());
    }
    protected void doDelete(Session session) {
        String sql = "delete from sessions where id=?";
        jdbcTemplate.update(sql, session.getId());
    }
    protected Session doReadSession(Serializable sessionId) {
        String sql = "select session from sessions where id=?";
        List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);
        if(sessionStrList.size() == 0) return null;
        return SerializableUtils.deserialize(sessionStrList.get(0));
    }
}

doCreate/doUpdate/doDelete/doReadSession 分別代表創建 / 修改 / 刪除 / 讀取會話;此處通過把會話序列化後存儲到數據庫實現。

2.7.實現rememberMe

Shiro 提供了記住我(RememberMe)的功能,比如訪問如淘寶等一些網站時,關閉了瀏覽器下次再打開時還是能記住你是誰,下次訪問時無需再登錄即可訪問,基本流程如下:

  1. 首先在登錄頁面選中 RememberMe 然後登錄成功;如果是瀏覽器登錄,一般會把 RememberMe 的 Cookie 寫到客戶端並保存下來;
  2. 關閉瀏覽器再重新打開;會發現瀏覽器還是記住你的;
  3. 訪問一般的網頁服務器端還是知道你是誰,且能正常訪問;
  4. 但是比如我們訪問淘寶時,如果要查看我的訂單或進行支付時,此時還是需要再進行身份認證的,以確保當前用戶還是你。

2.7.1.RememberMe 配置

spring中配置

<!-- 記住我cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMe" />
    <!-- 記住我cookie生效時間,默認單位是秒 7*24*60*60-->
    <property name="maxAge" value="604800" />
</bean>
  • sessionIdCookie:maxAge=-1 表示瀏覽器關閉時失效此 Cookie;
  • rememberMeCookie:即記住我的 Cookie,保存時長 30 天;
<!-- rememberMeManager管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <property name="cookie" ref="rememberMeCookie" />
</bean>
  • rememberMe 管理器,cipherKey 是加密 rememberMe Cookie 的密鑰;默認 AES 算法;
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="userRealm" />
    <property name="cacheManager" ref="cacheManager"/>
    <property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
  • web過濾器

    <!-- Shiro的Web過濾器 -->
      <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
          <property name="securityManager" ref="securityManager"/>
          <property name="loginUrl" value="/login/toLogin" />
          <property name="unauthorizedUrl" value="/error.html"/>
          <property name="filterChainDefinitions">
              <value>
                  /login/toLogin = anon
                  /login/checkLogin = anon
                  /login/logout = authc
                  /** = user
              </value>
          </property>
      </bean>
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.8.shiro異常處理

處理shiro異常有3種方式:
(1)使用Spring-MVC提供的SimpleMappingExceptionResolver;
(2)實現Spring的異常處理接口HandlerExceptionResolver 自定義自己的異常處理器;
(3)使用@ExceptionHandler註解實現異常處理;

2.8.1.使用Spring-MVC提供的SimpleMappingExceptionResolver;

代碼若拋出授權未通過異常即UnauthorizedException,跳轉noPermission.jsp頁面(會經過視圖解析器後跳轉)

<!--權限不足-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
        <props>
            <prop key="org.apache.shiro.authz.UnauthorizedException">/noPermission</prop>
        </props>
    </property>
</bean>

注:/noPermission 是指經過視圖解析器的頁面,這裏直接配需要跳轉到的頁面的名字即可

2.8.2.實現Spring的異常處理接口HandlerExceptionResolver 自定義自己的異常處理器;

實現HandlerExceptionResolver 接口自定義異常處理器,HandlerExceptionResolver是一個接口,只有一個方法,我們只需要實現這個接口;

我們在springMVC的配置文件中進行如下配置

<!--然後通過 Spring的HandlerExceptionResolver去進行全局捕獲,不論你在系統哪裏去throw,只要實現了 HandlerExceptionResovler這個接口,Spring都會攔截下異常進行處理  -->
<bean id="exceptionResolver" class="net.wanho.exception.MyExceptionResolver"></bean>

MyExceptionResolver

public class MyExceptionResolver implements HandlerExceptionResolver {

   public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
      System.out.println("==============異常開始=============");
         ex.printStackTrace();
      System.out.println("==============異常結束=============");
      ModelAndView mv = new ModelAndView("noPermission");
      return mv;
   }

}

2.8.3.@ExceptionHandler註解實現異常處理

將@ExceptionHandler標註在Controller的方法上,該方法將處理由@RequestMapping方法拋出的異常

首先要增加BaseController類,並在類中使用@ExceptionHandler註解聲明異常處理,代碼如下:

public class BaseController {
   /** 基於@ExceptionHandler異常處理 */
   @ExceptionHandler
   public String exp(HttpServletRequest request, Exception ex) {
      // 根據不同錯誤轉向不同頁面  
      if(ex instanceof org.apache.shiro.authz.UnauthorizedException) {
         return "noPermission";
      }else {
         return "error";
      }
   }

將需要異常處理的Controller繼承BaseController即可

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