目錄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 的配合,實現對不一樣角色或權限進行跳轉攔截控制。