1. 程式人生 > >springboot shiro許可權管理

springboot shiro許可權管理

 整合shiro大概分這麼一個步驟:

(一) pom.xml中新增Shiro依賴;

(二) 注入Shiro FactorySecurityManager

(三) 身份認證

(四) 許可權控制


一: pom.xml中新增Shiro依賴

1.1:要使用Shiro進行許可權控制,那麼很明顯的就需要新增對Shiro的依賴包,在pom.xml中加入如下配置:

 <!-- shiro許可權控制框架 -->
	 <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
pom.xml
pom.xml完整配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>


	<groupId>com.gt</groupId>
	<artifactId>commonService</artifactId>
	<version>0.0.1-SNAPSHOT</version> 
	<packaging>war</packaging>


	<name>springboot-mybatis</name>
	<description>Demo project for Spring Boot</description>


	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.10.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>


	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>


		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		<!-- 熱部署 -->
		 <dependency>    
        <groupId>org.springframework.boot</groupId>    
        <artifactId>spring-boot-devtools</artifactId>    
        <optional>true</optional> 
       </dependency>
		 <dependency>
		 	<groupId>org.springframework.boot</groupId>
		 	<artifactId>
		 		spring-boot-configuration-processor
		 	</artifactId>
		 	<optional>true</optional>
		 </dependency>
	 


        <!--jsp頁面使用jstl標籤-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>


        <!--用於編譯jsp-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- jpa物件持久化  利用該jar包,通過bean直接生成資料庫表-->
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- mysql支援 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--spring boot 整合 mybatis 依賴--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <!-- 資料庫連線池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.5</version> </dependency> <!-- json支援 --> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <!-- shiro許可權控制框架 -->
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.3.2</version> </dependency> <!-- 包含支援UI模版(Velocity,FreeMarker,JasperReports), 郵件服務, 指令碼服務(JRuby), 快取Cache(EHCache), 任務計劃Scheduling(uartz)。 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <!-- 單點登入 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-cas</artifactId> <version>1.2.4</version> </dependency> </dependencies> <build> <!-- 最終專案名,最終專案名 --> <finalName>commonService</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <!-- maven打包的時候告訴maven不需要web.xml,否剛會報找不到web.xml錯誤 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>

1.2:application.yml部分檔案配置

spring:
  profiles:
    active: dev
  mvc:
   view: 
    prefix: /WEB-INF/jsp/
    suffix: .jsp
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testDemo?characterEncoding=utf8&useSSL=true
    username: root
    password: root
########################################################  
### Java Persistence Api  
########################################################  
  jpa:
    database: MYSQL
    show-sql: true 
    hibernate: 
      ddl-auto: update  
      naming: 
       physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
       ####
        ##org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
        ##org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        ##對於PhysicalNamingStrategyStandardImpl有DefaultNamingStrategy的效果;對於SpringPhysicalNamingStrategy  有
        ##ImprovedNamingStrategy的效果。
      #####
    properties:
      hibernate: 
        dialect:   org.hibernate.dialect.MySQL5Dialect     
      
   
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml 
  type-aliases-package: com.gt.entity

1.3:application-dev.yml 開發配置檔案

server: 
  port: 8081
  context-path: /commonService

1.4:ehcache-shiro.xml  快取配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">

    <diskStore path="java.io.tmpdir"/>

    <!--
       name:快取名稱。
       maxElementsInMemory:快取最大數目
       maxElementsOnDisk:硬碟最大快取個數。
       eternal:物件是否永久有效,一但設定了,timeout將不起作用。
       overflowToDisk:是否儲存到磁碟,當系統當機時
       timeToIdleSeconds:設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
       timeToLiveSeconds:設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大。
       diskPersistent:是否快取虛擬機器重啟期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
       diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
       memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
        clearOnFlush:記憶體數量最大時是否清除。
         memoryStoreEvictionPolicy:
            Ehcache的三種清空策略;
            FIFO,first in first out,這個是大家最熟的,先進先出。
            LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
            LRU,Least Recently Used,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。
    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />


    <!-- 登入記錄快取鎖定10分鐘 -->
    <cache name="passwordRetryCache"
           maxEntriesLocalHeap="2000"
           eternal="false"
           timeToIdleSeconds="3600"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           statistics="true">
    </cache>

</ehcache> 

二:注入Shiro FactorySecurityManager

package com.gt.config;

import java.util.LinkedHashMap;  
import java.util.Map;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;  
/** 
* Shiro 配置 
*  
Apache Shiro 核心通過 Filter 來實現,就好像SpringMvc 通過DispachServlet 來主控制一樣。 
既然是使用 Filter 一般也就能猜到,是通過URL規則來進行過濾和許可權校驗,所以我們需要定義一系列關於URL的規則和訪問許可權。 
* 
* @author gt
* @version v.0.1 
*/  
@Configuration  
public class ShiroConfiguration {  
      
	/**
     * shiro快取管理器;
     * 需要注入對應的其它的實體類中:
     * 1、安全管理器:securityManager
     * 可見securityManager是整個shiro的核心;
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        System.out.println("ShiroConfiguration.getEhCacheManager()");
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return cacheManager;
    }
    
    @Bean  
    public SecurityManager securityManager(){  
       DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();  
       //設定realm.  
       securityManager.setRealm(myShiroRealm());
       //注入快取
       securityManager.setCacheManager(ehCacheManager());
       return securityManager;  
    }  
   
 /*   @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setCacheManager(ehCacheManager());
        return securityManager;
    }*/

  /** 
   * ShiroFilterFactoryBean 處理攔截資原始檔問題。 
   * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,以為在 
   * 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager 
   * 
      Filter Chain定義說明 
     1、一個URL可以配置多個Filter,使用逗號分隔 
     2、當設定多個過濾器時,全部驗證通過,才視為通過 
     3、部分過濾器可指定引數,如perms,roles 
   * 
   */  
  @Bean  
  public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){  
     System.out.println("ShiroConfiguration.shirFilter()");  
     ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();  
      
      // 必須設定 SecurityManager   
     shiroFilterFactoryBean.setSecurityManager(securityManager);  
      
      
      
     //攔截器.  
     Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();  
      
     //配置退出過濾器,其中的具體的退出程式碼Shiro已經替我們實現了  
     filterChainDefinitionMap.put("/logout", "logout");  
     filterChainDefinitionMap.put("/system/login", "anon");  
      
     //<!-- 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊 -->:這是一個坑呢,一不小心程式碼就不好使了;  
      //<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->  
     filterChainDefinitionMap.put("/**", "authc");  
      
     // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面  
      shiroFilterFactoryBean.setLoginUrl("/system/toLogin");  
      // 登入成功後要跳轉的連結  
      shiroFilterFactoryBean.setSuccessUrl("/system/index");  
      //未授權介面;  
      shiroFilterFactoryBean.setUnauthorizedUrl("/system/403");  
      
     shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
     return shiroFilterFactoryBean;  
  }  
  /** 
   *  開啟shiro aop註解支援. 
   *  使用代理方式;所以需要開啟程式碼支援; 
   * @param securityManager 
   * @return 
   */  
  @Bean  
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){  
     AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();  
     authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);  
     return authorizationAttributeSourceAdvisor;  
  }  
  /** 
   * 身份認證realm; 
   * (這個需要自己寫,賬號密碼校驗;許可權等) 
   * @return 
   */  
  @Bean  
  public MyShiroRealm myShiroRealm(){  
     MyShiroRealm myShiroRealm = new MyShiroRealm();  
     return myShiroRealm;  
  }  
}  

這裡說下:ShiroFilterFactory中已經由Shiro官方實現的過濾器:

Shiro內建的FilterChain

Filter Name

Class

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user

org.apache.shiro.web.filter.authc.UserFilter

anon:所有url都都可以匿名訪問;

authc: 需要認證才能進行訪問;

user:配置記住我或認證通過可以訪問;

這幾個是我們會用到的,在這裡說明下,其它的請自行查詢文件進行學習。

這時候我們執行程式,訪問/index頁面我們會發現自動跳轉到了login頁面,當然這個時候輸入賬號和密碼是無法進行訪問的。下面這才是重點:任何身份認證,如何許可權控制。


三:身份認證

在認證、授權內部實現機制中都有提到,最終處理都將交給Real進行處理。因為在Shiro中,最終是通過Realm來獲取應用程式中的使用者、角色及許可權資訊的。通常情況下,在Realm中會直接從我們的資料來源中獲取Shiro需要的驗證資訊。可以說,Realm是專用於安全框架的DAO.

認證實現

Shiro的認證過程最終會交由Realm執行,這時會呼叫RealmgetAuthenticationInfo(token)方法。

該方法主要執行以下操作:

1、檢查提交的進行認證的令牌資訊

2、根據令牌資訊從資料來源(通常為資料庫)中獲取使用者資訊

3、對使用者資訊進行匹配驗證。

4、驗證通過將返回一個封裝了使用者資訊的AuthenticationInfo例項。

5、驗證失敗則丟擲AuthenticationException異常資訊。

而在我們的應用程式中要做的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,過載doGetAuthenticationInfo (),重寫獲取使用者資訊的方法。

既然需要進行身份許可權控制,那麼少不了建立使用者實體類,許可權實體類。

      在許可權管理系統中,有這麼幾個角色很重要,這個要是不清楚的話,那麼就很難理解,我們為什麼這麼編碼了。第一是使用者表:在使用者表中儲存了使用者的基本資訊,賬號、密碼、姓名,性別等;第二是:許可權表(資源+控制權限):這個表中主要是儲存了使用者的URL地址,許可權資訊;第三就是角色表:在這個表重要儲存了系統存在的角色;第四就是關聯表:使用者-角色管理表(使用者在系統中都有什麼角色,比如adminvip等),角色-許可權關聯表(每個角色都有什麼許可權可以進行操作)。依據這個理論,我們進行來進行編碼,很明顯的我們第一步就是要進行實體類的建立。在這裡我們使用MysqlJPA進行操作資料庫。

3.1:那麼我們先在pom.xml中引入mysqlJPA的依賴:

 <!-- jpa物件持久化  利用該jar包,通過bean直接生成資料庫表-->
          <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>  
       <!-- mysql支援 -->
        <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
        </dependency>

3.2    準備工作準備好之後,那麼就可以編寫實體類了:

UserInfo.java

package com.gt.entity;


import java.io.Serializable;  
import java.util.List;  
   
import javax.persistence.Column;  
import javax.persistence.Entity;  
import javax.persistence.FetchType;  
import javax.persistence.GeneratedValue;  
import javax.persistence.Id;  
import javax.persistence.JoinColumn;  
import javax.persistence.JoinTable;  
import javax.persistence.ManyToMany;  
   
/** 
 * 使用者資訊. 
 * @author gt 
 * @version v.0.1 
 */  
@Entity  
public class UserInfo implements Serializable{  
    private static final long serialVersionUID = 1L;  
    @Id
    @GeneratedValue  
    private long uid;//使用者id;  
     
    @Column(unique=true)  
    private String username;//賬號.  
     
    private String name;//名稱(暱稱或者真實姓名,不同系統不同定義)  
     
    private String password; //密碼;  
    private String salt;//加密密碼的鹽  
     
    private byte state;//使用者狀態,0:建立未認證(比如沒有啟用,沒有輸入驗證碼等等)--等待驗證的使用者 , 1:正常狀態,2:使用者被鎖定.  
   
     
    @ManyToMany(fetch=FetchType.EAGER)//立即從資料庫中進行載入資料;  
    @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })  
    private List<SysRole> roleList;// 一個使用者具有多個角色  
     
    public List<SysRole> getRoleList() {  
       return roleList;  
    }  
   
    public void setRoleList(List<SysRole> roleList) {  
       this.roleList = roleList;  
    }  
   
    public long getUid() {  
       return uid;  
    }  
   
    public void setUid(long uid) {  
       this.uid = uid;  
    }  
   
    public String getUsername() {  
       return username;  
    }  
   
    public void setUsername(String username) {  
       this.username = username;  
    }  
   
    public String getName() {  
       return name;  
    }  
   
    public void setName(String name) {  
       this.name = name;  
    }  
   
    public String getPassword() {  
       return password;  
    }  
   
    public void setPassword(String password) {  
       this.password = password;  
    }  
   
    public String getSalt() {  
       return salt;  
    }  
   
    public void setSalt(String salt) {  
       this.salt = salt;  
    }  
   
    public byte getState() {  
       return state;  
    }  
   
    public void setState(byte state) {  
       this.state = state;  
    }  
   
    /** 
     * 密碼鹽. 
     * @return 
     */  
    public String getCredentialsSalt(){  
       return this.username+this.salt;  
    }  
   
    @Override  
    public String toString() {  
       return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password  
              + ", salt=" + salt + ", state=" + state + "]";  
    }  
   
     
}  

3.3 SysRole.java

package com.gt.entity;

import java.io.Serializable;  
import java.util.List;  
 
import javax.persistence.Entity;  
import javax.persistence.FetchType;  
import javax.persistence.GeneratedValue;  
import javax.persistence.Id;  
import javax.persistence.JoinColumn;  
import javax.persistence.JoinTable;  
import javax.persistence.ManyToMany;  
 
 
/** 
* 系統角色實體類; 
* @author gt
* @version v.0.1 
*/  
@Entity  
public class SysRole implements Serializable{  
  private static final long serialVersionUID = 1L;  
  @[email protected]  
  private Long id; // 編號  
  private String role; // 角色標識程式中判斷使用,如"admin",這個是唯一的:  
  private String description; // 角色描述,UI介面顯示使用  
  private Boolean available = Boolean.FALSE; // 是否可用,如果不可用將不會新增給使用者  
   
  //角色 -- 許可權關係:多對多關係;  
  @ManyToMany(fetch=FetchType.EAGER)  
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})  
  private List<SysPermission> permissions;  
   
  // 使用者 - 角色關係定義;  
  @ManyToMany  
@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})  
  private List<UserInfo> userInfos;// 一個角色對應多個使用者  
   
  public List<UserInfo> getUserInfos() {  
     return userInfos;  
  }  
  public void setUserInfos(List<UserInfo> userInfos) {  
     this.userInfos = userInfos;  
  }  
  public Long getId() {  
     return id;  
  }  
  public void setId(Long id) {  
     this.id = id;  
  }  
  public String getRole() {  
     return role;  
  }  
  public void setRole(String role) {  
     this.role = role;  
  }  
  public String getDescription() {  
     return description;  
  }  
  public void setDescription(String description) {  
     this.description = description;  
  }  
  public Boolean getAvailable() {  
     return available;  
  }  
  public void setAvailable(Boolean available) {  
     this.available = available;  
  }  
  public List<SysPermission> getPermissions() {  
     return permissions;  
  }  
  public void setPermissions(List<SysPermission> permissions) {  
     this.permissions = permissions;  
  }  
  @Override  
  public String toString() {  
     return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available  
            + ", permissions=" + permissions + "]";  
  }  
}  

3.4:SysPermission.java

package com.gt.entity;

import java.io.Serializable;  
import java.util.List;  
  
import javax.persistence.Column;  
import javax.persistence.Entity;  
import javax.persistence.GeneratedValue;  
import javax.persistence.Id;  
import javax.persistence.JoinColumn;  
import javax.persistence.JoinTable;  
import javax.persistence.ManyToMany;  
  
/** 
* 許可權實體類; 
* @author gt
* @version v.0.1 
*/  
@Entity  
public class SysPermission implements Serializable{  
   private static final long serialVersionUID = 1L;  
    
   @[email protected]  
   private long id;//主鍵.  
   private String name;//名稱.  
    
   @Column(columnDefinition="enum('menu','button')")  
   private String resourceType;//資源型別,[menu|button]  
   private String url;//資源路徑.  
   private String permission; //許可權字串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view  
   private Long parentId; //父編號  
   private String parentIds; //父編號列表  
   private Boolean available = Boolean.FALSE;  
    
   @ManyToMany  
@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})  
   private List<SysRole> roles;  
    
   public long getId() {  
      return id;  
   }  
   public void setId(long id) {  
      this.id = id;  
   }  
   public String getName() {  
      return name;  
   }  
   public void setName(String name) {  
      this.name = name;  
   }  
   public String getResourceType() {  
      return resourceType;  
   }  
   public void setResourceType(String resourceType) {  
      this.resourceType = resourceType;  
   }  
   public String getUrl() {  
      return url;  
   }  
   public void setUrl(String url) {  
      this.url = url;  
   }  
   public String getPermission() {  
      return permission;  
   }  
   public void setPermission(String permission) {  
      this.permission = permission;  
   }  
   public Long getParentId() {  
      return parentId;  
   }  
   public void setParentId(Long parentId) {  
      this.parentId = parentId;  
   }  
   public String getParentIds() {  
      return parentIds;  
   }  
   public void setParentIds(String parentIds) {  
      this.parentIds = parentIds;  
   }  
   public Boolean getAvailable() {  
      return available;  
   }  
   public void setAvailable(Boolean available) {  
      this.available = available;  
   }  
   public List<SysRole> getRoles() {  
      return roles;  
   }  
   public void setRoles(List<SysRole> roles) {  
      this.roles = roles;  
   }  
   @Override  
   public String toString() {  
      return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url  
             + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="  
             + available + ", roles=" + roles + "]";  
   }  
}   

ok,到這裡實體類就編碼完畢了,在這裡我們看到的是3個實體類,UserInfo,SysRole,SysPermission,對應的是資料庫的五張表:

1UserInfo2SysUserRole3SysRole4SysRolePermission5SysPermission

這時候執行程式,就會自動建表,然後我們新增一些資料:

INSERT INTO `SysPermission` VALUES ('1', 1, '使用者管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');

INSERT INTO `SysPermission` VALUES ('2', 1, '使用者新增', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');

INSERT INTO `SysPermission` VALUES ('3', 1, '使用者刪除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');

 

INSERT INTO `SysRole` VALUES ('1', 1, '管理員', 'admin');

INSERT INTO `SysRole` VALUES ('2', 1, 'VIP會員', 'vip');

 

INSERT INTO `SysRolePermission` VALUES ('1', 1);

INSERT INTO `SysRolePermission` VALUES ('1', 2);

 

INSERT INTO `SysUserRole` VALUES ('1', 1);

INSERT INTO `SysUserRole` VALUES ('1', 2);



INSERT INTO `UserInfo` VALUES ('1', '管理員', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '8d78869f470951332959580424d4bf4f', '0');

 這時候資料都準備完畢了,那麼接下來就應該編寫UserInfoMapper進行訪問資料了

 UserInfoMapper.java

package com.gt.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import com.gt.entity.SysRole;
import com.gt.entity.UserInfo;

public interface UserInfoMapper {
	
	/**通過username查詢使用者資訊;*/  
    public UserInfo findByUsername(@Param("username") String username);  
	/**通過username查詢使用者許可權;*/  
    public List<SysRole> findRoleListByUsername(@Param("uid") Long uid);  
}

userInfoMapper.xml

<?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.gt.mapper.UserInfoMapper" >
     <select id="findByUsername" resultType="com.gt.entity.UserInfo">
    select * from UserInfo
    WHERE 1=1 
         <if test="username !=null and username !=''">  
		     and  username= #{username}
        </if>  
   </select>   
   
     <select id="findRoleListByUsername" resultMap="userrole">
    <!-- SELECT ui.*,sur.*,sr.*,srp.*,sp.*  -->
    SELECT sr.id,sr.available,sr.role,sr.description,
    sp.id spId,sp.url,sp.permission,sp.resourceType,sp.available,
           sp.name roleName,sp.parentId,sp.parentIds,
    sr.id srId,sr.role,sr.description,sr.available
    FROM UserInfo ui 
	inner  JOIN SysUserRole sur on sur.uid=ui.uid
	inner  JOIN SysRole sr on sur.roleId=sr.id 
	inner  JOIN SysRolePermission srp on sur.roleId=srp.roleId
	inner  JOIN SysPermission sp on srp.permissionId=sp.id
    WHERE 1=1 
         <if test="uid !=null and uid !=''">  
		     and  ui.uid= #{uid}
        </if>  
   </select>  
    <resultMap type="com.gt.entity.SysRole" id="userrole">
      <id column="id" property="id"/>
      <result column="role" property="role"/>
      <result column="available" property="available"/>
      <result column="description" property="description"/>
      <collection property="permissions" ofType="com.gt.entity.SysPermission">
            <id property="id" column="spId" />
            <result property="name" column="roleName" />
            <result property="resourceType" column="resourceType" />
            <result property="url" column="url" />
            <result property="permission" column="permission" />
            <result property="parentId" column="parentId" />
            <result property="parentIds" column="parentIds" />
            <result property="available" column="available" />
             <collection property="roles" ofType="com.gt.entity.SysRole">
               <id property="id" column="srId" />
               <result property="role" column="role" />
               <result property="description" column="description" />
               <result property="available" column="available" />
               
             </collection>
     </collection>
    </resultMap>
  
</mapper>

基本工作準備好之後,剩下的才是重點,shiro的認證最終是交給了Realm進行執行了,所以我們需要自己重新實現一個Realm,此Realm繼承AuthorizingRealm

MyShiroRealm.java

package com.gt.config;


import java.util.ArrayList;
import java.util.List;

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.authc.UsernamePasswordToken;  
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 org.apache.shiro.util.ByteSource;

import com.gt.entity.SysPermission;
import com.gt.entity.SysRole;
import com.gt.entity.UserInfo;
import com.gt.mapper.UserInfoMapper;  
 
 
 
/** 
*  身份校驗核心類; 
* @author gt
* @version v.0.1 
*/  
public class MyShiroRealm extends AuthorizingRealm{  
 
       
  @Resource  
  private UserInfoMapper userInfoMapper;  
   
  /** 
   * 認證資訊.(身份驗證) 
   * : 
   * Authentication 是用來驗證使用者身份 
   * @param token 
   * @return 
   * @throws AuthenticationException 
   */  
  @Override  
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
     System.out.println("MyShiroRealm.doGetAuthenticationInfo()");  
      
      
     //獲取使用者的輸入的賬號.  
     String username = (String)token.getPrincipal();  
     System.out.println(token.getCredentials());  
      
     //通過username從資料庫中查詢 User物件,如果找到,沒找到.  
     //實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法  
     UserInfo userInfo = userInfoMapper.findByUsername(username);  
     System.out.println("----->>userInfo="+userInfo);  
     if(userInfo == null){  
         return null;  
     }  
      
     /* 
      * 獲取許可權資訊:這裡沒有進行實現, 
      * 請自行根據UserInfo,Role,Permission進行實現; 
      * 獲取之後可以在前端for迴圈顯示所有連結; 
      */  
     //userInfo.setPermissions(userService.findPermissions(user));  
      
      
     //賬號判斷;  
      
     //加密方式;  
     //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以自定義實現  
     SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
            userInfo, //使用者名稱  
            userInfo.getPassword(), //密碼  
              ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt  
              getName()  //realm name  
      );  
      
      //明文: 若存在,將此使用者存放到登入認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗  
//    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
//         userInfo, //使用者名稱  
//         userInfo.getPassword(), //密碼  
//           getName()  //realm name  
//    );  
      
     return authenticationInfo;  
  }  
   
   
   
  /** 
   * 此方法呼叫  hasRole,hasPermission的時候才會進行回撥. 
   * 
   * 許可權資訊.(授權): 
   * 1、如果使用者正常退出,快取自動清空; 
   * 2、如果使用者非正常退出,快取自動清空; 
   * 3、如果我們修改了使用者的許可權,而使用者不退出系統,修改的許可權無法立即生效。 
   * (需要手動程式設計進行實現;放在service進行呼叫) 
   * 在許可權修改後呼叫realm中的方法,realm已經由spring管理,所以從spring中獲取realm例項, 
   * 呼叫clearCached方法; 
   * :Authorization 是授權訪問控制,用於對使用者進行的操作授權,證明該使用者是否允許進行當前操作,如訪問某個連結,某個資原始檔等。 
   * @param principals 
   * @return 
   */  
  @Override  
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
     /* 
      * 當沒有使用快取的時候,不斷重新整理頁面的話,這個程式碼會不斷執行, 
      * 當其實沒有必要每次都重新設定許可權資訊,所以我們需要放到快取中進行管理; 
      * 當放到快取中時,這樣的話,doGetAuthorizationInfo就只會執行一次了, 
      * 快取過期之後會再次執行。 
      */  
     System.out.println("許可權配置-->MyShiroRealm.doGetAuthorizationInfo()");  
      
     SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
     UserInfo userInfo  = (UserInfo)principals.getPrimaryPrincipal(); 
     List<SysRole> roleList=new ArrayList<>();
     roleList= userInfoMapper.findRoleListByUsername(userInfo.getUid());  
     userInfo.setRoleList(roleList); 
     //實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法  
//   UserInfo userInfo = userInfoService.findByUsername(username)  
      
      
     //許可權單個新增;  
     // 或者按下面這樣新增  
      //新增一個角色,不是配置意義上的新增,而是證明該使用者擁有admin角色     
//   authorizationInfo.addRole("admin");   
      //新增許可權   
//   authorizationInfo.addStringPermission("userInfo:query");  
      
      
      
     ///在認證成功之後返回.  
     //設定角色資訊.  
     //支援 Set集合,  
     //使用者的角色對應的所有許可權,如果只使用角色定義訪問許可權,下面的四行可以不要  
//      List<Role> roleList=user.getRoleList();  
//      for (Role role : roleList) {  
//          info.addStringPermissions(role.getPermissionsName());  
//      }  
     for(SysRole role:userInfo.getRoleList()){  
         authorizationInfo.addRole(role.getRole());  
         for(SysPermission p:role.getPermissions()){  
            authorizationInfo.addStringPermission(p.getPermission());  
         }  
     }  
      
     //設定許可權資訊.  
//   authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList()));  
      
     return authorizationInfo;  
  }   
   
}  

com.gt.config.ShiroConfiguration.java

package com.gt.config;

import java.util.LinkedHashMap;  
import java.util.Map;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;  
/** 
* Shiro 配置 
* http://412887952-qq-com.iteye.com/blog/2299777
Apache Shiro 核心通過 Filter 來實現,就好像SpringMvc 通過DispachServlet 來主控制一樣。 
既然是使用 Filter 一般也就能猜到,是通過URL規則來進行過濾和許可權校驗,所以我們需要定義一系列關於URL的規則和訪問許可權。 
* 
* @author gt
* @version v.0.1 
*/  
@Configuration  
public class ShiroConfiguration {  
      
	/**
     * shiro快取管理器;
     * 需要注入對應的其它的實體類中:
     * 1、安全管理器:securityManager
     * 可見securityManager是整個shiro的核心;
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        System.out.println("ShiroConfiguration.getEhCacheManager()");
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return cacheManager;
    }
    
    @Bean  
    public SecurityManager securityManager(){  
       DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();  
       //設定realm.  
       securityManager.setRealm(myShiroRealm());
       //注入快取
       securityManager.setCacheManager(ehCacheManager());
       return securityManager;  
    }  
   
 /*   @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setCacheManager(ehCacheManager());
        return securityManager;
    }*/

  /** 
   * ShiroFilterFactoryBean 處理攔截資原始檔問題。 
   * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,以為在 
   * 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager 
   * 
      Filter Chain定義說明 
     1、一個URL可以配置多個Filter,使用逗號分隔 
     2、當設定多個過濾器時,全部驗證通過,才視為通過 
     3、部分過濾器可指定引數,如perms,roles 
   * 
   */  
  @Bean  
  public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){  
     System.out.println("ShiroConfiguration.shirFilter()");  
     ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();  
      
      // 必須設定 SecurityManager   
     shiroFilterFactoryBean.setSecurityManager(securityManager);  
      
      
      
     //攔截器.  
     Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();  
      
     //配置退出過濾器,其中的具體的退出程式碼Shiro已經替我們實現了  
     filterChainDefinitionMap.put("/logout", "logout");  
     filterChainDefinitionMap.put("/system/login", "anon");  
      
     //<!-- 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊 -->:這是一個坑呢,一不小心程式碼就不好使了;  
      //<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->  
     filterChainDefinitionMap.put("/**", "authc");  
      
     // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面  
      shiroFilterFactoryBean.setLoginUrl("/system/toLogin");  
      // 登入成功後要跳轉的連結  
      shiroFilterFactoryBean.setSuccessUrl("/system/index");  
      //未授權介面;  
      shiroFilterFactoryBean.setUnauthorizedUrl("/system/403");  
      
     shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  
     return shiroFilterFactoryBean;  
  }  
  /** 
   *  開啟shiro aop註解支援. 
   *  使用代理方式;所以需要開啟程式碼支援; 
   * @param securityManager 
   * @return 
   */  
  @Bean  
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){  
     AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();  
     authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);  
     return authorizationAttributeSourceAdvisor;  
  }  
  /** 
   * 身份認證realm; 
   * (這個需要自己寫,賬號密碼校驗;許可權等) 
   * @return 
   */  
  @Bean  
  public MyShiroRealm myShiroRealm(){  
     MyShiroRealm myShiroRealm = new MyShiroRealm();  
     return myShiroRealm;  
  }  

 
}  
SystemController.java

/**
 * 
 */
package com.gt.controller;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.gt.config.MyShiroRealm;
import com.gt.entity.Girl;
import com.gt.entity.UserInfo;
import com.gt.mapper.GirlMapper;
import com.gt.util.MD5Utils;

import net.sf.json.JSONObject;


/**
 * @author Administrator
 *
 */
@RequestMapping("system")
@Controller
public class SystemController {

	@RequestMapping(value="/toLogin")
    public String toLogin(HttpServletRequest request )  
    {
    	return "login";
    }
	@RequestMapping(value="/index")
    public String index(HttpServletRequest request )  
    {
    	return "index";
    }
	@RequestMapping(value="/403")
    public String er403(HttpServletRequest request )  
    {
    	return "403";
    }
	// 登入提交地址和applicationontext-shiro.xml配置的loginurl一致。 (配置檔案方式的說法)  
    @RequestMapping(value="/login",method=RequestMethod.POST)  
    public String login(HttpServletRequest request, Map<String, Object> map
    		, @RequestBody String json,HttpSession session) throws Exception {  
       System.out.println("HomeController.login()");  
       // 登入失敗從request中獲取shiro處理的異常資訊。  
       // shiroLoginFailure:就是shiro異常類的全類名.

		JSONObject jsparam = JSONObject.fromObject(json);
		String username=jsparam.optString("username");
		String password=MD5Utils.string2MD5(jsparam.optString("password"));
		String rememberMe=jsparam.optString("rememberMe");
        UsernamePasswordToken token = new UsernamePasswordToken(username, password,rememberMe);
        Subject subject =  SecurityUtils.getSubject();
       
        try {
            subject.login(token);   //完成登入
            UserInfo user=(UserInfo) subject.getPrincipal();
            session.setAttribute("user", user);
            return "index";
        } catch (UnknownAccountException ex) {  
            request.setAttribute("msg", "使用者不存在或者密碼錯誤!");
            return "login";  
        } catch (IncorrectCredentialsException ex) {  
            request.setAttribute("msg", "使用者不存在或者密碼錯誤!");
            return "login";  
        } catch (AuthenticationException ex) {  
            request.setAttribute("msg",ex.getMessage());
            return "login";    
        } catch (Exception ex) {  
            ex.printStackTrace(); 
            request.setAttribute("msg", "內部錯誤,請重試!");
            return "login";  
        }  
    
       // 此方法不處理登入成功,由shiro進行處理.  
    }  
	
}

index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<%@page import="java.util.List"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.HashMap"%>
 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>   
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>  
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>  
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>   
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
</head>
<body>
 
	<h3>index2</h3>
	 <ul>
          <shiro:hasPermission name="userInfo:add" ><li>增加</li></shiro:hasPermission>
        <shiro:hasPermission name="userInfo:del"><li>刪除</li></shiro:hasPermission>
        <shiro:hasPermission name="update"><li>修改</li></shiro:hasPermission>
        <shiro:hasPermission name="userInfo:view"><li>查詢</li></shiro:hasPermission>
      </ul> 
</body>
</html>


引數:{"rememberMe":"false","username":"admin","password":"123456"}

成功登入,且許可權正常