160720、SSM-Shiro使用詳解


前言

相比有做過企業級開發的童鞋應該都有做過權限安全之類的功能吧,最先開始我采用的是建用戶表,角色表,權限表,之后在攔截器中對每一個請求進行攔截,再到數據庫中進行查詢看當前用戶是否有該權限,這樣的設計能滿足大多數中小型系統的需求。不過這篇所介紹的Shiro能滿足之前的所有需求,並且使用簡單,安全性高,而且現在越來越的多企業都在使用Shiro,這應該是一個收入的你的技能庫。


創建自定義MyRealm

有關Shiro的基礎知識我這里就不過多介紹了,直接來干貨,到最后會整合Spring來進行權限驗證。
首先在使用Shiro的時候我們要考慮在什么樣的環境下使用:
- 登錄的驗證
- 對指定角色的驗證
- 對URL的驗證

基本上我們也就這三個需求,所以同時我們也需要三個方法:
1. findUserByUserName(String username)根據username查詢用戶,之后Shiro會根據查詢出來的User的密碼來和提交上來的密碼進行比對。
2. findRoles(String username)根據username查詢該用戶的所有角色,用於角色驗證。
3. findPermissions(String username)根據username查詢他所擁有的權限信息,用於權限判斷。

下面我貼一下我的mapper代碼(PS:該項目依然是基於之前的SSM,不太清楚整合的請看SSM一)。

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.crossoverJie.dao.T_userDao" > <resultMap id="BaseResultMap" type="com.crossoverJie.pojo.T_user" > <result property="id" column="id"/> <result property="userName" column="userName"/> <result property="password" column="password"/> <result property="roleId" column="roleId"/> </resultMap> <sql id="Base_Column_List" > id, username, password,roleId </sql> <select id="findUserByUsername" parameterType="String" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from t_user where userName=#{userName} </select> <select id="findRoles" parameterType="String" resultType="String"> select r.roleName from t_user u,t_role r where u.roleId=r.id and u.userName=#{userName} </select> <select id="findPermissions" parameterType="String" resultType="String"> select p.permissionName from t_user u,t_role r,t_permission p where u.roleId=r.id and p.roleId=r.id and u.userName=#{userName} </select> </mapper>

很簡單只有三個方法,分別對應上面所說的三個方法。對sql稍微熟悉點的童鞋應該都能看懂,不太清楚就拷到數據庫中執行一下就行了,數據庫的Sql也在我的github上。實體類就比較簡單了,就只有四個字段以及get,set方法。我就這里就不貼了,具體可以去githubfork我的源碼。

現在就需要創建自定義的MyRealm類,這個還是比較重要的。繼承至ShiroAuthorizingRealm類,用於處理自己的驗證邏輯,下面貼一下我的代碼:

package com.crossoverJie.shiro; import com.crossoverJie.pojo.T_user; import com.crossoverJie.service.T_userService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; 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; /** * Created with IDEA * Created by ${jie.chen} on 2016/7/14. * Shiro自定義域 */ public class MyRealm extends AuthorizingRealm { @Resource private T_userService t_userService; /** * 用於的權限的認證。 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = principalCollection.getPrimaryPrincipal().toString() ; SimpleAuthorizationInfo info = new SimpleAuthorizationInfo() ; Set<String> roleName = t_userService.findRoles(username) ; Set<String> permissions = t_userService.findPermissions(username) ; info.setRoles(roleName); info.setStringPermissions(permissions); return info; } /** * 首先執行這個登錄驗證 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //獲取用戶賬號 String username = token.getPrincipal().toString() ; T_user user = t_userService.findUserByUsername(username) ; if (user != null){ //將查詢到的用戶賬號和密碼存放到 authenticationInfo用於后面的權限判斷。第三個參數傳入realName。 AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(), "a") ; return authenticationInfo ; }else{ return null ; } } }

繼承AuthorizingRealm類之后就需要覆寫它的兩個方法,doGetAuthorizationInfo,doGetAuthenticationInfo,這兩個方法的作用我都有寫注釋,邏輯也比較簡單。
doGetAuthenticationInfo是用於登錄驗證的,在登錄的時候需要將數據封裝到Shiro的一個token中,執行shiro的login()方法,之后只要我們將MyRealm這個類配置到Spring中,登錄的時候Shiro就會自動的調用doGetAuthenticationInfo()方法進行驗證。
哦對了,忘了貼下登錄的Controller了:

package com.crossoverJie.controller; import com.crossoverJie.pojo.T_user; import com.crossoverJie.service.T_userService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; /** * Created with IDEA * Created by ${jie.chen} on 2016/7/14. * 后台Controller */ @Controller @RequestMapping("/") public class T_userController { @Resource private T_userService t_userService ; @RequestMapping("/loginAdmin") public String login(T_user user, Model model){ Subject subject = SecurityUtils.getSubject() ; UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),user.getPassword()) ; try { subject.login(token); return "admin" ; }catch (Exception e){ //這里將異常打印關閉是因為如果登錄失敗的話會自動拋異常 // e.printStackTrace(); model.addAttribute("error","用戶名或密碼錯誤") ; return "../../login" ; } } @RequestMapping("/admin") public String admin(){ return "admin"; } @RequestMapping("/student") public String student(){ return "admin" ; } @RequestMapping("/teacher") public String teacher(){ return "admin" ; } }

主要就是login()方法。邏輯比較簡單,只是登錄驗證的時候不是像之前那樣直接查詢數據庫然后返回是否有用戶了,而是調用subjectlogin()方法,就是我上面提到的,調用login()方法時Shiro會自動調用我們自定義的MyRealm類中的doGetAuthenticationInfo()方法進行驗證的,驗證邏輯是先根據用戶名查詢用戶,如果查詢到的話再將查詢到的用戶名和密碼放到SimpleAuthenticationInfo對象中,Shiro會自動根據用戶輸入的密碼和查詢到的密碼進行匹配,如果匹配不上就會拋出異常,匹配上之后就會執行doGetAuthorizationInfo()進行相應的權限驗證。
doGetAuthorizationInfo()方法的處理邏輯也比較簡單,根據用戶名獲取到他所擁有的角色以及權限,然后賦值到SimpleAuthorizationInfo對象中即可,Shiro就會按照我們配置的XX角色對應XX權限來進行判斷,這個配置在下面的整合中會講到。


整合Spring

接下來應該是大家比較關系的一步:整合Spring
我是在之前的Spring SpringMVC Mybatis的基礎上進行整合的。

web.xml配置

首先我們需要在web.xml進行配置Shiro的過濾器。
我只貼Shiro部分的,其余的和之前配置是一樣的。

    <!-- shiro過濾器定義 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 該值缺省為false,表示生命周期由SpringApplicationContext管理,設置為true則表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

配置還是比較簡單的,這樣會過濾所有的請求。
之后我們還需要在Spring中配置一個shiroFilter的bean。

spring-mybatis.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <!-- 自動掃描 --> <context:component-scan base-package="com.crossoverJie" /> <!-- 引入配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 指定連接數據庫的驅動 --> <property name="driverClassName" value="${jdbc.driverClass}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.user}" /> <property name="password" value="${jdbc.password}" /> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="3" /> <property name="minIdle" value="3" /> <property name="maxActive" value="20" /> <!-- 配置獲取連接等待超時的時間 --> <property name="maxWait" value="60000" /> <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <!-- 打開PSCache,並且指定每個連接上PSCache的大小 --> <property name="poolPreparedStatements" value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- 配置監控統計攔截的filters,去掉后監控界面sql無法統計 --> <property name="filters" value="stat" /> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描mapping.xml文件 --> <property name="mapperLocations" value="classpath:mapping/*.xml"></property> </bean> <!-- DAO接口所在包名,Spring會自動查找其下的類 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.crossoverJie.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置自定義Realm --> <bean id="myRealm" class="com.crossoverJie.shiro.MyRealm"/> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> </bean> <!-- Shiro過濾器 核心--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,這個屬性是必須的 --> <property name="securityManager" ref="securityManager"/> <!-- 身份認證失敗,則跳轉到登錄頁面的配置 --> <property name="loginUrl" value="/login.jsp"/> <!-- 權限認證失敗,則跳轉到指定頁面 --> <property name="unauthorizedUrl" value="/nopower.jsp"/> <!-- Shiro連接約束配置,即過濾鏈的定義 --> <property name="filterChainDefinitions"> <value> <!--anon 表示匿名訪問,不需要認證以及授權--> /loginAdmin=anon <!--authc表示需要認證 沒有進行身份認證是不能進行訪問的--> /admin*=authc /student=roles[teacher] /teacher=perms["user:create"] </value> </property> </bean> <!-- 保證實現了Shiro內部lifecycle函數的bean執行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 開啟Shiro注解 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans>

在這里我們配置了上文中所提到的自定義myRealm,這樣Shiro就可以按照我們自定義的邏輯來進行權限驗證了。其余的都比較簡單,看注釋應該都能明白。
着重講解一下:

        <property name="filterChainDefinitions"> <value> <!--anon 表示匿名訪問,不需要認證以及授權--> /loginAdmin=anon <!--authc表示需要認證 沒有進行身份認證是不能進行訪問的--> /admin*=authc /student=roles[teacher] /teacher=perms["user:create"] </value> </property>
  • /loginAdmin=anon的意思的意思是,發起/loginAdmin這個請求是不需要進行身份認證的,這個請求在這次項目中是一個登錄請求,一般對於這樣的請求都是不需要身份認證的。
  • /admin*=authc表示 /admin,/admin1,/admin2這樣的請求都是需要進行身份認證的,不然是不能訪問的。
  • /student=roles[teacher]表示訪問/student請求的用戶必須是teacher角色,不然是不能進行訪問的。
  • /teacher=perms[“user:create”]表示訪問/teacher請求是需要當前用戶具有user:create權限才能進行訪問的。
    更多相關權限過濾的資料可以訪問shiro的官方介紹:傳送門

使用Shiro標簽庫

Shiro還有着強大標簽庫,可以在前端幫我獲取信息和做判斷。
我貼一下我這里登錄完成之后顯示的界面:

<%-- Created by IntelliJ IDEA. User: Administrator Date: 2016/7/14 Time: 13:17 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <html> <head> <title>后台</title> </head> <body> <shiro:hasRole name="admin"> 這是admin角色登錄:<shiro:principal></shiro:principal> </shiro:hasRole> <shiro:hasPermission name="user:create"> 有user:create權限信息 </shiro:hasPermission> <br> 登錄成功 </body> </html>

要想使用Shiro標簽,只需要引入一下標簽即可:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
其實英語稍微好點的童鞋應該都能看懂。下面我大概介紹下一些標簽的用法:
- 具有admin角色才會顯示標簽內的信息。
- 獲取用戶信息。默認調用Subject.getPrincipal()獲取,即 Primary Principal。
- 用戶擁有user:create這個權限才回顯示標簽內的信息。
更多的標簽可以查看官網:傳送門


整體測試


這是我的測試數據。
首先來驗證一下登錄:
先輸入一個錯誤的賬號和密碼:
1.gif

接下來輸入一個正確的:
2.gif
可以看到我登錄的用戶是crossoverJie他是有admin的角色,並且擁有user:*(ps:系統數據詳見上面的數據庫截圖)的權限,所以在這里:

<shiro:hasRole name="admin"> 這是admin角色登錄:<shiro:principal></shiro:principal> </shiro:hasRole> <shiro:hasPermission name="user:create"> 有user:create權限信息 </shiro:hasPermission>

是能顯示出標簽內的信息,並把用戶信息也顯示出來了。
接着我們來訪問一下/student這個請求,因為在Spring的配置文件中:

        <property name="filterChainDefinitions"> <value> <!--anon 表示匿名訪問,不需要認證以及授權--> /loginAdmin=anon <!--authc表示需要認證 沒有進行身份認證是不能進行訪問的--> /admin*=authc /student=roles[teacher] /teacher=perms["user:create"] </value> </property>

只有teacher角色才能訪問/student這個請求的:
3.gif
果然,Shiro做了安全控制是不能進行訪問的。
然后我們換aaa用戶登錄,他正好是teacher角色,看能不能訪問/student
4.gif
果然是能訪問的。
因為我在控制器里訪問/student返回的是同一個界面所以看到的還是這個界面。

    @RequestMapping("/teacher") public String teacher(){ return "admin" ; }

並且沒有顯示之前Shiro標簽內的內容。
其他的我就不測了,大家可以自己在數據庫里加一些數據,或者是改下攔截的權限多試試,這樣對Shiro的理解就會更加深刻。


MD5加密

Shiro還封裝了一個我認為非常不錯的功能,那就是MD5加密,代碼如下:

package com.crossoverJie.shiro; import org.apache.shiro.crypto.hash.Md5Hash; /** * Created with IDEA * 基於Shiro的MD5加密 * Created by ${jie.chen} on 2016/7/13. */ public class MD5Util { public static String md5(String str,String salt){ return new Md5Hash(str,salt).toString() ; } public static void main(String[] args) { String md5 = md5("abc123","crossoverjie") ; System.out.println(md5); } }

代碼非常簡單,只需要調用Md5Hash(str,salt)方法即可,這里多了一個參數,第一個參數不用多解釋,是需要加密的字符串。第二個參數salt中文翻譯叫鹽,加密的時候我們傳一個字符串進去,只要這個salt不被泄露出去,那原則上加密之后是無法被解密的,在存用戶密碼的時候可以使用,感覺還是非常屌的。


總結

以上就是Shiro實際使用的案例,將的比較初略,但是關於Shiro的核心東西都在里面了。大家可以去我的github上下載源碼,只要按照我給的數據庫就沒有問題,項目跑起來之后試着改下里面的東西可以加深對Shiro的理解。

項目地址:https://github.com/crossoverJie/SSM.git
個人博客地址:http://crossoverjie.top
GitHub地址:https://github.com/crossoverJie


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM