前面已經介紹過springBoot和mybatis、JPA的整合,本篇主要是在上一篇的基礎上整合Shiro進行權限的管理。java
源碼連接mysql
先簡單介紹下shiro吧,其實它就是一個安全框架,相比spring Security使用起來更簡單易懂。引用一張架構圖(圖片來自官網),從圖中你們能夠看到他的三大核心:git
-Subject 當前用戶操做
- SecurityManager 用於管理全部的Subject
- Realms 用於進行權限信息的驗證,也是咱們須要本身實現的。web
咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。
Apache Shiro 核心經過 Filter 來實現,就好像SpringMvc 經過DispachServlet 來主控制同樣。
既然是使用 Filter 通常也就能猜到,是經過URL規則來進行過濾和權限校驗,因此咱們須要定義一系列關於URL的規則和訪問權限。
另外咱們能夠經過Shiro 提供的會話管理來獲取Session中的信息。Shiro 也提供了緩存支持,使用 CacheManager 來管理。spring
要集成shiro咱們必須知道他的幾個核心對象,分別是:
第一:ShiroFilterFactory,Shiro過濾器工廠類,具體的實現類是:ShiroFilterFactoryBean,此實現類是依賴於SecurityManager安全管理器。
第二:SecurityManager,Shiro的安全管理,主要是身份認證的管理,緩存管理,cookie管理,因此在實際開發中咱們主要是和SecurityManager進行打交道的,ShiroFilterFactory主要配置好了Filter就能夠了。固然SecurityManager並進行身份認證緩存的實現,咱們須要進行對應的編碼而後進行注入到安全管理器中。
第三:Realm,用於身份信息權限信息的驗證。
第四:其它的就是緩存管理,記住登陸之類的,這些大部分都是須要本身進行簡單的實現,而後注入到SecurityManager讓Shiro的安全管理器進行管理就行了。sql
概念介紹完了 ,咱們開始動手,完成咱們的demo,具體步驟以下:數據庫
(a) pom.xml中添加Shiro依賴;
(b) 注入Shiro Factory和SecurityManager。
(c) 身份認證
(d) 權限控制apache
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.pxk</groupId> <artifactId>SpringBootDemo_JPA</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringBootDemo_JPA Maven Webapp</name> <url>http://maven.apache.org</url> <build> <finalName>SpringBootDemo_JPA</finalName> </build> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- web容器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.10.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!--日誌 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- druid鏈接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> </dependencies> </project>
使用springBoot的配置方式,新建config類,主要配置兩個類ShiroFilterFactory和SecurityManager緩存
package com.pxk.springboot.config; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ShiroConfiguration { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設置SecuritManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 攔截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出過濾器,其中的具體代碼Shiro已經替咱們實現了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 過濾鏈定義,從上向下順序執行,通常將 /**放在最爲下邊 -->:這是一個坑呢,一不當心代碼就很差使了; // <!-- authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問--> filterChainDefinitionMap.put("/**", "authc"); // 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登陸成功後要跳轉的連接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未受權界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); return securityManager; } }
在認證、受權內部實現機制中都有提到,最終處理都將交給Real進行處理。由於在Shiro中,最終是經過Realm來獲取應用程序中的用戶、角色及權限信息的。一般狀況下,在Realm中會直接從咱們的數據源中獲取Shiro須要的驗證信息。能夠說,Realm是專用於安全框架的DAO.
認證明現
Shiro的認證過程最終會交由Realm執行,這時會調用Realm的getAuthenticationInfo(token)方法。
該方法主要執行如下操做:
一、檢查提交的進行認證的令牌信息
二、根據令牌信息從數據源(一般爲數據庫)中獲取用戶信息
三、對用戶信息進行匹配驗證。
四、驗證經過將返回一個封裝了用戶信息的AuthenticationInfo實例。
五、驗證失敗則拋出AuthenticationException異常信息。
而在咱們的應用程序中要作的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,重載doGetAuthenticationInfo (),重寫獲取用戶信息的方法。
既然須要進行身份權限控制,那麼少不了建立用戶實體類,權限實體類。
在權限管理系統中,有這麼幾個角色很重要,這個要是不清楚的話,那麼就很難理解,咱們爲何這麼編碼了。第一是用戶表:在用戶表中保存了用戶的基本信息,帳號、密碼、姓名,性別等;第二是:權限表(資源+控制權限):這個表中主要是保存了用戶的URL地址,權限信息;第三就是角色表:在這個表重要保存了系統存在的角色;第四就是關聯表:用戶-角色管理表(用戶在系統中都有什麼角色,好比admin,vip等),角色-權限關聯表(每一個角色都有什麼權限能夠進行操做)。依據這個理論,咱們進行來進行編碼,很明顯的咱們第一步就是要進行實體類的建立。在這裏咱們使用Mysql和JPA進行操做數據庫。安全
新建實體類UserInfo、SysRole、SysPermission(採用逆向工程生成數據庫表,而後導入數據)--非關鍵的get、set方法省略
UserInfo.java
package com.pxk.springboot.domain; 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 Administrator * */ @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;// 一個用戶具備多個角色 /** * 密碼鹽. * * @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 + "]"; } }
SysRole.java
package com.pxk.springboot.domain; 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 Administrator * */ @Entity public class SysRole implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue 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;// 一個角色對應多個用戶 @Override public String toString() { return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available + ", permissions=" + permissions + "]"; } }
SysPermission.java
package com.pxk.springboot.domain; 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; /** * 權限實體類; * */ @Entity public class SysPermission implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue 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; @Override public String toString() { return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url + ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available=" + available + "]"; } }
MyShiroRealm.java
該類是實現權限認證的核心,須要咱們手動實現
package com.pxk.springboot.config; 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 org.apache.shiro.util.ByteSource; import com.pxk.springboot.domain.SysPermission; import com.pxk.springboot.domain.SysRole; import com.pxk.springboot.domain.UserInfo; import com.pxk.springboot.serivce.UserInfoService; /** * 身份校驗覈心類 * * @author Administrator * */ public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; /** * 認證信息(身份驗證) Authentication 是用來驗證用戶身份 */ @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 = userInfoService.findByUsername(username); System.out.println("----->>userInfo=" + userInfo); if (userInfo == null) { return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用戶名 userInfo.getPassword(), // 密碼 ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt getName() // realm name ); return authenticationInfo; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal(); for (SysRole role : userInfo.getRoleList()) { authorizationInfo.addRole(role.getRole()); System.out.println(role.getPermissions()); for (SysPermission p : role.getPermissions()) { System.out.println(p); authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } }
核心功能基本完成,接下來就是編寫controller和頁面了
頁面代碼我就不詳細貼出了,直接給你們源碼地址進行下載。