Spring+SpringMVC+Hibernate 與 shiro 整合步驟

經過這篇文章你能夠了解到:java

  1. SSH 三大框架(spring + springMVC + Hiberante) 與 shiro 安全驗證框架如何整合;
  2. 經過一個示例,快速理解 shiro 框架。

1. 業務需求分析

用戶 N - 角色 N - 權限 Nmysql

咱們能夠想象一下,在平時工做中的職務,好比:業務經理,部門主管等,他們擁有不少的權力,而一個公司中不會只有一個業務經理,也不會只有一個部門主管,若是咱們要給不一樣的人分配職務權力時,每次都是具體的條條框框去分配,人累心也累。而若是咱們事先將具體的職務權力都賦予給某個具體的職務頭銜,那麼就只須要把已經定義好的職務頭銜賦予給某我的員就能夠了,擁有該職務頭銜的人,也就間接得到了對應的職務權力,就省時省力又開心了。web

這裏的人員咱們能夠定義爲用戶 User;將職務頭銜定義爲角色 Role;將具體的權力定義爲權限 Permission。spring

用戶 和 權限之間沒有直接關係,雖然在程序中也能夠掛上鉤,可是不建議這樣作,這會違背數據庫的第三範式,會形成大量的冗餘數據。sql

2. 建立數據庫

使用 MySQL 5.5,咱們首先建立一個數據庫:shiro_demo數據庫

而後在數據庫中添加剛剛業務分析須要的實體表、多對多中間關係表。apache

use shiro_demo;

-- 3個實體:用戶N - N角色N - N權限
-- 2個實體中間表:用戶多對多角色,角色多對多權限

-- 用戶表 tb_user
create table tb_user(
    user_id int PRIMARY KEY auto_increment,
    user_name varchar(50) not null,
    user_password varchar(50) not null,
    user_password_salt  varchar(100)
);

-- 角色表 tb_role
create table tb_role(
    role_id int primary key auto_increment,
    role_name   varchar(50) not null
);

-- 權限表 tb_permission
create table tb_permission(
    permission_id int PRIMARY KEY auto_increment,
    permission_name varchar(100)
);

-- 建立 3 個實體之間的多對多關係實體
-- 用戶和角色之間的多對多關係中間表 tb_user_role
-- 創建這個多對多中間表目的是符合第三範式,減小不合理的冗餘
create table tb_user_role(
    ur_id int PRIMARY KEY auto_increment,
    ur_user_id  int ,   ## 關聯用戶表的外鍵
    ur_role_id  int ## 關聯角色表的外鍵
);

-- 角色和權限之間的多對多關係中間表 tb_role_permission
create table tb_role_permission(
    rp_id int PRIMARY KEY auto_increment,
    rp_role_id int ,    ## 關聯角色表的外鍵
    rp_permission_id int ## 關聯權限表的外鍵
);

-- 插入數據
insert into tb_user(user_name, user_password) values ("zhangsan","123456");
insert into tb_role(role_name) values ("admin");
insert into tb_permission(permission_name) values ("user:insert");
insert into tb_permission(permission_name) values ("hotel:insert");
-- 給用戶 zhangsan 設置 'admin' 角色
insert into tb_user_role(ur_user_id, ur_role_id) values (1, 1);
-- 給 'admin' 角色設置 相應的權限
insert into tb_role_permission(rp_role_id, rp_permission_id) values (1,1);
insert into tb_role_permission(rp_role_id, rp_permission_id) values (1,2);

3. 建立 maven webapp 工程

循環漸進,咱們先來讓 hibernate 跑起來。先作這一塊的單元測試,沒有問題了以後再進行下一步。編程

先導入 hibernate 的依賴包,pom.xml:api

<!-- hibernate core -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>5.2.12.Final</version>
</dependency>

<!-- mysql-connector -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.45</version>
</dependency>
<!-- c3p0數據庫鏈接池 -->
<dependency>
  <groupId>com.mchange</groupId>
  <artifactId>c3p0</artifactId>
  <version>0.9.5.2</version>
</dependency>

<!-- junit 單元測試 -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
</dependency>

4. 建立實體類(POJO)

配置實體類 User:

public class TbUserEntity {

    private int userId;
    private String userName;
    private String userPassword;
    private String userPasswordSalt;
    private Set<TbRoleEntity> roles; // 用戶對應的角色集合
    
    // ... 省略 getter/setter 方法
}

配置實體類 Role:

public class TbRoleEntity {

    private int roleId;
    private String roleName;
    private Set<TbPermissionEntity> permissions; // 角色對應的權限集合
  
    // ... 省略 getter/setter 方法
}

配置實體類 Permission:

public class TbPermissionEntity {

    private int permissionId;
    private String permissionName;
  
    // ... 省略 getter/setter 方法
}

5. 配置 Hibernate 和 Mapping

hibernate 的配置咱們有兩種方式能夠選擇,一種是 hibernate 傳統的 xml 配置方式,另外一種是 JPA(Java 持久化 API)支持的註解方式。由於涉及到多對多關係的配置,雖然 JPA 註解的方式也是支持的,可是配置起來比較繁瑣,因此在例子中咱們仍是用 XML 配置文件方式,二者實現的效果是同樣的。

5.1 Hibernate 主配置文件

配置 hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.url">jdbc:mysql://localhost:3306/shiro_demo</property>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.username">root</property>
        <property name="connection.password">Cs123456</property>

        <!-- xml 配置 -->
        <value>classpath:mapper/TbUserEntity.hbm.xml</value>
        <value>classpath:mapper/TbRoleEntity.hbm.xml</value>
        <value>classpath:mapper/TbPermissionEntity.hbm.xml</value>
      
        <!-- JPA 註解配置 -->
        <!--<mapping class="com.uzipi.shiro_spring_hibernate.user.entity.TbPermissionEntity"/>-->
        <!--<mapping class="com.uzipi.shiro_spring_hibernate.user.entity.TbRoleEntity"/>-->
        <!--<mapping class="com.uzipi.shiro_spring_hibernate.user.entity.TbUserEntity"/>-->
        <!-- DB schema will be updated if needed -->
        <!-- <property name="hbm2ddl.auto">update</property> -->
    </session-factory>
</hibernate-configuration>

按照咱們建立表的對應方向,咱們只須要在 user 和 role 這兩個 xml 文件中加上多對多的配置便可。

5.2 User Mapping 配置文件:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="com.uzipi.shiro_spring_hibernate.user.entity.TbUserEntity" table="tb_user" schema="shiro_demo">
        <id name="userId" column="user_id">
            <generator class="native"/> <!-- 主鍵生成策略:依據本地數據庫特性 -->
        </id>
        <property name="userName" column="user_name"/>
        <property name="userPassword" column="user_password"/>
        <property name="userPasswordSalt" column="user_password_salt"/>

        <!-- 配置多對多關係 -->
        <!--
            須要在實體類中配置對應的 Set 集合
            name:表示該 Set 集合屬性名
            table:表示數據庫中肯定兩個表之間多對多關係的表
            <key column="">:指定的字段名是當前配置文件 <class> 所對應的表在中間表中的外鍵
         -->
        <set name="roles" table="tb_user_role">
            <key column="ur_user_id"></key>
            <many-to-many column="ur_role_id"
                          class="com.uzipi.shiro_spring_hibernate.user.entity.TbRoleEntity"/>
        </set>
    </class>
</hibernate-mapping>

5.3 Role Mapping 配置文件

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="com.uzipi.shiro_spring_hibernate.user.entity.TbRoleEntity" table="tb_role" schema="shiro_demo">
        <id name="roleId" column="role_id">
            <generator class="native"/>
        </id>
        <property name="roleName" column="role_name"/>

        <!-- 配置多對多關係 -->
        <!--
            須要在實體類中配置對應的 Set 集合
            name:表示該 Set 集合屬性名
            table:表示數據庫中肯定兩個表之間多對多關係的表
            <key column="">:指定的字段名是當前配置文件 <class> 所對應的表在中間表中的外鍵
         -->
        <set name="permissions" table="tb_role_permission">
            <key column="rp_role_id"></key>
            <many-to-many column="rp_permission_id"
                          class="com.uzipi.shiro_spring_hibernate.user.entity.TbPermissionEntity"/>
        </set>
    </class>
</hibernate-mapping>

5.4 Permission Mappint 配置文件

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="com.uzipi.shiro.user.entity.TbPermissionEntity" table="tb_permission"
           schema="shiro_demo">
        <id name="permissionId" column="permission_id"/>
        <property name="permissionName" column="permission_name"/>
    </class>
</hibernate-mapping>

5.5 測試 Hibernate 配置是否成功

/**
 * 測試一下Hibernate
 */
public class HibernateTest {
    @Test
    public void testHiberante(){
        Configuration configure = new Configuration().configure();
        SessionFactory sessionFactory = configure.buildSessionFactory();
        Session session = sessionFactory.openSession();
        TbUserEntity user = session.get(TbUserEntity.class, 1);
        System.out.println("user = " + user.getUserName());
        System.out.println("該用戶擁有的角色數量:" + user.getRoles().size());
        TbRoleEntity role = user.getRoles().iterator().next();
        System.out.println("該角色擁有的權限數量:" + role.getPermissions().size());
        session.close();
        sessionFactory.close();
    }
}

在這裏小結一下:由 hibernate 完成查詢數據庫中用戶、角色、權限等信息的工做。接下來 hibernate 將這些信息交給 shiro 進行安全驗證的處理。

6. 配置 Spring

導入 Spring 的依賴包,pom.xml:

<!-- javax.servlet-api  spring 依賴於 servlet -->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>4.0.0</version>
  <scope>provided</scope>
</dependency>
<!-- spring-webmvc -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>
<!-- spring-web -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>
<!-- spring-orm -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>
<!-- spring-tx transaction -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>4.3.12.RELEASE</version>
</dependency>

須要注意的是:

由於 spring mvc 的核心類 DispatcherServlet 是依賴於 Servlet的,因此還須要導入 Servlet。

6.1 spring 與 hibernate 整合

爲了不一個 Spring ContextApplication 配置文件中的內容太多太雜,咱們考慮將 spring-hibernate 的整合配置單獨放在一個 xml 文件中,首先建立一個 spring-hibernate.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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 整合 Hibernate 配置 BEGIN -->
    <!-- dataSource -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://wangchm-PC:3306/shiro_demo" />
        <property name="user" value="root" />
        <property name="password" value="Cs123456" />
    </bean>
    <!-- sessionFactory -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mappingLocations">
            <list>
                <value>mapper/TbUserEntity.hbm.xml</value>
                <value>mapper/TbRoleEntity.hbm.xml</value>
                <value>mapper/TbPermissionEntity.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
            </props>
        </property>
    </bean>
    <!-- transactionManager -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- 整合 Hibernate 配置 END -->

</beans>

而後咱們再建立一個 spring.xml ,這個纔是 spring 框架的核心配置文件:

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

    <context:annotation-config />
    <context:component-scan base-package="com.uzipi.shiro"></context:component-scan>
    <mvc:annotation-driven />
    <mvc:default-servlet-handler />

    <!-- 引入 spring 與 hibernate 整合配置 -->
    <import resource="spring-hibernate.xml"/>

</beans>

注意到了嗎?在 spring.xml 文件中,咱們使用 <import resource="spring-hibernate.xml"/> 引入剛剛建立的spring-hibernate.xml 配置文件,也算是實現了配置文件之間的 「解耦」 吧。

6.2 建立 UserDAO

建立一個 IUserDAO 接口(面向接口編程):

package com.uzipi.shiro.user.dao;

import com.uzipi.shiro.user.entity.TbUserEntity;

public interface IUserDAO {

    /**
     * 登陸
     * @param user
     * @return
     */
    TbUserEntity findUserForLogin(TbUserEntity user);
}

而後建立接口的實現類 UserDAO:

package com.uzipi.shiro.user.dao.impl;

import com.uzipi.shiro.user.dao.IUserDAO;
import com.uzipi.shiro.user.entity.TbUserEntity;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.CriteriaQueryImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Repository
public class UserDAO implements IUserDAO {

    @Resource
    private SessionFactory sessionFactory; // 注入 Hibernate session 工廠

    @Override
    @Transactional // 加入事務管理
    public TbUserEntity findUserForLogin(TbUserEntity user) {
        TbUserEntity loginUser = sessionFactory.getCurrentSession()
                .createQuery("from TbUserEntity u " +
                        " where u.userName=:userName " +
                        " and u.userPassword=:userPassword ", TbUserEntity.class)
                .setParameter("userName", user.getUserName())
                .setParameter("userPassword", user.getUserPassword())
                .getResultList().get(0);
        return loginUser;
    }
}

有幾個知識點說明一下:

  1. @Repository 註解 表示將這個 dao 類交給 spring 管理,且說明了這是一個操做數據庫的類
  2. @Resource 註解 表示自動注入類,固然也能夠用 @Autowired 替換(注意兩個註解仍是有一點點區別的哦)
  3. @Transactional 註解 表示該註解的方法受到 spring 事務管理,也就是說這一個方法就是一個事務,必須加上這個註解,不然 spring 沒法爲 hibernate 開啓 session。
  4. 使用 hibernate 的 HQL 語句進行查詢,寫法相似 SQL,可是能夠用面向對象的方式操做數據實體。

你們可能以爲奇怪,爲何要在 配置 Spring 這一節中建立 UserDAO,目的很簡單,就是爲了用這個 DAO 來測試一下咱們的 Spring 和 Hibernate 是否整合成功嘛 ^_^

6.3 測試 spring 與 hibernate 的整合

寫一個測試類,用到了 spring-test(不得不說,spring 提供的配套功能真多):

咱們先導入 spring-text 依賴包:

<!-- spring-test -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>4.3.12.RELEASE</version>
  <scope>test</scope>
</dependency>

而後編寫測試類:

import com.uzipi.shiro.user.dao.IUserDAO;
import com.uzipi.shiro.user.entity.TbUserEntity;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

/**
 * 使用 spring test 的註解
 * 幫助咱們快速建立 spring context
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class SpringTest {

    @Resource
    private IUserDAO userDAO;

    @Test
    public void testSpring(){
        // 使用 Spring test 測試
        TbUserEntity user = new TbUserEntity();
        user.setUserName("zhangsan");
        user.setUserPassword("123456");
        TbUserEntity userForLogin = userDAO.findUserForLogin(user);
        // 斷言從數據庫中查詢出來的結果與咱們給定的字符串相等
        Assert.assertEquals("zhangsan", userForLogin.getUserName());
    }
}

運行測試,斷言成功,說明 spring 與 hibernate 整合成功了。

6.4 配置 SpringMVC

(1)在 spring.xml 中加入視圖解析器的配置

<!-- SpringMVC 視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
  <!-- 前綴 -->
  <property name="prefix" value="/WEB-INF/pages/"/>
  <!-- 後綴 -->
  <property name="suffix" value=".jsp"/>
</bean>

6.4 配置 web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <welcome-file-list>
    <welcome-file>login</welcome-file>
  </welcome-file-list>

    <!-- 在 shiro 以前,須要先加載 spring 到上下文環境 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>

  <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
  <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>

  <!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
  <!-- requests.  Usually this filter mapping is defined first (before all others) to -->
  <!-- ensure that Shiro works in subsequent filters in the filter chain:             -->
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

    <!-- 啓動監聽器,須要放在 shiroFilter 與 springMVC 的配置之間 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

  <!-- spring MVC 的配置要放在 shiroFilter 以後 -->
  <servlet>
    <servlet-name>springMVC</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  
</web-app>

在 web.xml 的配置中,有一些知識點須要注意:

  1. <context-param> 配置 spring.xml 的加載路徑,須要放在最前面(也在 shiroFilter 以前);
  2. shiroFilter 這個過濾器採用了委派代理模式 Delegating Proxy ,其代理的是 bean shiroFilter,也就是說,shiroFilter 的核心是在 spring bean 中定義的,調用 web.xml 的 shiroFilter 實質上調用是 spring bean 中的 shiroFilter。關於 shiroFilter 的配置將在下面一節講到。
  3. 爲了符合 web.xml 的文檔規範,<listener> 須要放在 <filter><servlet> 之間。

7. 配置 Shiro 與 spring 整合

首先咱們先要導入 shiro 與 spring 整合的依賴包,pom.xml:

<!-- shiro-spring 整合 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>1.4.0</version>
</dependency>

而後根據 Apache shiro 官方網站提供的配置模版:

建立 spring-shiro.xml 文件,複製 shiro 官方提供的配置模版,並作一些修改:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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.xsd">

    <!-- shiro 的核心,web.xml中委派代理的實質內容就在這裏定義 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
      <property name="securityManager" ref="securityManager"/>
      <!-- 沒有登陸的用戶請求,將會返回到這個地址 -->
      <property name="loginUrl" value="/login"/> 
      <!-- <property name="successUrl" value="/home.jsp"/> -->
      <!-- <property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
      <property name="filterChainDefinitions">
        <value>
          <!--/admin/** = authc, roles[admin]-->
          <!--/docs/** = authc, perms[document:read]-->
          /index = authc
        </value>
      </property>
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 單 Realm。若是是多 Realm 須要配置爲 'realms' -->
        <property name="realm" ref="myRealm"/>
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 自定義 Realm 的類 -->
    <bean id="myRealm" class="com.uzipi.shiro.user.shiro.HibernateRealm">
    </bean>
</beans>

須要注意:

  1. bean shiroFilter 要與 web.xml 中的 filter shiroFilter 名稱同樣。這裏的 shiroFilter 配置也就是 web.xml 中所用到的委派代理的實質內容。
  2. shiroFilter 屬性中配置了 filterChainDefinitions ,這個屬性中配置的是須要對哪些資源的請求進行攔截,anon 表示該資源不須要 shiro 控制,authc 表示須要通過 shiro 的身份和權限驗證,經過驗證的才能訪問的資源。配置支持通配符,可參考 shiro 官網模版的提示。
  3. 配置 securityManager 須要指明 realm,這裏咱們使用到了自定義 Realm,下面咱們會建立這個自定義 Realm 類,固然咱們也可使用本地文件配置方式的 Realm,或者 shiro 提供的 jdbcRealm 模版(這個模板對數據庫表的表名和字段名要求比較嚴格,可拓展性比較弱,適合小型快速開發的項目)

接着咱們將 spring-shiro.xml 引入到 spring.xml ,實現 spring 與 shiro 的整合。

<!-- 引入 spring 與 shiro 整合配置 -->
<import resource="spring-shiro.xml"/>

8. 建立自定義 Realm

Realm 是 shiro 框架的身份、權限等信息的數據源。

當咱們使用 shiro 去驗證某個用戶的身份信息(好比賬號、密碼)或者是要驗證某個用戶所擁有的角色和權限時,shiro 就會從這個 Realm 中查找對應的身份、角色、權限等信息。

建立自定義的 Realm,其實就是在建立一個咱們自定義的登陸身份認證和權限驗證的邏輯。

好比,有的時候業務需求規定,不能僅僅靠用戶名和密碼來判斷一個用戶的身份,有可能還須要經過用戶的手機、微信等等方式來驗證,那麼僅靠 shiro 提供的模版 Realm 就不太夠用,須要咱們建立自定義 Realm。

Realm 有多種配置選擇:

  1. Realm 中的信息內容能夠是固定死的,好比在 Realm 中咱們用 if 來判斷一個用戶名是否爲 "zhangsan",那麼這個系統就只容許賬號爲"zhangsan"的人使用,其餘人都不能使用;
  2. Realm 域信息也能夠寫在本地文件中,可是不夠靈活;
  3. Realm 域中的內容也能夠經過讀取數據庫中的信息,達到動態更新 Realm 內容的目的。

自定義 Realm 需要繼承抽象類 AuthorizingRealm,而且重寫兩個方法:

  1. doGetAuthorizationInfo:獲取角色受權的驗證信息
  2. doGetAuthenticationInfo:獲取登陸身份的認證信息

雖然 shiro 沒有強制性地規定,但咱們仍是須要重寫一下 getName() 方法,該方法用於獲取當前 Realm 的名稱。

8.1 建立 Realm 類

package com.uzipi.shiro.user.shiro;

import com.uzipi.shiro.user.dao.IUserDAO;
import com.uzipi.shiro.user.entity.TbUserEntity;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.Set;

public class HibernateRealm extends AuthorizingRealm{

    @Resource
    private IUserDAO userDAO;  // 注入 userDAO

    /**
     * 獲取一個全局惟一的 Realm 名稱,能夠自定義,最好是不容易重複的
     */
    @Override
    public String getName(){
        return this.getClass().toString();
    }

    /**
     * 權限驗證的方法
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = principals.getPrimaryPrincipal().toString();
        Set<String> roleNameSet = userDAO.findRoleNameByUsername(username);
        Set<String> permissionNameSet = userDAO.findPermissionNameByUserName(username);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roleNameSet); // 將角色名集合加入驗證信息
        simpleAuthorizationInfo.setStringPermissions(permissionNameSet); // 權限名加入驗證信息
        return simpleAuthorizationInfo;
    }

    /**
     * 登陸認證的方法
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        // 轉型
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername(); // 獲取 用戶名
        // 獲取 密碼,字符數組須要轉型爲 String
        String password = new String(upToken.getPassword());
        TbUserEntity user = new TbUserEntity();
        user.setUserName(username);
        user.setUserPassword(password);
        // 如下是登陸認證的邏輯
        TbUserEntity userForLogin = userDAO.findUserForLogin(user);
        if (userForLogin != null){
            // 身份認證成功,返回 SimpleAuthenticationInfo 對象
            return new SimpleAuthenticationInfo(
                    userForLogin.getUserName(), // 參數1:用戶名
                    userForLogin.getUserPassword(), // 參數2:密碼
                    this.getName() // 參數3:當前 Realm 的名稱
                    );
        } else {
            // 身份認證失敗
            throw new AuthenticationException("用戶名或密碼錯誤!");
        }
    }
}

從代碼上咱們能夠看到:

  1. doGetAuthorizationInfo 方法爲了獲取用戶的權限驗證信息,須要藉助咱們編寫的邏輯功能方法:findRoleNameByUsername(String username)findPermissionNameByUserName(String username) ,做用是按已登陸的用戶名,查詢出該用戶對應的所有角色,以及角色下對應的全部權限,並將這些信息加入到 SimpleAuthorizationInfo 對象中,shiro 在進行權限驗證時,經過自定義 Realm 返回的 SimpleAuthorizationInfo 就能夠自動爲咱們攔截不符合權限之外的非法操做。
  2. 例子中,獲取用戶登陸身份認證的邏輯比較簡單,經過 userDAO.findUserForLogin(user) 查詢數據庫中匹配用戶名和密碼的記錄,若能找到對應的記錄,則登陸認證經過,不然登陸認證失敗。shiro 中判斷一個用戶登陸失敗的方式是直接拋出一個 AuthenticationException 異常。

8.2 UserDAO 中增長查詢角色和權限的方法

在自定義 Realm 類中,用到了 UserDAO 中的獲取角色名集合和權限集合的方法,咱們在 UserDAO 中作定義。

在 6.2 一節中,咱們已經建立 UserDAO 實現類,並進行了測試,如今咱們需要在 IUserDAO 接口和實現類中增長兩個方法:findRoleNameByUsernamefindPermissionNameByUserName

新的 UserDAO 代碼以下:

package com.uzipi.shiro.user.dao.impl;

import com.uzipi.shiro.user.dao.IUserDAO;
import com.uzipi.shiro.user.entity.TbPermissionEntity;
import com.uzipi.shiro.user.entity.TbRoleEntity;
import com.uzipi.shiro.user.entity.TbUserEntity;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Repository
public class UserDAO implements IUserDAO {

    @Resource
    private SessionFactory sessionFactory; // 注入 Hibernate session 工廠

    @Override
    @Transactional // 指定當前方法的事務
    public TbUserEntity findUserForLogin(TbUserEntity user) {
        List<TbUserEntity> list = sessionFactory.getCurrentSession()
                .createQuery("from TbUserEntity u " +
                        " where u.userName=:userName " +
                        " and u.userPassword=:userPassword ", TbUserEntity.class)
                .setParameter("userName", user.getUserName())
                .setParameter("userPassword", user.getUserPassword())
                .getResultList();
        // 查詢結果是否爲空
        if (list == null || list.isEmpty()){
            return null;
        }
        return list.get(0);
    }

    @Override
    @Transactional // 指定當前方法的事務
    public Set<String> findRoleNameByUsername(String username) {
        List<TbUserEntity> list = sessionFactory.getCurrentSession()
                .createQuery("from TbUserEntity u " +
                        " where u.userName=:userName", TbUserEntity.class)
                .setParameter("userName", username)
                .getResultList();
        // 查詢結果是否爲空
        if (list == null || list.isEmpty()){
            return null;
        }
        TbUserEntity user = list.get(0);
        Set<String> roleNameSet =  new HashSet<>();
        for (TbRoleEntity role : user.getRoles()) {
            roleNameSet.add(role.getRoleName());
        }
        return roleNameSet;
    }

    @Override
    @Transactional // 指定當前方法的事務
    public Set<String> findPermissionNameByUserName(String username) {
        List<TbUserEntity> list = sessionFactory.getCurrentSession()
                .createQuery("from TbUserEntity u " +
                        " where u.userName=:userName", TbUserEntity.class)
                .setParameter("userName", username)
                .getResultList();
        // 查詢結果是否爲空
        if (list == null || list.isEmpty()){
            return null;
        }
        TbUserEntity user = list.get(0); // 查詢到用戶
        Set<String> permissionNameSet = new HashSet<>();
        // 遍歷用戶對應的全部角色
        for (TbRoleEntity role : user.getRoles()) {
            Set<TbPermissionEntity> permissionSet = new HashSet<>();
            // 遍歷角色對應的全部權限
            for (TbPermissionEntity permission : permissionSet) {
                permissionNameSet.add(permission.getPermissionName());
            }
        }
        return permissionNameSet;
    }
}

9. 建立 Controller

建立 AuthController 實現登陸認證相關的跳轉控制

package com.uzipi.shiro.user.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class AuthController {

    /**
     * 跳轉到登陸頁
     * @return
     */
    @RequestMapping("/login")
    public String forwardToLogin(){
        return "login";
    }

    /**
     * 登陸
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/login.do")
    public String login(String username, String password){
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try{
            SecurityUtils.getSubject().login(token);
            return "home"; // 登陸身份驗證成功,跳轉到我的頁 home.jsp
        } catch (AuthenticationException ace){
            ace.printStackTrace();
        }
        return "login"; // 登陸認證失敗,返回 login.jsp 頁面要求繼續認證
    }

    /**
     * 退出登陸
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/logout.do")
    public String logout(String username, String password){
        Subject subject = SecurityUtils.getSubject();
        // 當前用戶是否爲登陸狀態,已登陸狀態則登出
        if (subject.isAuthenticated()) {
            subject.logout();
        }
        return "login"; // 退出登陸,並返回到登陸頁面
    }

}

10. 建立 JSP 頁面

10.1 建立 login.jsp 頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>用戶登陸</title>
    <base href="<%=request.getContextPath()%>/"/>
</head>
<body>
    <form action="login.do" method="post">
        <input type="text" name="username" placeholder="請輸入用戶名"/> <br>
        <input type="password" name="password" placeholder="請輸入密碼"/> <br>
        <input type="checkbox" name="rememberMe" />記住我 <br>
        <input type="submit" value="登陸" />
    </form>
</body>
</html>

10.2 建立 home.jsp 頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE html>
<html>
<head>
    <title>登陸成功頁</title>
    <base href="<%=request.getContextPath()%>/"/>
</head>
<body>
    你好,<shiro:principal/>
    <br>
    <shiro:hasRole name="admin">
        你的角色是:管理員
    </shiro:hasRole>
    <br>
    <a href="logout.do">安全退出</a>
</body>
</html>

使用 shiro 的標籤:

<shiro:principal/> 用於顯示當前登陸認證經過的用戶;

<shiro:hasRole name="admin">
  當前登錄認證經過的用戶,若是擁有 "admin" 角色(也就是經過自定義 Realm 配置的角色),就能夠渲染顯示標籤對中的內容,不然在最終頁面中不渲染。
</shiro:hasRole>

至此,spring + spring mvc + hibernate + shiro 的框架整合就已經完成了。

後面我還會寫一篇文章,具體講解如何經過 shiro 和 controller 的配合,實現對不一樣角色或權限進行跳轉攔截控制。

相關文章
相關標籤/搜索