日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Shiro从入门到实战(整合进SpringBoot)

發布時間:2023/12/16 javascript 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Shiro从入门到实战(整合进SpringBoot) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 一、Shiro是什么?
  • 二、Shiro 的功能介紹
    • 1.基本功能點
    • 2.功能點介紹
  • 三、Shiro 的架構
    • 1.Shrio的應用層面架構
    • 2.Shiro的核心架構
  • 四、Shiro入門案例
    • 1.shiro.ini
    • 2.Quickstart.class
  • 五、Shiro實戰-整合進Spring Boot
    • 1.創建父工程shiroDemo
    • 2.創建shiroAuth子模塊(Maven)
    • 3.代碼結構講解
    • 4.各個類詳細講解
    • 5.補充多Realm認證


前言

這篇文章主要是為了輔助記憶Shiro的,下面的代碼主要復用了我看的一個視頻里面的代碼:https://www.bilibili.com/video/BV1Tf4y1w7Yo,所以有些地方如果不太了解的話可以去看看這個視頻,不過這個視頻有些地方可能有點繞,所以如果有shiro基礎,只是想回顧一下的話,我覺得看這篇文章應該就可以很快回顧起來,如果沒有shiro基礎的話可能還是得跟著視頻一起看。由于本人水平有限,所以可能有些地方寫的不是很好,有發現什么問題的話,大家都可以指出來,共同進步!


一、Shiro是什么?

  • Apache Shiro是Java的一個安全(權限)框架。
  • Shiro可以非常容易的開發出足夠好的應用,其不僅可以用在JavaSE環境,也可以用在JavaEE環境。
  • Shiro可以完成:認證、授權、加密、會話管理、與Web集成、緩存等。

Shiro官網:http://shiro.apache.org/

二、Shiro 的功能介紹

1.基本功能點

2.功能點介紹

  • Authentication(認證):判斷一個用戶是否為合法用戶的處理過程。最常用的身份認證方式是核對用戶輸入的用戶名和密碼是否與系統中存儲的用戶名和密碼一致。還可以采用指紋和人臉識別等方式認證;
  • Authorization(授權):訪問控制或權限驗證,簡單來說就是給不同的角色授予不同的權限,以及驗證某個已認證的用戶是否擁有某個權限;
  • Session Manager(會話管理):管理所有用戶登錄后的會話信息。用戶登錄后用Subject.getSession() 即可獲取會話,在沒有退出之前,用戶的所有信息都在會話中;會話可以是普通JavaSE環境,也可以是Web環境的;
  • Cryptography(加密):保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲;
  • Web Support(Web支持):可以非常容易的集成到Web環境;
  • Caching(緩存):用戶登錄后,其用戶信息、擁有的角色/權限不必每次去查,直接從緩存中獲取,這樣可以提高效率;
  • Concurrency:Shiro支持多線程應用的并發驗證,即如在一個線程中開啟另一個線程,能把權限自動傳播過去;
  • Testing:提供測試支持;
  • Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
  • Remember Me(記住我):非常常見的功能,即一次登錄后,下次無需登錄;

三、Shiro 的架構

1.Shrio的應用層面架構

  • Subject:任何直接與應用代碼交互的對象。Subject代表了當前“用戶”,這個用戶不具體指代某個人,只要與當前應用交互的任何東西都是Subject,如第三方服務、網絡爬蟲、機器人等;所有Subject實例都必須被綁定到一個SecurityManager上,SecurityManager才是實際的執行者;
  • SecurityManager(安全管理器):與安全有關的操作都會與SecurityManager進行交互;管理著所有的Subject;它是Shiro的核心,負責與Shiro的其他組件進行交互,相當于SpringMVC中DispatcherServlet的角色;
  • Realm:擔當Shiro和你的應用程序的安全數據之間的“橋梁”或“連接器”。Shiro可以從Realm中獲取安全數據(如用戶、角色、權限),即認證和授權等操作所需要的安全數據都需要從Realm中獲得;

2.Shiro的核心架構

  • Subject:任何直接與應用代碼交互的對象;
  • SecurityManager(安全管理器):所有與安全相關的操作都會與SecurityManager進行交互;它管理著所有的Subject;它是Shiro的核心,負責與Shiro的其他組件進行交互,相當于SpringMVC中DispatcherServlet的角色;
  • Authenticator:負責Subject認證,當一個用戶嘗試登錄時,該邏輯被 Authenticator執行;且它是一個擴展點,可以自定義實現;可以使用認證策略(Authentication Strategy),即什么情況下算用戶認證通過;
  • Authorizer(授權器):即訪問控制器,用來決定主體(Subject)是否有權限進行相應的操作;即控制著用戶能訪問應用中的哪些功能;
  • Realm:可以有1個或多個Realm,可以認為是安全實體數據源,即用于獲取安全數據;可以是JDBC實現,也可以是內存實現等等;它由用戶提供,所以一般在應用中都需要實現自己的Realm;
  • SessionManager:管理所有用戶登錄后的會話信息。用戶登錄后用Subject.getSession() 即可獲取會話,在沒有退出之前,用戶的所有信息都在會話中;會話可以是普通JavaSE環境,也可以是Web環境的;
  • CacheManager(緩存控制器):管理如用戶、角色、權限等信息的緩存;因為這些數據
    基本上很少改變,放到緩存中后可以提高訪問的性能;
  • Cryptography:密碼模塊,Shiro提供了一些常見的加密組件用于如密碼加密/解密;

四、Shiro入門案例

以下是Shiro官網的一個入門小案例,供我們參考學習,我們主要關注其中的Quickstart.class以及shiro.ini中的部分代碼就好。shiro.ini中主要放的就是用戶、角色以及權限等數據,Quickstart.class就是簡單的模擬一下認證及授權的過程,其中所用到的數據就來源于shiro.ini中,實際開發中我們的這些數據肯定是從數據庫中獲得。

1.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] # '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 'delete' (action) the User(type) with # license plate 'zhangsan' (instance specific id) goodguy = User:delete:zhangsan

2.Quickstart.class

package com.m33.shiro.helloworld;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;public class Quickstart {private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);public static void main(String[] args) {// 創建SecurityManager(下面這三行看看就好)Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// get the currently executing user// 獲取當前的 Subject!調用 SecurityUtils.getSubject();Subject currentUser = SecurityUtils.getSubject();// Do some stuff with a Session (no need for a web or EJB container!!!)// 測試使用 Session // 首先獲取 Session: Subject的getSession()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:// 測試當前的用戶是否已經被認證. 即是否已經登錄. // 調用 Subject 的 isAuthenticated() ,若未認證,執行if里面的代碼if (!currentUser.isAuthenticated()) {// 把用戶名和密碼封裝為 UsernamePasswordToken 對象UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");// remembermetoken.setRememberMe(true);try {// 執行登錄. currentUser.login(token);} // 若沒有指定的賬戶, 則 shiro 將會拋出 UnknownAccountException 異常. catch (UnknownAccountException uae) {log.info("----> There is no user with username of " + token.getPrincipal());return; } // 若賬戶存在, 但密碼不匹配, 則 shiro 會拋出 IncorrectCredentialsException 異常。 catch (IncorrectCredentialsException ice) {log.info("----> Password for account " + token.getPrincipal() + " was incorrect!");return; } // 用戶被鎖定的異常 LockedAccountExceptioncatch (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) {//unexpected condition? error?}}// 成功登錄log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully.");//test a role:// 測試是否有某一個角色. 調用 Subject 的 hasRole 方法. if (currentUser.hasRole("schwartz")) {log.info("----> May the Schwartz be with you!");} else {log.info("----> Hello, mere mortal.");return; }//test a typed permission (not instance-level)// 測試用戶是否具備某一個行為. 調用 Subject 的 isPermitted() 方法。 if (currentUser.isPermitted("lightsaber:weild")) {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:// 測試用戶是否具備某一個行為. User:delete:zhangsan: 表示允許刪除User類型的zhangsanif (currentUser.isPermitted("User:delete:zhangsan")) {log.info("----> You are permitted to 'delete'(action) the User(type)with license plate (id) 'zhangsan'. " +"Here are the keys - have fun!");} else {log.info("Sorry, you aren't allowed to 'delete' the 'zhangsan' User!");}//all done - log out!// 執行登出. 調用 Subject 的 Logout() 方法. System.out.println("---->" + currentUser.isAuthenticated()); // truecurrentUser.logout();System.out.println("---->" + currentUser.isAuthenticated()); // falseSystem.exit(0);} }

五、Shiro實戰-整合進Spring Boot

這里先簡單介紹一下我們這次實戰所要完成的功能,其實主要就是對用戶登錄進行認證以及授予某些權限,讓不同用戶登錄的時候看到不同資源。下面代入實際的應用場景給大家講解一下:
首先我們有三個用戶admin、manager、worker,它們分別具有role(角色):admin、manager、worker;admin角色擁有訪問mobile、salary資源的權限,manager角色擁有訪問salary資源的權限;woker角色不具備任何權限;當我們用admin用戶登錄的時候就可以查看mobile和salary,manager只能查看salary,worker則什么都查看不了

1.創建父工程shiroDemo

以下為父工程的pom.xml文件內容,需要引入以下依賴

<properties><java.version>1.8</java.version> </properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.3.3.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.6.0</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies> </dependencyManagement>

2.創建shiroAuth子模塊(Maven)

以下為子工程的pom.xml文件內容,需要引入以下依賴,具體版本由父工程管理,這里只需添加我們需要用到的依賴即可

<dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId></dependency> </dependencies>

3.代碼結構講解

下圖是我們本次實戰項目的代碼結構,其中藍色劃掉的部分先暫時不用管它,后續我會講解的
下面帶大家先簡單的了解下這些類所要實現的功能,后面再進行更詳細的講解

類名功能
User實體類,主要就是定義一些屬性:用戶名、密碼、角色和權限等
MyRealm一個自定義的Realm,主要負責獲取認證和授權所需要的信息
ShiroConfigshiro的配置類,緩存、過濾器等功能在這里進行配置,在編譯的時候便會先執行該配置類,后續登錄認證或者授權的時候就會進到MyRealm中獲取數據
LoginController負責登錄業務流程的控制
MobileController負責獲取mobile
SalaryController負責獲取salary
UserService負責獲取TestData中數據,返回給LoginController
MyPassWordEncoder這是我們封裝的對密碼加鹽加密的工具類
TestData一些測試數據,主要是為了模擬UserService獲取數據庫中的數據,我們這次實戰并沒有連接數據庫,為了測試使用才有了這個工具類,實際開發中這些數據需要存放在數據庫中

4.各個類詳細講解

User

import java.util.List; public class User {private String userName;private String userPass;private List<String> userRoles; //用戶的角色private List<String> userPerms; //用戶所具有的權限// 省略構造方法和get/set方法... }

MyRealm

import com.m33.shiroAuth.bean.User; import com.m33.shiroAuth.service.UserService; import com.m33.shiroAuth.utils.MyPassWordEncoder; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; 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.subject.Subject; import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;@Configuration(value = "myRealm") public class MyRealm extends AuthorizingRealm {private final Logger logger = LoggerFactory.getLogger(MyRealm.class);@Resourceprivate UserService userService;//授權@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {logger.info("---------->entered method doGetAuthorizationInfo;");//拿到當前用戶Subject subject = SecurityUtils.getSubject();User currentUser = (User)subject.getPrincipal();//寫入當前用戶的角色和權限信息SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRoles(currentUser.getUserRoles());info.addStringPermissions(currentUser.getUserPerms());return info;}// 認證@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {logger.info("--------------->entered method doGetAuthenticationInfo;");//獲得當前用戶的tokenUsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//從token中獲得當前用戶的用戶名String username = userToken.getUsername();//模擬通過用戶名獲取數據庫中用戶密碼User user = userService.getUserByUserName(username);if(null == user){//后續會拋出UnknownAccountExceptionreturn null;}else{//MyPassWordEncoder.getEncodedPassword()是通過我們的工具類對用戶密碼進行加鹽加密//正常是不需要這一步的,注冊的時候就需要對密碼進行加鹽加密。因為我們沒有模擬注冊流程,// 所以現在密碼是明文,我們才需要先把明文密碼加鹽加密,后續才能與輸入的密碼進行比對user.setUserPass(MyPassWordEncoder.getEncodedPassword(user.getUserPass()));//各參數含義分別為當前用戶、用戶密碼(加密后的)、鹽值(里面的“salt”可隨意設置)、realm名SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getUserPass(),ByteSource.Util.bytes("salt"), "myRealm");//密碼驗證邏輯由shiro完成,我們只需返回SimpleAuthenticationInfo對象即可。密碼不匹配會拋出密碼不匹配異常。//密碼正確,會把user對象傳入SecurityUtils.getSubject()。這些都是shiro幫我們做的。return authenticationInfo;}} }

ShiroConfig

import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.MemoryConstrainedCacheManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map;@Configuration public class ShiroConfig {// 1、注冊Realm對象,在MyRealm中有@Configuration(value = "myRealm"),已經注冊好Realm對象了//所以以下代碼就不需要了,但是我們需要知道配置shiro一般都需要以下三個步驟,我們可以當成模板來記憶,根據實際需求進行修改即可//@Bean//public Realm myRealm(){// return new MyRealm();// }// 2、DefaultWebSecurityManager @Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") AuthorizingRealm myRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 通過HashedCredentialsMatcher 指定算法和加密次數HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();matcher.setHashAlgorithmName("MD5");matcher.setHashIterations(2);myRealm.setCredentialsMatcher(matcher);//設置緩存,使用緩存可以避免需要授權信息時頻繁的調用數據庫查詢的問題。//原理很簡單,只要在SecurityManager里注入CacheManager即可。//我們也可以自定義CacheManager的實現,可以是ehcache、redis等等。//實際開發中自定義RedisCache是很常見的,這里由于本人水平有限,就不演示如何用redis實現緩存了securityManager.setCacheManager(new MemoryConstrainedCacheManager());//最后要把我們的myRealm交給securityManager進行管理securityManager.setRealm(myRealm);return securityManager;}//3、ShiroFilterFactoryBean@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();bean.setSecurityManager(securityManager);/*配合Shiro的內置過濾器,參見DefaultFilteranon : 無需認證就可以訪問authc : 必須認證才可以訪問 對應@RequiresAuthentication注解user : 用戶登錄且記住我 才可以訪問 對應@RequiresUser注解perms : 擁有某個資源才可以訪問 對應@RequiresPermissions注解roles : 擁有某個角色才可以訪問 對應@RequiresRoles注解*/Map<String, String> filterChainDefinition = new HashMap<>();//配置antMatcher,跟SpringSecurity一樣,可以配**,*,?// **:匹配路徑中的零個或多個路徑,如/admin/**將匹配/admin/a或/admin/a/b// *:匹配零個或多個字符串,如/admin將匹配/admin、/admin123,但不匹配/admin/1// ?:匹配一個字符,如/admin?將匹配/admin1,但不匹配/admin或/admin/filterChainDefinition.put("/mobile/**","perms[mobile]");filterChainDefinition.put("/salary/**","perms[salary]");filterChainDefinition.put("/main.html","authc");//配置登出過濾器filterChainDefinition.put("/logout","logout");//將配好的內容添加到攔截器中bean.setFilterChainDefinitionMap(filterChainDefinition);//設置登錄頁bean.setLoginUrl("/index.html");//設置成功后跳轉的頁面bean.setSuccessUrl("/main.html");//設置沒有資源權限時跳轉到的頁面bean.setUnauthorizedUrl("/common/noauth");return bean;} }

補充:Shiro中默認的過濾器

配置縮寫對應的過濾器功能例子
anonAnonymousFilter指定url可以匿名訪問/admins/**=anon
authcFormAuthenticationFilter指定url需要form表單登錄,默認會從請求中獲取username、password,rememberMe等參數并嘗試登錄,如果登錄不了就會跳轉到loginUrl配置的路徑。我們也可以用這個過濾器做默認的登錄邏輯,但是一般都是我們自己在控制器寫登錄邏輯的,自己寫的話出錯返回的信息都可以定制嘛。/user/**=authc
authcBasicBasicHttpAuthenticationFilter指定url需要basic登錄/user/**=authcBasic
logoutLogoutFilter登出過濾器,配置指定url就可以實現退出功能,非常方便filterChainDefinition.put(“/logout”,“logout”);
noSessionCreationNoSessionCreationFilter禁止創建會話
permsPermissionsAuthorizationFilter需要指定權限才能訪問/admin/**=perms[user:add:*],多個perms需要在[]中加“”,中間用,隔開
portPortFilter需要指定端口才能訪問/admins/**=port[8080]
restHttpMethodPermissionFilter將http請求方法轉化成相應的動詞來構造一個權限字符串,這個感覺意義不大,有興趣自己看源碼的注釋/admin/user/**=perms[user.method],其中method為post,get,delete等
rolesRolesAuthorizationFilter需要指定角色才能訪問/admins/**=roles[“admin,user”]
sslSslFilter需要https請求才能訪問
userUserFilter需要已登錄或“記住我”的用戶才能訪問

LoginController、MobileController、SalaryController

import com.m33.shiroAuth.service.UserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map;@RequestMapping("/common") @RestController public class LoginController {private final Logger logger = LoggerFactory.getLogger(LoginController.class);@PostMapping("/login")public Object login(String username,String password){Map<String,String> errorCode = new HashMap<>();UsernamePasswordToken token = new UsernamePasswordToken(username,password);Subject user = SecurityUtils.getSubject();//如果用戶已認證,直接打印already loginif(user.isAuthenticated()){return "already login";}else{try{//用戶未認證,下面進行認證user.login(token);user.getSession().setAttribute("currentUser",user.getPrincipal());return "login succeed";} catch (UnknownAccountException uae) {//捕獲認證過程中出現的各種異常logger.info("There is no user with username of " + token.getPrincipal());errorCode.put("errorMsg","不存在的用戶名");} catch (IncorrectCredentialsException ice) {logger.info("Password for account " + token.getPrincipal() + " was incorrect!");errorCode.put("errorMsg","密碼不正確");} catch (LockedAccountException lae) {logger.info("The account for username " + token.getPrincipal() + " is locked. " +"Please contact your administrator to unlock it.");errorCode.put("errorMsg","賬號被鎖定");} catch(AuthenticationException authe){logger.info("Authentication error ",authe);errorCode.put("errorMsg",authe.getMessage());}return errorCode;}}//獲取當前用戶,用于前臺頁面展示用@RequestMapping("/getCurrentUser")public Object getCurrentUser(){Subject subject = SecurityUtils.getSubject();return subject.getSession().getAttribute("currentUser");}//未授權時執行的方法@RequestMapping("/noauth")public String noAuth(){return "未經授權,無法訪問。";} } -------------------------------------------------------------- import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/mobile") public class MobileController {/*** 代表一個查手機號的后臺接口。* @return*/@GetMapping("/query")public String query(){return "mobile";} } ------------------------------------------------------------- import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/salary") public class SalaryController {/*** 代表一個查薪水的后臺接口。* @return*/@RequiresPermissions("salary")@GetMapping("/query")public String query(){return "salary";} }

UserService

import com.m33.shiroAuth.bean.User; import com.m33.shiroAuth.utils.TestData; import org.apache.commons.beanutils.BeanUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; import java.util.stream.Collectors;@Service public class UserService {@Resourceprivate TestData testData;//這里的內容看看就好,主要是為了模擬獲取數據庫中的用戶數據,實際開發中肯定是直接從數據庫中獲得public User getUserByUserName(String username){List<User> queryUsers = testData.getAllUser().stream().filter(user -> username.equals(user.getUserName())).collect(Collectors.toList());if(null != queryUsers && queryUsers.size()>0){try {return (User)BeanUtils.cloneBean(queryUsers.get(0));} catch (Exception e) {e.printStackTrace();return null;}}else{return null;}} }

TestData

import com.m33.shiroAuth.bean.User; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Arrays; import java.util.List;@Component public class TestData {private List<User> allUser;/*** 模擬數據庫獲取到的數據。* admin用戶 擁有admin角色,擁有mobile和salary兩個資源。* mobile用戶,擁有mobile角色,擁有mobile資源。* worker用戶,擁有worker角色,沒有資源。* @return*/public List<User> getAllUser(){if(null == allUser){allUser = new ArrayList<User>();allUser.add(new User("admin","admin",Arrays.asList("admin"),Arrays.asList("mobile","salary")));allUser.add(new User("manager","manager",Arrays.asList("manager"),Arrays.asList("salary")));allUser.add(new User("worker","worker",Arrays.asList("worker"),Arrays.asList("worker")));}return allUser;} }

MyPassWordEncoder

import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.util.ByteSource; /*** 負責密碼加鹽加密*/ public class MyPassWordEncoder {public static String getEncodedPassword(String password){//各參數含義分別為加密方式、密碼、鹽值(可隨意設置)、迭代次數(指反復加鹽加密的次數)SimpleHash simpleHash = new SimpleHash("MD5",password, ByteSource.Util.bytes("salt"),2);return simpleHash.toString();} }

以上便是全部類的代碼講解,前端代碼這里就不去研究了,下面補充一下認證流程和授權流程

認證流程如下:

  • 1、首先調用Subject.login(token)進行登錄,其會自動委托給SecurityManager
  • 2、SecurityManager負責真正的身份驗證邏輯;它會委托給Authenticator進行身份驗證;
  • 3、Authenticator才是真正的身份驗證者,ShiroAPI中核心的身份認證入口點,此處可以自定義插入自己的實現;
  • 4、Authenticator可能會委托給相應的AuthenticationStrategy進行多Realm身份驗證,默認ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm身份驗證;
  • 5、Authenticator會把相應的token傳入Realm,從Realm獲取身份驗證信息,如果沒有返回/拋出異常表示身份驗證失敗了。此處可以配置多個Realm,將按照相應的順序及策略進行訪問。

授權流程如下:

  • 1、首先調用Subject.isPermitted*/hasRole*接口,其會委托給SecurityManager,而SecurityManager接著會委托給Authorizer;
  • 2、Authorizer是真正的授權者,如果調用如isPermitted(“user:view”),其首先會通過PermissionResolver把字符串轉換成相應的Permission實例;
  • 3、在進行授權之前,其會調用相應的Realm獲取Subject相應的角色/權限用于匹配傳入的角色/權限;
  • 4、Authorizer會判斷Realm的角色/權限是否和傳入的匹配,如果有多個Realm,會委托給ModularRealmAuthorizer進行循環判斷,如果匹配如isPermitted*/hasRole*會返回true,否則返回false表示授權失敗。

5.補充多Realm認證

當我們需要通過Shiro實現“用戶名/手機號/郵箱”任意一個都可以登錄的時候,我們就需要用到多Realm認證;我們要實現的效果就是通過用戶名+密碼或者手機號碼+密碼都可以實現登錄

修改User類,加一個mobile參數,為了可以通過手機號登錄

import java.util.List;public class User {private String userName;private String userPass;private String mobile;//補充mobile屬性private List<String> userRoles;private List<String> userPerms;...... }

修改TestData,添加手機號

public List<User> getAllUser(){if(null == allUser){allUser = new ArrayList<User>();allUser.add(new User("admin","admin","15777777777",Arrays.asList("admin"),Arrays.asList("mobile","salary")));allUser.add(new User("manager","manager","15888888888",Arrays.asList("manager"),Arrays.asList("salary")));allUser.add(new User("worker","worker","15999999999",Arrays.asList("worker"),Arrays.asList("worker")));}return allUser;}

修改UserService類,添加getUserByMobile方法

public User getUserByMobile(String mobile){List<User> queryUsers = testData.getAllUser().stream().filter(user -> mobile.equals(user.getMobile())).collect(Collectors.toList());if(null != queryUsers && queryUsers.size()>0){return queryUsers.get(0);}else{return null;}}

在config下添加MobileRealm,這就是上面那個代碼結構圖劃掉的藍色部分

import com.m33.shiroAuth.bean.User; import com.m33.shiroAuth.service.UserService; import com.m33.shiroAuth.utils.MyPassWordEncoder; import org.apache.shiro.authc.*; import org.apache.shiro.realm.AuthenticatingRealm; import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; //與MyRealm差不多,但是只需要認證流程即可,因為我們只是為了用手機號登錄認證 @Configuration(value = "mobileRealm") public class MobileRealm extends AuthenticatingRealm {private final Logger logger = LoggerFactory.getLogger(MobileRealm.class);@Resourceprivate UserService userService;//認證@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {logger.info("---->entered method doGetAuthenticationInfo;");UsernamePasswordToken usertoken = (UsernamePasswordToken)authenticationToken;String username = usertoken.getUsername();//只需要管用戶名查詢邏輯。模擬獲取數據庫中用戶手機號User user = userService.getUserByMobile(username);if(null == user){return null; //后續會拋出UnknownAccountException}else{user.setUserPass(MyPassWordEncoder.getEncodedPassword(user.getUserPass()));SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getUserPass(),ByteSource.Util.bytes("salt"), "mobileRealm");return authenticationInfo;}} }

最后,修改ShiroConfig的getDefaultWebSecurityManager方法

@Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") AuthorizingRealm myRealm, @Qualifier("mobileRealm") Realm mobileRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();matcher.setHashAlgorithmName("MD5");matcher.setHashIterations(2);myRealm.setCredentialsMatcher(matcher);securityManager.setCacheManager(new MemoryConstrainedCacheManager());//創建多Realm認證器,設置認證策略,下面有對認證策略進行一個簡單的介紹ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());securityManager.setAuthenticator(authenticator);//securityManager.setRealm(myRealm);securityManager.setRealms(Arrays.asList(myRealm,mobileRealm));return securityManager;}

補充:認證策略說明,Shiro提供了3個具體的 AuthenticationStrategy 實現
[1] AtLeastOneSuccessfulStrategy(默認實現):如果一個(或更多)驗證成功,則整體的嘗試被認為是成功的。如果沒有一個驗證成功,則整體失敗。說白了就是,至少有一個Realm的驗證是成功的算才認證通過,否則認證失敗。
[2] FirstSuccessfulStrategy:第一個Realm成功驗證返回的信息將被使用,其他的Realm將被忽略。如果沒有一個Realm驗證成功,則整體失敗。
[3] AllSuccessfulStrategy:所有配置的Realm都必須驗證成功才算認證通過,否則認證失敗。
補充:Realm的繼承關系

一般繼承AuthorizingRealm(授權)即可;從圖中我們可以看到它繼承了AuthenticatingRealm(即身份驗證),而且也間接繼承了CachingRealm(帶有緩存實現)。

總結

以上是生活随笔為你收集整理的Shiro从入门到实战(整合进SpringBoot)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。