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 的配合,实现对不一样角色或权限进行跳转拦截控制。

相关文章
相关标签/搜索