目录html
经过这篇文章你能够了解到:java
用户 N - 角色 N - 权限 Nmysql
咱们能够想象一下,在平时工做中的职务,好比:业务经理,部门主管等,他们拥有不少的权力,而一个公司中不会只有一个业务经理,也不会只有一个部门主管,若是咱们要给不一样的人分配职务权力时,每次都是具体的条条框框去分配,人累心也累。而若是咱们事先将具体的职务权力都赋予给某个具体的职务头衔,那么就只须要把已经定义好的职务头衔赋予给某我的员就能够了,拥有该职务头衔的人,也就间接得到了对应的职务权力,就省时省力又开心了。web
这里的人员咱们能够定义为用户 User;将职务头衔定义为角色 Role;将具体的权力定义为权限 Permission。spring
用户 和 权限之间没有直接关系,虽然在程序中也能够挂上钩,可是不建议这样作,这会违背数据库的第三范式,会形成大量的冗余数据。sql
使用 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);
循环渐进,咱们先来让 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>
配置实体类 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 方法 }
hibernate 的配置咱们有两种方式能够选择,一种是 hibernate 传统的 xml 配置方式,另外一种是 JPA(Java 持久化 API)支持的注解方式。由于涉及到多对多关系的配置,虽然 JPA 注解的方式也是支持的,可是配置起来比较繁琐,因此在例子中咱们仍是用 XML 配置文件方式,二者实现的效果是同样的。
配置 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 文件中加上多对多的配置便可。
<?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>
<?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>
<?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>
/** * 测试一下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 进行安全验证的处理。
导入 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。
为了不一个 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
配置文件,也算是实现了配置文件之间的 “解耦” 吧。
建立一个 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; } }
有几个知识点说明一下:
你们可能以为奇怪,为何要在 配置 Spring
这一节中建立 UserDAO,目的很简单,就是为了用这个 DAO 来测试一下咱们的 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 整合成功了。
(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>
<!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 的配置中,有一些知识点须要注意:
<context-param>
配置 spring.xml 的加载路径,须要放在最前面(也在 shiroFilter 以前);shiroFilter
这个过滤器采用了委派代理模式 Delegating Proxy ,其代理的是 bean shiroFilter,也就是说,shiroFilter 的核心是在 spring bean 中定义的,调用 web.xml 的 shiroFilter 实质上调用是 spring bean 中的 shiroFilter。关于 shiroFilter 的配置将在下面一节讲到。<listener>
须要放在 <filter>
与 <servlet>
之间。首先咱们先要导入 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>
须要注意:
shiroFilter
属性中配置了 filterChainDefinitions
,这个属性中配置的是须要对哪些资源的请求进行拦截,anon
表示该资源不须要 shiro 控制,authc
表示须要通过 shiro 的身份和权限验证,经过验证的才能访问的资源。配置支持通配符,可参考 shiro 官网模版的提示。securityManager
须要指明 realm,这里咱们使用到了自定义 Realm,下面咱们会建立这个自定义 Realm 类,固然咱们也可使用本地文件配置方式的 Realm,或者 shiro 提供的 jdbcRealm 模版(这个模板对数据库表的表名和字段名要求比较严格,可拓展性比较弱,适合小型快速开发的项目)接着咱们将 spring-shiro.xml
引入到 spring.xml
,实现 spring 与 shiro 的整合。
<!-- 引入 spring 与 shiro 整合配置 --> <import resource="spring-shiro.xml"/>
Realm 是 shiro 框架的身份、权限等信息的数据源。
当咱们使用 shiro 去验证某个用户的身份信息(好比账号、密码)或者是要验证某个用户所拥有的角色和权限时,shiro 就会从这个 Realm 中查找对应的身份、角色、权限等信息。
建立自定义的 Realm,其实就是在建立一个咱们自定义的登陆身份认证和权限验证的逻辑。
好比,有的时候业务需求规定,不能仅仅靠用户名和密码来判断一个用户的身份,有可能还须要经过用户的手机、微信等等方式来验证,那么仅靠 shiro 提供的模版 Realm 就不太够用,须要咱们建立自定义 Realm。
Realm 有多种配置选择:
if
来判断一个用户名是否为 "zhangsan",那么这个系统就只容许账号为"zhangsan"的人使用,其余人都不能使用;自定义 Realm 需要继承抽象类 AuthorizingRealm
,而且重写两个方法:
doGetAuthorizationInfo
:获取角色受权的验证信息doGetAuthenticationInfo
:获取登陆身份的认证信息虽然 shiro 没有强制性地规定,但咱们仍是须要重写一下 getName()
方法,该方法用于获取当前 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("用户名或密码错误!"); } } }
从代码上咱们能够看到:
doGetAuthorizationInfo
方法为了获取用户的权限验证信息,须要借助咱们编写的逻辑功能方法:findRoleNameByUsername(String username)
和 findPermissionNameByUserName(String username)
,做用是按已登陆的用户名,查询出该用户对应的所有角色,以及角色下对应的全部权限,并将这些信息加入到 SimpleAuthorizationInfo
对象中,shiro 在进行权限验证时,经过自定义 Realm 返回的 SimpleAuthorizationInfo
就能够自动为咱们拦截不符合权限之外的非法操做。userDAO.findUserForLogin(user)
查询数据库中匹配用户名和密码的记录,若能找到对应的记录,则登陆认证经过,不然登陆认证失败。shiro 中判断一个用户登陆失败的方式是直接抛出一个 AuthenticationException
异常。在自定义 Realm 类中,用到了 UserDAO 中的获取角色名集合和权限集合的方法,咱们在 UserDAO 中作定义。
在 6.2 一节中,咱们已经建立 UserDAO 实现类,并进行了测试,如今咱们需要在 IUserDAO 接口和实现类中增长两个方法:findRoleNameByUsername
和 findPermissionNameByUserName
。
新的 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; } }
建立 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"; // 退出登陆,并返回到登陆页面 } }
<%@ 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>
<%@ 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 的配合,实现对不一样角色或权限进行跳转拦截控制。