學習一門新技術的思路:html
是什麼?vue
是用來幹嗎的,應用在哪些地方?java
爲何要學?mysql
與它相關的有哪些,須要另外瞭解的知識?jquery
怎樣去學?ios
官方文檔:git
http://shiro.apache.org/index.htmlgithub
官方介紹:web
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.面試
Primary Concerns:
Supporting Features:
......
但,Shiro不會去維護用戶、維護權限,這些須要咱們本身去設計/提供,而後經過相應的接口注入給Shiro
其中Authentication(認證), Authorization(受權), Session Management(會話管理), Cryptography(加密)被 Shiro 框架的開發團隊稱之爲應用安全的四大基石
優勢(爲何要學?):
易於使用、全面、靈活、Web支持、低耦合、被普遍支持普遍使用(是Apache軟件基金會的一部分)
公司要用、面試要問。。。
維基百科:以角色爲基礎的訪問控制(Role-based access control,RBAC),是資訊安全領域中,一種較新且廣爲使用的訪問控制機制,不直接賦予使用者權限,而是將權限賦予角色。
RBAC經過角色關聯用戶,角色關聯權限的方式間接賦予用戶權限。以下圖
有人會問爲何不直接給用戶分配權限,還畫蛇添足的增長角色這一環節呢?
實際上是能夠直接給用戶分配權限,只是直接給用戶分配權限,少了一層關係,擴展性弱了許多,適合那些用戶數量、角色類型少的平臺。
對於一般的系統,好比:存在多個用戶擁有相同的權限,在分配的時候就要分別爲這幾個用戶指定相同的權限,修改時也要爲這幾個用戶的權限進行一一修改。有了角色後,咱們只須要爲該角色制定好權限後,將相同權限的用戶都指定爲同一個角色便可,便於權限管理。
對於批量的用戶權限調整,只需調整用戶關聯的角色權限,無需對每個用戶都進行權限調整,既大幅提高權限調整的效率,又下降了漏調權限的機率。
小結:
RBAC 的優勢主要在於易用和高效。給用戶受權時只須要對角色受權,而後將相應的角色分配給用戶便可;從技術角度講,思路清晰且易於實現,且後期維護時只須要維護關係模型,顯得簡單而高效。
RBAC 的缺點主要有兩個:一個是在進行較爲複雜的權限校驗時須要不斷地遍歷和遞歸,會形成必定的性能影響。另外一個是缺乏數據權限模型,基於 RBAC 來實現數據權限校驗比較複雜和低效。
如今主流的權限管理系統設計大多仍是基於RBAC模型的,只是根據不一樣的業務和設計方案,呈現不一樣的顯示效果。
RBAC模型能夠分爲:RBAC0、RBAC一、RBAC二、RBAC3 四種。其中RBAC0是基礎,也是最簡單的,至關於底層邏輯,RBAC一、RBAC二、RBAC3都是以RBAC0爲基礎的升級。
通常狀況下,使用RBAC0模型就能夠知足常規的權限管理系統設計了。
RBAC0模型:
最簡單的用戶、角色、權限模型。是基礎,定義了能構成 RBAC 權限控制系統的最小的集合。
RBAC0 由四部分構成:
RBAC0對應的表結構:
RBAC0 裏面又包含了2種(用戶和角色的表關係):
那麼,何時該使用多對一的權限體系,何時又該使用多對多的權限體系呢?
若是系統功能比較單一,使用人員較少,崗位權限相對清晰且確保不會出現兼崗的狀況,此時能夠考慮用多對一的權限體系。其他狀況儘可能使用多對多的權限體系,保證系統的可擴展性。如:張三既是行政,也負責財務工做,那張三就同時擁有行政和財務兩個角色的權限。
角色與權限是多對多關係,用戶與權限之間也是多對多關係,經過角色間接創建。
3張基礎表:用戶、角色、權限
2張中間表:創建用戶與角色的多對多關係,角色與權限的多對多關係。
DROP DATABASE IF EXISTS shiro; CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; USE shiro; DROP TABLE IF EXISTS `user`; DROP TABLE IF EXISTS role; DROP TABLE IF EXISTS permission; DROP TABLE IF EXISTS user_role; DROP TABLE IF EXISTS role_permission; /*用戶表*/ CREATE TABLE `user` ( id BIGINT AUTO_INCREMENT, NAME VARCHAR(100), PASSWORD VARCHAR(100), CONSTRAINT pk_users PRIMARY KEY(id) ) CHARSET=utf8 ENGINE=INNODB; /*角色表*/ CREATE TABLE role ( id BIGINT AUTO_INCREMENT, NAME VARCHAR(100), CONSTRAINT pk_roles PRIMARY KEY(id) ) CHARSET=utf8 ENGINE=INNODB; /*權限表*/ CREATE TABLE permission ( id BIGINT AUTO_INCREMENT, NAME VARCHAR(100), CONSTRAINT pk_permissions PRIMARY KEY(id) ) CHARSET=utf8 ENGINE=INNODB; /*用戶角色(關係)表*/ CREATE TABLE user_role ( uid BIGINT, rid BIGINT, CONSTRAINT pk_users_roles PRIMARY KEY(uid, rid) ) CHARSET=utf8 ENGINE=INNODB; /*角色權限(關係)表*/ CREATE TABLE role_permission ( rid BIGINT, pid BIGINT, CONSTRAINT pk_roles_permissions PRIMARY KEY(rid, pid) ) CHARSET=utf8 ENGINE=INNODB;
其餘模型這裏不作過多深究介紹,其餘模型的理解參考此篇文章:
http://www.woshipm.com/pd/1150093.html
權限是資源的集合。
這裏的資源指的是軟件中全部的內容,包括模塊、菜單、頁面、字段、操做功能(增刪改查)等等。
具體的權限配置上,能夠將權限分爲:頁面權限、操做權限和數據權限
頁面權限:全部系統都是由一個個的頁面組成,頁面再組成模塊,用戶是否能看到這個頁面的菜單、是否能進入這個頁面就稱爲頁面權限。
操做權限:用戶凡是在操做系統中的任何動做、交互都是操做權限,如增刪改查等。
數據權限:通常業務管理系統,都有數據私密性的要求:哪些人能夠看到哪些數據,不能夠看到哪些數據。
與Shiro相關的,可能就是SpringSecurity了
官網:https://spring.io/projects/spring-security
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
是一個認證和訪問控制(受權)框架,來保護基於Spring的應用程序,專一於爲java應用提供認證和受權。
SpringSecurity屬於Spring全家桶的一部分,對於Spring項目來講,其實使用它是討巧的。
關於Shiro和SpringSecurity的對比,筆者在網上查詢了下資料,並沒發現有講得很好的。
大多說的是因需使用,看使用場景,選擇使用。但在簡單性上,仍是優先選擇Shiro。本身對這兩大框架的應用也並無太多,所以也沒法講清。在此只是提一嘴SpringSecurity,感興趣的可自行對比研究。
根據官網:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.1</version> </dependency> <!-- Shiro uses SLF4J for logging. We'll use the 'simple' binding in this example app. See http://www.slf4j.org for more info. --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/resources/log4j.properties
log4j.properties:
log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n # General Apache libraries log4j.logger.org.apache=WARN # Spring log4j.logger.org.springframework=WARN # Default Shiro logging log4j.logger.org.apache.shiro=INFO # Disable verbose logging log4j.logger.org.apache.shiro.util.ThreadContext=WARN log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini:
[users] # user 'root' with password 'secret' and the 'admin' role root = secret, admin # user 'guest' with the password 'guest' and the 'guest' role guest = guest, guest # user 'presidentskroob' with password '12345' ("That's the same combination on # my luggage!!!" ;)), and role 'president' presidentskroob = 12345, president # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' darkhelmet = ludicrousspeed, darklord, schwartz # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # # Each line conforms to the format defined in the # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc # ----------------------------------------------------------------------------- [roles] # 'admin' role has all permissions, indicated by the wildcard '*' admin = * # The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5
官網上有一個10分鐘教程,它讓咱們先看Quickstart.java學習
https://github.com/apache/shiro/blob/master/samples/quickstart/src/main/java/Quickstart.java
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Simple Quickstart application showing how to use Shiro's API. * 簡單的快速啓動應用程序,演示如何使用Shiro的API。 * @since 0.9 RC2 */ public class Quickstart { //日誌門面,默認是commons-logging private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class); public static void main(String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityUtils.setSecurityManager(securityManager); // Now that a simple Shiro environment is set up, let's see what you can do: // get the currently executing user: //得到當前執行用戶(重要!!) Subject currentUser = SecurityUtils.getSubject(); // Do some stuff with a Session (no need for a web or EJB container!!!) //不一樣於HttpSession,不須要Web或EJB的容器支持 Session session = currentUser.getSession(); //存值取值 session.setAttribute("someKey", "aValue"); String value = (String) session.getAttribute("someKey"); if (value.equals("aValue")) { log.info("Retrieved the correct value! [" + value + "]"); } // let's login the current user so we can check against roles and permissions: //當前用戶身份驗證 if (!currentUser.isAuthenticated()) { //建立標記,其中用戶名和密碼是讀取shiro.ini配置文件中的 UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); try { //執行登陸操做!!!源碼中看不到,但就是這個操做! currentUser.login(token); } catch (UnknownAccountException uae) {//未知用戶異常(用戶不存在) log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) {//密碼不正確 log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) {//如密碼輸入錯誤次數過多,鎖帳戶 log.info("The account for username " + token.getPrincipal() + " is locked. " + "Please contact your administrator to unlock it."); } // ... catch more exceptions here (maybe custom ones specific to your application? catch (AuthenticationException ae) {//大異常,相似java中的Exception //unexpected condition? error? } } //say who they are: //print their identifying principal (in this case, a username): log.info("User [" + currentUser.getPrincipal() + "] logged in successfully."); //test a role: if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); } //粗粒度 //test a typed permission (not instance-level) if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); } //細粒度 //a (very powerful) Instance Level permission: if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); } //註銷 //all done - log out! currentUser.logout(); //結束系統 System.exit(0); } }
運行結果:
打印了一堆默認的日誌消息
2020-09-19 13:09:07,652 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 2020-09-19 13:09:07,733 INFO [Quickstart] - Retrieved the correct value! [aValue] 2020-09-19 13:09:07,734 INFO [Quickstart] - User [lonestarr] logged in successfully. 2020-09-19 13:09:07,734 INFO [Quickstart] - May the Schwartz be with you! 2020-09-19 13:09:07,734 INFO [Quickstart] - You may use a lightsaber ring. Use it wisely. 2020-09-19 13:09:07,735 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. Here are the keys - have fun!
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
<scope>runtime</scope>
做用域①類的描述
/** * Simple Quickstart application showing how to use Shiro's API. * 簡單的快速啓動應用程序,演示如何使用Shiro的API。 */
②經過工廠模式建立SecurityManager的實例對象
// 讀取類路徑下的shiro.ini文件 // Use the shiro.ini file at the root of the classpath // (file: and url: prefixes load from files and urls respectively): Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 已經創建了一個簡單的Shiro環境
③獲取當前的Subject
// get the currently executing user: 獲取當前正在執行的用戶 Subject currentUser = SecurityUtils.getSubject();
④session的操做
// 用會話作一些事情(不須要web或EJB容器!!!) // Do some stuff with a Session (no need for a web or EJB container!!!) //存值取值 Session session = currentUser.getSession(); //得到session session.setAttribute("someKey", "aValue"); //設置Session的值! String value = (String) session.getAttribute("someKey"); //從session中獲取 值 if (value.equals("aValue")) { //判斷session中是否存在這個值! log.info("==Retrieved the correct value! [" + value + "]"); }
⑤用戶認證
// 測試當前的用戶是否已經被認證,便是否已經登陸! // let's login the current user so we can check against roles and permissions: if (!currentUser.isAuthenticated()) { // isAuthenticated();是否定證 //將用戶名和密碼封裝爲 UsernamePasswordToken ; UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa"); token.setRememberMe(true); //記住我功能 try { currentUser.login(token); //執行登陸,能夠登陸成功的! } catch (UnknownAccountException uae) { //若是沒有指定的用戶,則UnknownAccountException異常 log.info("There is no user with username of " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { //密碼不正確的異常! log.info("Password for account " + token.getPrincipal() + " was incorrect!"); } catch (LockedAccountException lae) { //用戶被鎖定的異常 log.info("The account for username " + token.getPrincipal() + "is locked. " +"Please contact your administrator to unlock it."); } // ... catch more exceptions here (maybe custom ones specific toyour application? catch (AuthenticationException ae) { //認證異常,上面的異常都是它的子類 //unexpected condition? error? } } // log.info("User [" + currentUser.getPrincipal() + "] logged insuccessfully.");
⑥角色檢查
//test a role: //是否存在某一個角色 if (currentUser.hasRole("schwartz")) { log.info("May the Schwartz be with you!"); } else { log.info("Hello, mere mortal."); }
⑦權限檢查,粗粒度
//測試用戶是否具備某一個權限,行爲 //test a typed permission (not instance-level) if (currentUser.isPermitted("lightsaber:wield")) { log.info("You may use a lightsaber ring. Use it wisely."); } else { log.info("Sorry, lightsaber rings are for schwartz masters only."); }
⑧權限檢查,細粒度
//測試用戶是否具備某一個權限,行爲,比上面更加的具體! //a (very powerful) Instance Level permission: if (currentUser.isPermitted("winnebago:drive:eagle5")) { log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " + "Here are the keys - have fun!"); } else { log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!"); }
⑨註銷操做
//執行註銷操做! //all done - log out! currentUser.logout();
⑩退出系統
System.exit(0);
Shiro 架構包含三個主要的理念:Subject、SecurityManager和 Realm
咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。
內部架構
Shiro中其餘的一些概念:
場景任務:
一、建庫建表sql
DROP DATABASE IF EXISTS shiro; CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; USE shiro; /*用戶表*/ /*salt 是鹽,用來和 shiro 結合的時候,加密用的*/ CREATE TABLE `user`( id INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(255)DEFAULT NULL, `password` VARCHAR(255)DEFAULT NULL, salt VARCHAR(255)DEFAULT NULL, PRIMARY KEY(id) )ENGINE=INNODB DEFAULT CHARSET=utf8;
二、新建一個SpringBoot項目
勾選lombok、SpringWeb、thymeleaf、JDBC API以及MySQL Driver
三、導入Pom依賴,shiro兩種方式
第一種:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
第二種:
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.6.0</version> </dependency>
其中第二種會報錯,在末尾第九部分會具體說明出現了什麼錯誤以及怎麼解決
mybatis依賴:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>
四、配置application.yml文件
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 username: root password: admin thymeleaf: cache: false encoding: utf-8 mode: HTML5 servlet: content-type: text/html mybatis: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.cqy.shiro.pojo configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl server: servlet: context-path: /shiro
五、編寫一個簡單的register.html註冊頁面(使用Vue)
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>註冊</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'submitregister', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, mounted: function(){ }, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { //暫時不寫,後面寫了controller後再編寫 }); } } }); }); </script> <div id="work"> <h3>註冊</h3> 用戶名:<input type="text" v-model="user.name"><br> 密碼:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">提交</button> </div> </body> </html>
六、PageController用於頁面跳轉
register.html放在templates裏面,直接訪問不了,須要內部跳轉訪問
@Controller public class PageController { @GetMapping("/register") public String register(){ return "register"; } }
七、建立pojo,User類
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String password; private String salt; }
八、建立UserMapper和UserMapper.xml
這裏爲了便捷,直接使用Class再也不進行dao、service層的編寫
提供兩個方法
@Repository public interface UserMapper { //根據name查詢用戶 public User queryUserByName(String name); //添加用戶 public int addUser(User user); }
<?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.cqy.shiro.mapper.UserMapper"> <select id="queryUserByName" resultType="user"> select * from `user` where `name`=#{name} </select> <insert id="addUser" parameterType="user"> insert into `user` values (#{id},#{name},#{password},#{salt}) </insert> </mapper>
九、建立一個login.html登陸頁面用於登陸
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>登陸</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'submitlogin', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { //暫時不寫,後面寫了controller後再編寫 }); } } }); }); </script> <div id="work"> <h3>登陸</h3> 用戶名:<input type="text" v-model="user.name"><br> 密碼:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">登陸</button> </div> </body> </html>
一、導入Shiro的Pom依賴(文上)
二、自定義一個 Realm 的類
用來編寫一些認證與受權的邏輯
//自定義Realm public class UserRealm extends AuthorizingRealm { //執行受權邏輯 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //執行時機:在用戶進行登陸,認證身份時執行 System.out.println("====>>>>執行了受權邏輯PrincipalCollection"); return null; } //執行認證邏輯 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //執行時機:在用戶登陸後,每訪問一次那些須要權限才能訪問的頁面時執行 System.out.println("====>>>>執行了認證邏輯AuthenticationToken"); //暫時爲空,後面根據業務邏輯編寫認證代碼 return null; } }
三、編寫Shiro配置類,config包下
@Configuration public class ShiroConfig { //建立ShiroFilterFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); return shiroFilterFactoryBean; } //建立DefaultWebSecurityManager @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("getUserRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //關聯Realm securityManager.setRealm(userRealm); return securityManager; } //建立Hash憑證匹配器 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); //md5默認就是加密1次 hashedCredentialsMatcher.setHashAlgorithmName("md5"); hashedCredentialsMatcher.setHashIterations(1); return hashedCredentialsMatcher; } //建立Realm對象,須要自定義 @Bean public UserRealm getUserRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher hashedCredentialsMatcher){ UserRealm userRealm = new UserRealm(); //關聯憑證匹配器!!! userRealm.setCredentialsMatcher(hashedCredentialsMatcher); return userRealm; } }
一、編寫註冊的Controller
@RestController public class ShiroController { @Autowired UserMapper userMapper; @PostMapping("/submitregister") public Object submitRegister(@RequestBody User user){ String name = user.getName(); String password = user.getPassword(); //根據name從數據庫中查詢對應的user if (null!=userMapper.queryUserByName(name)){ return "用戶名已經被使用,請從新輸入"; } //經過隨機方式建立鹽,而且加密算法採用md5 String salt = new SecureRandomNumberGenerator().nextBytes().toString(); //加密次數,默認加密1次 int times = 1; String algorithmName = "md5"; String encodePassword = new SimpleHash(algorithmName,password,salt,times).toString(); //鹽若是丟失了,沒法驗證密碼是否正確,所以會添加到數據庫保存 user.setSalt(salt); user.setPassword(encodePassword); //添加到數據庫 userMapper.addUser(user); return 0; } }
二、register.html修改
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>註冊</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'register', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, mounted: function(){ }, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { var result = response.data; //若是註冊成功,跳轉到註冊成功頁面 if (0===result){ location.href="registerSuccess"; } }); } } }); }); </script> <div id="work"> <h3>註冊</h3> 用戶名:<input type="text" v-model="user.name"><br> 密碼:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">提交</button> </div> </body> </html>
三、建立registerSuccess.html註冊成功頁面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>註冊成功頁面</title> </head> <body> <h1>恭喜您,註冊成功!</h1> </body> </html>
四、PageController中添加跳轉
@GetMapping("/registerSuccess") public String registerSuccess(){ return "registerSuccess"; }
五、啓動SpringBoot項目
訪問http://localhost:8080/shiro/register
輸入用戶名和密碼,點擊提交進行註冊
發現頁面跳轉到註冊成功頁面,查看數據庫user表
加密註冊成功!
一、編寫Realm裏的認證邏輯
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //將AuthenticationToken轉成UsernamePasswordToken UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //獲取帳號名 String username = token.getUsername(); //根據name獲取用戶對象從而拿到密碼和鹽值 User user = userMapper.queryUserByName(username); //獲取用戶密碼(數據庫中加密後的),須要經過user對象來拿 String passwordInDB = user.getPassword(); //拿到鹽 String salt = user.getSalt(); //認證信息裏存放帳號密碼,getName()是當前Realm的繼承方法,一般返回當前類名:UserRealm SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, passwordInDB, ByteSource.Util.bytes(salt), getName()); return simpleAuthenticationInfo; }
二、編寫登陸的Controller,在ShiroController中
@PostMapping("/submitlogin") public Object submitLogin(@RequestBody User userParam){ String name = userParam.getName(); String password = userParam.getPassword(); //使用Shiro方式獲取當前用戶 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(name, password); //執行登陸,與Realm中查詢的數據庫中用戶的信息進行比較 try { subject.login(token); //根據用戶名 User user = userMapper.queryUserByName(name); subject.getSession().setAttribute("user",user); return 0; } catch (AuthenticationException e) { return "帳號或密碼錯誤"; } }
三、login.html修改
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>登陸</title> <script src="js/vue/2.5.16/vue.min.js"></script> <script src="js/axios/0.17.1/axios.min.js"></script> <!-- <script src="js/jquery/2.0.0/jquery.min.js"></script>--> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <script> $(function () { var data4 = { uri: 'submitlogin', user: { name: '', password:'' } }; var vue = new Vue({ el: '#work', data: data4, methods: { submit: function () { var url = this.uri; axios.post(url,this.user).then(function (response) { var result = response.data; //若是返回0,說明認證成功,即跳轉到登錄成功頁面 if (0===result){ location.href="loginSuccess"; } }); } } }); }); </script> <div id="work"> <h3>登陸</h3> 用戶名:<input type="text" v-model="user.name"><br> 密碼:<input type="password" v-model="user.password"><br> <button type="button" @click="submit">登陸</button> </div> </body> </html>
四、建立loginSuccess.html登陸成功頁面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>登陸成功頁面</title> </head> <body> <h1>恭喜您登陸成功!</h1> <span>當前用戶是: <a th:text="${session.user.name}"></a> </span> </body> </html>
五、PageController中添加跳轉
@GetMapping("/loginSuccess") public String loginSuccess(){ return "loginSuccess"; }
六、啓動SpringBoot項目
訪問http://localhost:8080/shiro/login
輸入用戶名密碼,點擊登陸
發現頁面跳轉到登陸成功頁面,並顯示出了當前登陸用戶:紙飛機
一、在templates目錄下新建一個user目錄,編寫兩個頁面add.html、update.html
<body> <h1>add</h1> </body>
<body> <h1>update</h1> </body>
二、編寫對應跳轉到兩個頁面的Controller
@GetMapping("/user/add") public String toAdd(){ return "user/add"; } @GetMapping("/user/update") public String toUpdate(){ return "user/update"; }
三、在templates目錄下建立一個首頁index.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首頁</title> </head> <body> <a href="login">點擊進入登陸頁面</a> <br> <br> <a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a> </body> </html>
四、跳轉到首頁index.html的Controller
@GetMapping({"/","/index"}) public String toIndex(){ return "index"; }
五、運行測試頁面跳轉是否OK(能夠直接進入add或update頁面)
六、準備編寫Shiro的內置過濾器(設置過濾規則)
//建立ShiroFilterFactoryBean @Bean(name ="shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); //修改到要跳轉的login頁面; shiroFilterFactoryBean.setLoginUrl("/login"); /* * 經常使用的有以下過濾器: * anon: 無需認證就能夠訪問 * authc: 必須認證才能夠訪問 * user: 若是使用了記住我功能就能夠直接訪問 * perms: 擁有某個資源權限才能夠訪問 * role: 擁有某個角色權限才能夠訪問 */ //注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就再也不繼續驗證 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/user/add","authc"); filterMap.put("/user/update","authc"); //能夠直接使通配符 //filterMap.put("/user/*","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; }
七、啓動測試訪問,發現點擊add|update連接沒法進入,已經被攔截,會自動給咱們跳轉到login.jsp頁面。咱們須要自定義跳轉頁面。
八、在ShiroFilterFactoryBean方法中添加shiroFilterFactoryBean.setLoginUrl
//修改到要跳轉的login頁面; //前面咱們已經建立好了login.html頁面和跳轉的Controller shiroFilterFactoryBean.setLoginUrl("/login");
九、再次啓動測試,發現點擊add|update連接會直接跳轉到login.html登陸頁面讓你進行登陸
一、在登陸成功頁面loginSuccess添加退出的連接
<a href="logout">退出</a>
二、編寫退出的Controller
//退出登陸,跳轉到首頁 @GetMapping("/logout") public String loginOut() { Subject subject = SecurityUtils.getSubject(); if(subject.isAuthenticated()) subject.logout();//會自動讓session失效 return "redirect:index"; }
簡單Shiro的過濾器攔截請求
一、在登陸成功頁面loginSuccess添加跳轉到首頁index.html的連接
<a href="index">點擊跳轉到首頁</a>
二、在ShiroFilterFactoryBean中添加
//受權攔截,須要放在認證的上面。某個用戶有add的權限才能夠訪問。 filterMap.put("/user/add","perms[user:add]");
三、啓動測試,登陸訪問add,發現出現401錯誤,Unauthorized未受權
當咱們實現權限攔截後,shiro會自動跳轉到未受權的頁面,但咱們沒有這個頁面,因此401
注意:ShiroFilterFactoryBean中完成受權配置後,此處訪問add必需要先登陸,未登陸的狀況下,受權的perms參數也會直接跳轉到login,要求你先登陸。
四、配置一個未受權的提示的頁面,增長一個controller提示。而後在ShiroFilterFactoryBean中添加配置
@RequestMapping("/noauth") @ResponseBody public String noAuth(){ return "未經受權不能訪問此頁面"; }
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
五、再次啓動訪問測試。攔截成功。
六、ShiroFilterFactoryBean中完整代碼
//建立ShiroFilterFactoryBean @Bean(name ="shiroFilterFactoryBean") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); /* * 經常使用的有以下過濾器: * anon: 無需認證就能夠訪問 * authc: 必須認證才能夠訪問 * user: 若是使用了記住我功能就能夠直接訪問 * perms: 擁有某個資源權限才能夠訪問 * role: 擁有某個角色權限才能夠訪問 */ //注意此處使用的是LinkedHashMap,是有順序的,shiro會按從上到下的順序匹配驗證,匹配了就再也不繼續驗證 //因此上面的url要苛刻,寬鬆的url要放在下面,尤爲是"/**"要放到最下面,若是放前面的話其後的驗證規則就沒做用了。 Map<String, String> filterMap = new LinkedHashMap<>(); //受權攔截,須要放在認證的上面。某個用戶有add的權限才能夠訪問。 //受權,正常狀況下,沒有受權會跳轉到未受權頁面 filterMap.put("/user/update","perms[user:update]"); filterMap.put("/user/add","perms[user:add]"); //認證 // filterMap.put("/user/add","authc"); // filterMap.put("/user/update","authc"); //修改到要跳轉的login頁面; shiroFilterFactoryBean.setLoginUrl("/login"); //跳轉到未受權的請求頁面 shiroFilterFactoryBean.setUnauthorizedUrl("/noauth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; }
而如何給某個用戶具體受權,進行權限的操做等。本文不作細緻的講解,由於這須要結合項目中具體的業務邏輯來進行編寫,而且須要進行數據庫權限表等一系列的設計操做等,相較複雜。
在文章集成SpringBoot部分,說了Shiro的導入Pom依賴的兩種方式。其中第二種可能會報錯。
報錯內容一:
可能會出現啓動時沒法找到shiroFilterFactoryBean的bean
*************************** APPLICATION FAILED TO START *************************** Description: Method filterShiroFilterRegistrationBean in org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration required a bean named 'shiroFilterFactoryBean' that could not be found. Action: Consider defining a bean named 'shiroFilterFactoryBean' in your configuration.
解決:若是在Shiro配置類中的ShiroFilterFactoryBean的方法名沒有命名爲shiroFilterFactoryBean,須要手動在@Bean上添加上名字,不然沒法識別注入。以下
@Bean(name = "shiroFilterFactoryBean")
報錯內容二:
項目沒法啓動,報沒有authorizer的bean的錯誤: No bean named 'authorizer' available
解決:本身在配置類中配置
@Configuration public class ShiroConfig { @Bean public Authorizer authorizer(){ return new ModularRealmAuthorizer(); } }
具體參考這篇文章:https://www.cnblogs.com/insan...
本文只是Shiro的入門學習,適合於新手初探Shiro,有個大體的瞭解與應用。
主要講了一些Shiro的基礎知識,以及集成SpringBoot簡單應用的流程和配置,而對於Shiro的一些源碼等內容並無進行一個分析和講解,可能會在以後的進階文章中補充。
學習建議:
一、Shiro的一些配置類和簡單業務邏輯代碼是死的,大可不用花不少精力去死記那些單詞又長的代碼,在初學以後應總結成本身的環境模板,用時即取便可,多將精力放在Shiro的整個認證受權的運行過程上,瞭解是怎麼進行的。
二、Shiro可定製化程度很高,在不少涉及到權限的項目中都有運用,可自行去碼雲或GitHub上尋找優質的(star數較高)的開源項目(如權限管理系統)來進行學習,看實際的項目中Shiro的應用。
三、本文文中涉及的代碼。須要的話自行下載:連接:https://pan.baidu.com/s/1xicF...
提取碼:quo0
參考:
一、http://shiro.apache.org/index... Shiro官網
二、https://www.bilibili.com/vide... 碰見狂神說,【狂神說Java】SpringBoot整合Shiro框架
三、https://zhuanlan.zhihu.com/p/... 我沒有三顆心臟,Shiro安全框架【快速入門】就這一篇!公衆號wmyskxz
四、https://juejin.im/post/684490... 碼農小胖哥,Spring Security 實戰乾貨: RBAC權限控制概念的理解
五、http://www.woshipm.com/pd/115... 珣玗琪,RBAC模型:基於用戶-角色-權限控制的一些思考
六、https://how2j.cn/k/shiro/shir... How2j Shiro系列教材
謝謝您看完這篇技術文章
若是能對您有所幫助
那將是一件很美好的事情
保持好奇心的終身學習也是極棒的事
願世界簡單又多彩
轉載請註明出處
——紙飛機