Spring+SpringMVC+Hibernate 与 shiro 整合步骤

通过这篇文章你可以了解到:

  1. SSH 三大框架(spring + springMVC + Hiberante) 与 shiro 安全验证框架如何整合;
  2. 通过一个示例,快速理解 shiro 框架。

[TOC]

1. 业务需求分析

用户 N - 角色 N - 权限 N

我们可以想象一下,在平时工作中的职务,比如:业务经理,部门主管等,他们拥有很多的权力,而一个公司中不会只有一个业务经理,也不会只有一个部门主管,如果我们要给不同的人分配职务权力时,每次都是具体的条条框框去分配,人累心也累。而如果我们事先将具体的职务权力都赋予给某个具体的职务头衔,那么就只需要把已经定义好的职务头衔赋予给某个人员就可以了,拥有该职务头衔的人,也就间接获得了对应的职务权力,就省时省力又开心了。

这里的人员我们可以定义为用户 User;将职务头衔定义为角色 Role;将具体的权力定义为权限 Permission。

用户 和 权限之间没有直接关系,虽然在程序中也可以挂上钩,但是不建议这样做,这会违背数据库的第三范式,会造成大量的冗余数据。

2. 创建数据库

使用 MySQL 5.5,我们首先创建一个数据库:shiro_demo

然后在数据库中添加刚刚业务分析需要的实体表、多对多中间关系表。

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:

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

原文地址:https://www.cnblogs.com/uzipi/p/8372620.html

时间: 2024-10-29 10:46:57

Spring+SpringMVC+Hibernate 与 shiro 整合步骤的相关文章

springMVC系列之(四) spring+springMVC+hibernate 三大框架整合(转)

https://blog.csdn.net/lishehe/article/details/38356261 基于spring+springmvc+hibernate的maven项目搭建 https://blog.csdn.net/fancy_t/article/details/70989754 原文地址:https://www.cnblogs.com/1987721594zy/p/9708567.html

spring+springMVC+hibernate 三大框架整合

首先我们要知道hibernate五大对象:,本实例通过深入的使用这五大对象和spring+springMVC相互结合,体会到框架的好处,提高我们的开发效率 Hibernate有五大核心接口,分别是:Session Transaction Query SessionFactoryConfiguration .这五个接口构成了Hibernate运行的基本要素,可以执行存取,持久化,事务管理等操作.这五个接口可以位于系统的业务逻辑层和持久化层.下面是一张Hibernate的关系图: Session接口

Spring MVC基础知识整理?Spring+SpringMVC+Hibernate整合操作数据库

概述 Hibernate是一款优秀的ORM框架,能够连接并操作数据库,包括保存和修改数据.Spring MVC是Java的web框架,能够将Hibernate集成进去,完成数据的CRUD.Hibernate使用方便,配置响应的XML文件即可.由于spring3.x,基于asm的某些特征,而这些asm还没有用jdk8编译,所以采用Spring 3+JDK8就会报错,提示错误信息( java.lang.IllegalArgumentException),具体解决方案有:1.Spring 3+JDK7

spring+springmvc+hibernate整合实例

上篇博文中写了spring与springmvc的整合,而这一篇则是又加上了hibernate. 与上次一样,这一次仍然是先导入jar包,这一次则要加入hibernate中的jar包,如下图所示: 同时再新建两个源文件夹,一个为config,一个为test,分别存放配置文件与测试用例,现在来进行spring,springmvc以及hibernate的配置. 新建spring-hibernate.xml,applicationContext.xml,springmvc.xml,hibernate.c

Spring mvc 源码 和 Spring+springmvc+hibernate整合源码

本来想着再写下spring+springmvc+hibernate整合的文章 暂时就不写了 直接附上源码,有需要的直接下载看吧.还有前面文章中的springmvc源码  前面文章只是简单的说了下 搭建环境 访问页面成功.这个源码中有模拟的增删改查和文件上传,转换json 实体类验证等功能.有问题可以留言我. http://download.csdn.net/detail/qinyanbin123/8631175 springmvc源码下载地址 http://download.csdn.net/d

idea spring+springmvc+mybatis环境配置整合详解

idea spring+springmvc+mybatis环境配置整合详解 1.配置整合前所需准备的环境: 1.1:jdk1.8 1.2:idea2017.1.5 1.3:Maven 3.5.2 2.查看idea中是否安装Maven插件: 2.1:File --> Settings --> Plugins 2.2:如下图所示的步骤进行操作(注:安装完插件,idea会重新启动) 3.idea创建Maven项目的步骤 4.搭建目录结构 下图就是我搭建Maven项目之后,添加对应的目录和文件 5.p

spring+springmvc+hibernate架构、maven分模块开发例子小项目案例

maven分模块开发例子小项目案例 spring+springmvc+hibernate架构 以用户管理做测试,分dao,sevices,web层,分模块开发测试!因时间关系,只测查询成功,其他的准备在ext上做个完整的案例来的,可惜最近时间很紧, 高级部分也没做测试,比如建私服,其他常用插件测试之类的,等用时间了我做个完整ext前端和maven 完整的例子出来,在分享吧! 不过目前这些撑握了,在项目中做开发是没有问题的,其他高级部分是架构师所做的. 之前我有的资源都加上了积分,有些博友向我要,

spring+springmvc+hibernate架构、maven分模块开发样例小项目案例

maven分模块开发样例小项目案例 spring+springmvc+hibernate架构 以用户管理做測试,分dao,sevices,web层,分模块开发測试!因时间关系.仅仅測查询成功.其它的准备在ext上做个完整的案例来的,可惜近期时间非常紧. 高级部分也没做測试,比方建私服,其它经常使用插件測试之类的,等用时间了我做个完整ext前端和maven 完整的样例出来,在分享吧. 只是眼下这些撑握了.在项目中做开发是没有问题的,其它高级部分是架构师所做的. 之前我有的资源都加上了积分,有些博友

Spring+SpringMVC+MyBatis+Maven框架整合

本文记录了Spring+SpringMVC+MyBatis+Maven框架整合的记录,主要记录以下几点 一.Maven需要引入的jar包 二.Spring与SpringMVC的配置分离 三.Spring与MyBatis的整合 一.Maven需要引入的jar包 本文默认读者已经掌握Maven的使用,Maven配置片段如下 Xml代码   <!-- 引入spring-webmvc与spring-jdbc --> <dependency> <groupId>org.sprin