前言
相比有做過企業級開發的童鞋應該都有做過權限安全之類的功能吧,最先開始我采用的是建用戶表
,角色表
,權限表
,之后在攔截器中對每一個請求進行攔截,再到數據庫中進行查詢看當前用戶是否有該權限,這樣的設計能滿足大多數中小型系統的需求。不過這篇所介紹的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方法。我就這里就不貼了,具體可以去github
上fork
我的源碼。
現在就需要創建自定義的MyRealm
類,這個還是比較重要的。繼承至Shiro
的AuthorizingRealm
類,用於處理自己的驗證邏輯,下面貼一下我的代碼:
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()
方法。邏輯比較簡單,只是登錄驗證的時候不是像之前那樣直接查詢數據庫然后返回是否有用戶了,而是調用subject
的login()
方法,就是我上面提到的,調用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">