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

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

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

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

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

    首先我们要知道Hibernate五大对象:,本实例通过深入的使用这五大对象和spring+springMVC相互结合,体会到框架的好处,提高我们的开发效率 Hibernate有五大核心接口,分别是:S ...

  2. spring+springmvc+hibernate 整合

    三大框架反反复复搭了很多次,虽然每次都能搭起来,但是效率不高.最近重新搭了一次,理顺了思路,整理了需要注意的地方,分享出来. 工具:Eclipse(jdk 1.7) spring和hibernate版 ...

  3. 框架篇:Spring+SpringMVC+hibernate整合开发

    前言: 最近闲的蛋疼,搭个框架写成博客记录下来,拉通一下之前所学知识,顺带装一下逼. 话不多说,我们直接步入正题. 准备工作: 1/ IntelliJIDEA的安装配置:jdk/tomcat等..(本 ...

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

    概述 Hibernate是一款优秀的ORM框架,能够连接并操作数据库,包括保存和修改数据.Spring MVC是Java的web框架,能够将Hibernate集成进去,完成数据的CRUD.Hibern ...

  5. SSH(Spring SpringMVC Hibernate)框架整合

    项目说明: 使用SSH(Spring SpringMVC Hibernate)框架整合添加部门功能 项目结构   1.导入依赖jar包 <!--单测--> <dependency&g ...

  6. spring+springmvc+hibernate整合遇到的问题

    spring+springmvc+hibernate整合遇到的问题2016年10月20日 23:24:03 守望dfdfdf 阅读数:702 标签: ssh学习经历的异常exception异常框架更多 ...

  7. Spring+SpringMvc+Hibernate整合记录

    Spring+SpringMvc+Hibernate+Maven整合各大配置文件记录 1.Idea新建的Maven架构 2.Maven的对象模型的内容 <project xmlns=" ...

  8. 笔记48 Spring+SpringMVC+Hibernate整合

    搭建Spring+SpringMVC+Hibernate的框架的思路如下: 1.创建Maven项目,按需映入Maven包依赖. 2.搭建Spring:配置Spring对控件层Bean的注入. 3.搭建 ...

  9. springmvc框架(Spring SpringMVC, Hibernate整合)

    直接干货 model 考虑给用户展示什么.关注支撑业务的信息构成.构建成模型. control 调用业务逻辑产生合适的数据以及传递数据给视图用于呈献: view怎样对数据进行布局,以一种优美的方式展示 ...

随机推荐

  1. 浅谈[0,1]区间内的n个随机实数变量中增加偏序关系类题目的解法

    浅谈[0,1]区间内的n个随机实数变量中增加偏序关系类题目的解法 众所周知,把[0,1]区间内的n个随机.相互独立的实数变量\(x_i\)之间的大小关系写成一个排列\(\{p_i\}\),使得\(\f ...

  2. Lab2:物理内存管理

    前言 现在内存管理的方法都是非连续内存管理,也就是结合段机制和分页机制 段机制 段地址空间 进程的段地址空间由多个段组成,比如代码段.堆栈段和符号表段等等 段对应一个连续的内存"块" ...

  3. 【原】无脑操作:Webstorm集成Git/Github

    Webstorm作为前端开发的主流工具,对Git及Github可以非常简便的集成. 1.开发环境:(如何安装就不说了) ① Webstorm 2018 ② git version 2.20.1 ③ G ...

  4. 修改mysql端口后重启mysql报错:Can't start server: Bind on TCP/IP port. Got error...n denied

    1:错误信息:如下 [root@host ~]# systemctl status mariadb ● mariadb.service - MariaDB database server Loaded ...

  5. 微信小程序之判断页面来源

    1. 对非首页,使用 getCurrentPages 函数获取当前页面栈 onLoad: function (options) { let pages = getCurrentPages() if ( ...

  6. 通过inspect在电脑的Chrome上查看手机上的H5

    首先打开手机的开发者模式,(在连续点击7次版本号,系统会提示已经打开开发者模式) 然后打开一个手机浏览器. 然后在电脑上打开chrome://inspect/#devices.这是就会出现手机上浏览器 ...

  7. Appium Grid并发测试

    背景 Selenium玩的比较6的同学比较清楚:在Selenium中三大组件中有包含了Selenium Grid,而其作用就是分布式执行测试用例.主要的应用场景在于: 缩短测试执行时间,提高自动化测试 ...

  8. Centos 6.8 公钥登录

    # lsb_release -a LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch Distrib ...

  9. Springboot Actuator之十一:actuator transaction

    前言spring boot 的自动化配置其实就是在spring 的基础上做的封装,在我们之前对mvc,aop的自动化配置中可以发现–> 只是在spring 的基础上添加了一些特性,可以认为只是一 ...

  10. java 简单工具

    1.String操作 /** * 根据正则字符串过滤不需要的字符串 * @param arr * @param regex * @return */ public static String[] fi ...