springboot shiro許可權管理
整合shiro大概分這麼一個步驟:
(一) pom.xml中新增Shiro依賴;
(二) 注入Shiro Factory和SecurityManager。
(三) 身份認證
(四) 許可權控制
一: 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 Factory和SecurityManager。
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執行,這時會呼叫Realm的getAuthenticationInfo(token)方法。
該方法主要執行以下操作:
1、檢查提交的進行認證的令牌資訊
2、根據令牌資訊從資料來源(通常為資料庫)中獲取使用者資訊
3、對使用者資訊進行匹配驗證。
4、驗證通過將返回一個封裝了使用者資訊的AuthenticationInfo例項。
5、驗證失敗則丟擲AuthenticationException異常資訊。
而在我們的應用程式中要做的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,過載doGetAuthenticationInfo (),重寫獲取使用者資訊的方法。
既然需要進行身份許可權控制,那麼少不了建立使用者實體類,許可權實體類。
在許可權管理系統中,有這麼幾個角色很重要,這個要是不清楚的話,那麼就很難理解,我們為什麼這麼編碼了。第一是使用者表:在使用者表中儲存了使用者的基本資訊,賬號、密碼、姓名,性別等;第二是:許可權表(資源+控制權限):這個表中主要是儲存了使用者的URL地址,許可權資訊;第三就是角色表:在這個表重要儲存了系統存在的角色;第四就是關聯表:使用者-角色管理表(使用者在系統中都有什麼角色,比如admin,vip等),角色-許可權關聯表(每個角色都有什麼許可權可以進行操作)。依據這個理論,我們進行來進行編碼,很明顯的我們第一步就是要進行實體類的建立。在這裡我們使用Mysql和JPA進行操作資料庫。
3.1:那麼我們先在pom.xml中引入mysql和JPA的依賴:
<!-- 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,對應的是資料庫的五張表:
1表UserInfo、2表SysUserRole、3表SysRole、4表SysRolePermission、5表SysPermission
這時候執行程式,就會自動建表,然後我們新增一些資料:
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"}
成功登入,且許可權正常