1. 程式人生 > >Shiro學習--實際應用(SSM+Shiro)整合

Shiro學習--實際應用(SSM+Shiro)整合

可以參考Apache Shiro官網的文件:http://shiro.apache.org/reference.html

程式碼下載地址:https://download.csdn.net/download/qq_42969074/10810610

Apache Shiro是一個功能強大且靈活的開源安全框架,可以清晰地處理身份驗證,授權,企業會話管理和加密。

Apache Shiro的首要目標是易於使用和理解。安全有時可能非常複雜,甚至是痛苦的,但並非必須如此。框架應儘可能掩蓋複雜性,並提供簡潔直觀的API,以簡化開發人員確保其應用程式安全的工作。

最先建立三個表:使用者表(t_user),角色表(t_role),許可權表(t_permission):

建立自定義MyRealm

有關Shiro的基礎知識我這裡就不過多介紹了,直接來乾貨,到最後會整合Spring來進行許可權驗證。
首先在使用Shiro的時候我們要考慮在什麼樣的環境下使用:

  • 登入的驗證
  • 對指定角色的驗證
  • 對URL的驗證

基本上我們也就這三個需求,所以同時我們也需要三個方法:

  1. getUserByUserName(String username)根據username查詢使用者,之後Shiro會根據查詢出來的User的密碼來和提交上來的密碼進行比對。
  2. getRoles(String username)根據username查詢該使用者的所有角色,用於角色驗證。
  3. getPermissions(String username)根據username查詢他所擁有的許可權資訊,用於許可權判斷。

下面貼一下程式碼

Dao介面程式碼:

public interface UserDao {

	/**
	 * 通過使用者名稱查詢使用者
	 * @param userName
	 * @return
	 */
	public User getByUserName(String userName);
	
	/**
	 * 通過使用者名稱查詢角色資訊
	 * @param userName
	 * @return
	 */
	public Set<String> getRoles(String userName);
	
	/**
	 * 通過使用者名稱查詢許可權資訊
	 * @param userName
	 * @return
	 */
	public Set<String> getPermissions(String userName);
}

mapper中的程式碼: 

<?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.thr.dao.UserDao">

	<resultMap type="User" id="UserResult">
		<result property="id" column="id"/>
		<result property="userName" column="userName"/>
		<result property="password" column="password"/>
	</resultMap>
	
	<select id="getByUserName" parameterType="String" resultMap="UserResult">
		select * from t_user where userName=#{userName}
	</select>
	
	<select id="getRoles" 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="getPermissions" 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> 

很簡單隻有三個方法,分別對應上面所說的三個方法。實體類就比較簡單了,就只有欄位以及get,set方法。我就這裡就不貼了。

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

package com.thr.realm;

import javax.annotation.Resource;

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 com.thr.entity.User;
import com.thr.service.UserService;

public class MyRealm extends AuthorizingRealm{

	@Resource
	private UserService userService;
	
	/**
	 * 為當前登入的使用者授予角色和許可權
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String userName=(String) principals.getPrimaryPrincipal();
		SimpleAuthorizationInfo authorization=new SimpleAuthorizationInfo();
		authorization.setRoles(userService.getRoles(userName));
		authorization.setStringPermissions(userService.getPermissions(userName));
		return null;
	}

	/**
	 * 驗證當前登入的使用者
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String userName=(String) token.getPrincipal();
		User user=userService.getByUserName(userName);
		if(user!=null){
			AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), "xx");
			return authcInfo;
		}
		return null;
	}

}

繼承AuthorizingRealm類之後就需要覆寫它的兩個方法,doGetAuthorizationInfo,doGetAuthenticationInfo,這兩個方法的作用我都有寫註釋,邏輯也比較簡單。
doGetAuthenticationInfo是用於登入驗證的,在登入的時候需要將資料封裝到Shiro的一個token中,執行shiro的login()方法,之後只要我們將MyRealm這個類配置到Spring中,登入的時候Shiro就會自動的呼叫doGetAuthenticationInfo()方法進行驗證。
貼下登入的Controller

package com.thr.controller;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.thr.entity.User;

@Controller
@RequestMapping("/")
public class UserController {

	/**
	 * 使用者登入
	 * @param user
	 * @param request
	 * @return
	 */
	@RequestMapping("/login")
	public String login(User user,HttpServletRequest request){
		Subject subject=SecurityUtils.getSubject();
		UsernamePasswordToken token=new UsernamePasswordToken(user.getUserName(), user.getPassword());
		try{
			subject.login(token);
			Session session=subject.getSession();
			System.out.println("sessionId:"+session.getId());
			System.out.println("sessionHost:"+session.getHost());
			System.out.println("sessionTimeout:"+session.getTimeout());
			session.setAttribute("info", "session的資料");
			return "redirect:/success.jsp";
		}catch(Exception e){
			e.printStackTrace();
			request.setAttribute("user", user);
			request.setAttribute("errorMsg", "使用者名稱或密碼錯誤!");
			return "index";
		}
	}
	@RequestMapping("/admin")
    public String admin(HttpServletRequest request) {
        return "success";
    }

    @RequestMapping("/student")
    public String student(HttpServletRequest request) {
        return "success";
    }   

    @RequestMapping("/teacher")
    public String teacher(HttpServletRequest request) {
        return "success";
    } 
	
}

主要就是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部分的,其餘的和之前配置是一樣的。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>ShiroWeb2</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <!-- 配置shiro過濾器 -->
  <filter>
  	<filter-name>shiroFilter</filter-name>
  	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  	<init-param>
  		<param-name>targeFilterLifecycle</param-name>
  		<param-value>true</param-value>
  	</init-param>
  </filter>
  
  <filter-mapping>
  	<filter-name>shiroFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <!-- 配置spring檔案 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>
	
	<!-- spring監聽器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
  
  <!-- 配置spring編碼格式 -->
  <filter>
  	<filter-name>encodingFilter</filter-name>
  	<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  	<init-param>
  		<param-name>encoding</param-name>
  		<param-value>UTF-8</param-value>
  	</init-param>
  </filter>
  
  <filter-mapping>
	<filter-name>encodingFilter</filter-name>  
	<url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <!-- 配置springMVC檔案 -->
  <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-mvc.xml</param-value>
  	</init-param>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
  	<servlet-name>springMVC</servlet-name>
  	<url-pattern>*.do</url-pattern>
  </servlet-mapping>
  
  
</web-app>

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

applicationContext.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"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
	
	<!-- 自動掃描 -->
	<context:component-scan base-package="com.thr.service"></context:component-scan>
	
	<!-- 載入jdbc.properties -->
	<context:property-placeholder location="classpath:jdbc.properties"/>

	<!-- 配置資料來源 -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="${jdbc.driverClassName}"></property>
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
	
	<!-- 配置Mybatis的sqlsessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:mybatis-config.xml"></property>
		<property name="dataSource" ref="dataSource"></property>
		<property name="mapperLocations" value="classpath:com/thr/mapper/*.xml"></property>
	</bean>
	
	<!-- DAO介面所在包名,Spring會自動查詢其下的類 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.thr.dao"></property>
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
	</bean>
	
	<!-- 自定義Realm -->
	<bean id="myRealm" class="com.thr.realm.MyRealm"></bean>
	
	<!-- shiro安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="myRealm"></property>
	</bean>
	
	<!-- 配置shiro過濾器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
			<!-- shiro的核心安全介面,這個是必須的 -->
			<property name="securityManager" ref="securityManager"></property>
			<!-- 身份認證失敗,則跳轉到登入頁面配置 -->
			<property name="loginUrl" value="/index.jsp"></property>
			<!-- 許可權認證失敗,則跳到指定頁面 -->
			<property name="unauthorizedUrl" value="/unautor.jsp"></property>
			<!-- 配置shiro連線約束配置,即過濾鏈的定義 -->
			<property name="filterChainDefinitions">
				<value>
					<!-- 遊客身份,多可以訪問 -->
					/login=anon
					<!-- 必須進行身份認證 -->
					/admin*=authc
					<!-- 要有teacher的角色 -->
					/student=roles[teacher]
					/teacher=perms["user:create"]
					
				</value>
			</property>
	</bean>
	
	<!-- 保證實現了shiro內部lifecycle函式的bean執行 -->
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
	
	<!-- 開啟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>
	
		
	<!-- spring事物 -->	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
		
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="insert*" propagation="REQUIRED" />  
            <tx:method name="update*" propagation="REQUIRED" />  
            <tx:method name="edit*" propagation="REQUIRED" />  
            <tx:method name="save*" propagation="REQUIRED" />  
            <tx:method name="add*" propagation="REQUIRED" />  
            <tx:method name="new*" propagation="REQUIRED" />  
            <tx:method name="set*" propagation="REQUIRED" />  
            <tx:method name="remove*" propagation="REQUIRED" />  
            <tx:method name="delete*" propagation="REQUIRED" />  
            <tx:method name="change*" propagation="REQUIRED" />  
            <tx:method name="get*" propagation="REQUIRED" read-only="true" />  
            <tx:method name="find*" propagation="REQUIRED" read-only="true" />  
            <tx:method name="load*" propagation="REQUIRED" read-only="true" />  
            <tx:method name="*" propagation="REQUIRED" read-only="true" /> 
		</tx:attributes>
	</tx:advice>
	
	<!-- 配置事物切面 -->
	<aop:config>
		<aop:pointcut expression="execution(* com.thr.service.*.*(..))" id="serviceOperation"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
	</aop:config>
		
</beans>

這個JSP中的程式碼:這裡有3個jsp頁面,我將它們寫在一起了。

//index.jsp
<body>
<form action="${pageContext.request.contextPath }/login.do" method="post">
	userName:<input type="text" name="userName" value="${user.userName }"/><br/>
	password:<input type="password" name="password" value="${user.password }"><br/>
	<input type="submit" value="login"/><font color="red">${errorMsg }</font>
</form>
</body>

//success.jsp
<body>
登入成功
</body>

//unauthor.jsp

<body>
<h1>授權失敗!</h1>
</body>

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

​
<property name="filterChainDefinitions">

<value>

<!--anon 表示匿名訪問,不需要認證以及授權-->

/login=anon

<!--authc表示需要認證 沒有進行身份認證是不能進行訪問的-->

/admin*=authc

/student=roles[teacher]

/teacher=perms["user:create"]

</value>

</property>

​

1、/login=anon的意思的意思是,發起/login這個請求是不需要進行身份認證的,這個請求在這次專案中是一個登入請求,一般對於這樣的請求都是不需要身份認證的。

2、/admin*=authc表示 /admin,/admin1,/admin2這樣的請求都是需要進行身份認證的,不然是不能訪問的。

3、/student=roles[teacher]表示訪問/student請求的使用者必須是teacher角色,不然是不能進行訪問的。

4、/teacher=perms[“user:create”]表示訪問/teacher請求是需要當前使用者具有user:create許可權才能進行訪問的

 

測試:

 首先來驗證一下登入:由於上面配置了/login=anon  所有login不會被攔截。


接著我們來訪問一下/student這個請求,因為在Spring的配置檔案中:

<property name="filterChainDefinitions">

<value>

<!--anon 表示匿名訪問,不需要認證以及授權-->

/login=anon

<!--authc表示需要認證 沒有進行身份認證是不能進行訪問的-->

/admin*=authc

/student=roles[teacher]

/teacher=perms["user:create"]

</value>

</property>

只有teacher角色才能訪問/student這個請求:所有會授權失敗!