Spring boot 入門(四):集成 Shiro 實現登陸認證和許可權管理

来源:https://www.cnblogs.com/dz-boss/archive/2019/03/02/10459448.html
-Advertisement-
Play Games

本文是接著上篇博客寫的:Spring boot 入門(三):SpringBoot 集成結合 AdminLTE(Freemarker),利用 generate 自動生成代碼,利用 DataTable 和 PageHelper 進行分頁顯示。按照前面的博客,已經可以搭建一個簡單的 Spring Boot ...


本文是接著上篇博客寫的:Spring boot 入門(三):SpringBoot 集成結合 AdminLTE(Freemarker),利用 generate 自動生成代碼,利用 DataTable 和 PageHelper 進行分頁顯示。按照前面的博客,已經可以搭建一個簡單的 Spring Boot 系統,本篇博客繼續對此系統進行改造,主要集成了 Shiro 許可權認證框架,關於 Shiro 部分,在本人之前的博客(認證與Shiro安全框架)有介紹到,這裡就不做累贅的介紹。

此系列的博客為實踐部分,以代碼和搭建系統的過程為主,如遇到專業名詞,自行查找其含義。

1.Shiro 配置類

系統搭建到目前為止,主要用到了3個配置類,均與 Shiro 有關,後期隨著項目的擴大,配置文件也會隨之增多。

 

  • FreeMarkerConfig:主要針對 FreeMarker 頁面顯示的配置,關於 Shiro 部分,為 Shiro 標簽設置了共用變數,如果不設置此變數,FreeMarker 頁面將不能識別 Shiro 的標簽,其主要代碼如下:
1 configuration.setSharedVariable("shiro", new ShiroTags());
  • MShiroFilterFactoryBean:設置了過濾器,當然也可以在 Config 文件裡面配置過濾器,其缺點是:在每次請求裡面都做了 session 的讀取和更新訪問時間等操作,這樣在集群部署 session 共用的情況下,數量級的加大了處理量負載。本項目後期將用到分散式,因此這裡就直接將過濾器與 Config 配置文件分離,提高效率。
 1 private final class MSpringShiroFilter extends AbstractShiroFilter {
 2         protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
 3             super();
 4             if (webSecurityManager == null) {
 5                 throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
 6             }
 7             setSecurityManager(webSecurityManager);
 8             if (resolver != null) {
 9                 setFilterChainResolver(resolver);
10             }
11         }
12 
13         @Override
14         protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
15                                         FilterChain chain) throws ServletException, IOException {
16             HttpServletRequest request = (HttpServletRequest) servletRequest;
17             String str = request.getRequestURI().toLowerCase();
18             boolean flag = true;
19             int idx = 0;
20             if ((idx = str.indexOf(".")) > 0) {
21                 str = str.substring(idx);
22                 if (ignoreExt.contains(str.toLowerCase()))
23                     flag = false;
24             }
25             if (flag) {
26                 super.doFilterInternal(servletRequest, servletResponse, chain);
27             } else {
28                 chain.doFilter(servletRequest, servletResponse);
29             }
30         }
31 
32     }
  • ShiroConfiguration:通用配置文件,此配置文件為 Shiro 的基礎通用配置文件,只要是集成 Shiro,必有此文件,主要配置 Shiro 的登錄認證相關的信息,其代碼如下:
  1 /**
  2      * 設置shiro的緩存,緩存參數均配置在xml文件中
  3      * @return
  4      */
  5     @Bean
  6     public EhCacheManager getEhCacheManager() {
  7         EhCacheManager em = new EhCacheManager();
  8         em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");  
  9         return em;  
 10     }
 11     /**
 12      * 憑證匹配器
 13      * (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
 14      *  所以我們需要修改下doGetAuthenticationInfo中的代碼;
 15      * )
 16      * @return
 17      */
 18     @Bean
 19     public HashedCredentialsMatcher hashedCredentialsMatcher(){
 20        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
 21        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列演算法:這裡使用MD5演算法;
 22        hashedCredentialsMatcher.setHashIterations(1);//散列的次數,比如散列兩次,相當於 md5(md5(""));
 23        return hashedCredentialsMatcher;
 24     }
 25     /**
 26      * 
 27      * 主文件
 28      */
 29     @Bean(name = "myShiroRealm")
 30     public UserRealm myShiroRealm(EhCacheManager cacheManager) {
 31         UserRealm realm = new UserRealm(); 
 32         realm.setCacheManager(cacheManager);
 33         realm.setCredentialsMatcher(hashedCredentialsMatcher());
 34         return realm;
 35     }
 36    //會話ID生成器
 37     @Bean(name = "sessionIdGenerator")
 38     public JavaUuidSessionIdGenerator javaUuidSessionIdGenerator(){
 39         JavaUuidSessionIdGenerator javaUuidSessionIdGenerator = new JavaUuidSessionIdGenerator();
 40         return javaUuidSessionIdGenerator;
 41     }
 42     @Bean(name = "sessionIdCookie")
 43     public SimpleCookie getSessionIdCookie(){
 44         SimpleCookie sessionIdCookie = new SimpleCookie("sid");
 45         sessionIdCookie.setHttpOnly(true);
 46         sessionIdCookie.setMaxAge(-1);
 47         return sessionIdCookie;
 48         
 49     }
 50     /*<!-- 會話DAO -->*/
 51     @Bean(name = "sessionDAO")
 52     public EnterpriseCacheSessionDAO enterpriseCacheSessionDAO(){
 53         EnterpriseCacheSessionDAO sessionDao = new EnterpriseCacheSessionDAO();
 54         sessionDao.setSessionIdGenerator(javaUuidSessionIdGenerator());
 55         sessionDao.setActiveSessionsCacheName("shiro-activeSessionCache");
 56         return sessionDao;
 57     }
 58     @Bean(name = "sessionValidationScheduler")
 59     public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
 60         ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
 61         scheduler.setInterval(1800000);
 62         return scheduler;
 63     }
 64     @Bean(name = "sessionManager")
 65     public DefaultWebSessionManager sessionManager(EnterpriseCacheSessionDAO sessionDAO){
 66         DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
 67         sessionManager.setGlobalSessionTimeout(1800000);
 68         sessionManager.setDeleteInvalidSessions(true);
 69         sessionManager.setSessionValidationSchedulerEnabled(true);
 70         sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
 71         sessionManager.setSessionDAO(sessionDAO);
 72         sessionManager.setSessionIdCookieEnabled(true);
 73         sessionManager.setSessionIdCookie(getSessionIdCookie());
 74         return sessionManager;
 75     }
 76     @Bean(name = "lifecycleBeanPostProcessor")
 77     public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
 78         return new LifecycleBeanPostProcessor();
 79     }
 80     @Bean
 81     public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
 82         DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
 83         daap.setProxyTargetClass(true);
 84         return daap;
 85     }
 86     @Bean(name = "securityManager")
 87     public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm myShiroRealm, DefaultWebSessionManager sessionManager) {
 88         DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
 89         dwsm.setRealm(myShiroRealm);
 90 //      <!-- 用戶授權/認證信息Cache, 採用EhCache 緩存 --> 
 91         dwsm.setCacheManager(getEhCacheManager());
 92         dwsm.setSessionManager(sessionManager);
 93         return dwsm;
 94     }
 95     /**
 96      *  開啟shiro aop註解支持.
 97      *  使用代理方式;所以需要開啟代碼支持;
 98      * @param securityManager
 99      * @return
100      */
101     @Bean
102     public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
103         AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
104         aasa.setSecurityManager(securityManager);
105         return aasa;
106     }
107     /**
108      * ShiroFilter<br/>
109      * 註意這裡參數中的 StudentService 和 IScoreDao 只是一個例子,因為我們在這裡可以用這樣的方式獲取到相關訪問資料庫的對象,
110      * 然後讀取資料庫相關配置,配置到 shiroFilterFactoryBean 的訪問規則中。實際項目中,請使用自己的Service來處理業務邏輯。
111      *
112      */
113     @Bean(name = "shiroFilter")
114     public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
115         ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean();
116         // 必須設置 SecurityManager  
117         shiroFilterFactoryBean.setSecurityManager(securityManager);
118         // 如果不設置預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
119         shiroFilterFactoryBean.setLoginUrl("/login");
120         // 登錄成功後要跳轉的連接
121         shiroFilterFactoryBean.setSuccessUrl("/certification");
122         //shiroFilterFactoryBean.setSuccessUrl("/index");
123         shiroFilterFactoryBean.setUnauthorizedUrl("/403");
124         loadShiroFilterChain(shiroFilterFactoryBean);
125         return shiroFilterFactoryBean;
126     }
127     /**
128      * 載入shiroFilter許可權控制規則(從資料庫讀取然後配置)
129      *
130      */
131     private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
132         /////////////////////// 下麵這些規則配置最好配置到配置文件中 ///////////////////////
133         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
134         // authc:該過濾器下的頁面必須驗證後才能訪問,它是Shiro內置的一個攔截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
135         filterChainDefinitionMap.put("/login", "authc");
136         filterChainDefinitionMap.put("/logout", "logout");
137         // anon:它對應的過濾器裡面是空的,什麼都沒做
138         logger.info("##################從資料庫讀取許可權規則,載入到shiroFilter中##################");
139 //        filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 這裡為了測試,固定寫死的值,也可以從資料庫或其他配置中讀取
140         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
141     }

2.登錄認證與許可權管理

主要重寫了 Realm域,完成許可權認證和許可權管理:

 1     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
 2         //如果沒有做許可權驗證,此處只需要return null即可
 3         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
 4         String userName = (String) principals.getPrimaryPrincipal();
 5         Result<TUser> list = userService.getUserByUsername(userName);
 6         if(list.isStatus()) {
 7             //獲取該用戶所屬的角色
 8             Result<List<TRole>> resultRole = roleService.getRoleByUserId(list.getResultData().getUserId());
 9             if(resultRole.isStatus()) {
10                 HashSet<String> role = new HashSet<String>();
11                 for(TRole tRole : resultRole.getResultData()) {
12                     role.add(tRole.getRoleId()+"");
13                 }
14                 //獲取該角色擁有的許可權
15                 Result<List<TPermission>> resultPermission = permissionService.getPermissionsByRoleId(role);
16                 if(resultPermission.isStatus()) {
17                     HashSet<String> permissions = new HashSet<String>();
18                     for(TPermission tPermission : resultPermission.getResultData()) {
19                         permissions.add(tPermission.getPermissionsValue());
20                     }
21                     System.out.println("許可權:"+permissions);
22                     authorizationInfo.setStringPermissions(permissions);
23                 }
24             }
25         }
26         //return null;
27         return authorizationInfo;
28     }
29 
30     @Override
31     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
32         //認證登錄
33         String username = (String) authenticationToken.getPrincipal();
34         //String password = new String((char[]) authenticationToken.getCredentials());
35         Result<TUser> result = userService.getUserByUsername(username);
36         if (result.isStatus()) {
37             TUser user = result.getResultData();
38             return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
39         }
40         //return new SimpleAuthenticationInfo(user., "123456", getName());
41         return null;
42     }
43 }

2.1.登錄認證

首先創建一個前端登錄界面,做一個簡單的登錄 Form 表單

點擊登錄即想後臺發送一個請求,必須是Post請求,否則Shiro不能識別,認證部分主要在 Ream 中完成,新建一個類,繼承 AuthorizingRealm ,然後在重寫 doGetAuthenticationInfo 方法:

  只需要通過界面上的用戶名查找到資料庫存儲的相關信息即可,具體的認證是 Shiro 內部自己完成的,我們只需要傳入資料庫中存儲的用戶名和密碼個認證函數即可(new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName())),我們可以自己重新定義密碼比較器,密碼比較器的寫法較多,在認證與Shiro安全框架中,直接將密碼比較器寫入到Ream中,耦合度太高,本項目通過配置的方式重寫密碼比較器,具體代碼請參考參考ShiroConfiguration配置類:

 

在具體的 Login 方法中,寫入一些登錄失敗的異常即可,主要用戶將此失敗結果存入 Session,並顯示在頁面上:
 1     @RequestMapping(value = "/login", method = RequestMethod.POST)
 2     public String postLogin(RedirectAttributes redirectAttributes, HttpServletRequest request, HttpSession session) {
 3         // 登錄失敗從request中獲取shiro處理的異常信息。
 4         // shiroLoginFailure:就是shiro異常類的全類名.
 5         String exception = (String) request.getAttribute("shiroLoginFailure");
 6 
 7         System.out.println("exception=" + exception);
 8         String msg = "";
 9         if (exception != null) {
10             if (UnknownAccountException.class.getName().equals(exception)) {
11                 System.out.println("UnknownAccountException -- > 賬號不存在:");
12                 msg = "用戶不存在!";
13             } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
14                 System.out.println("IncorrectCredentialsException -- > 密碼不正確:");
15                 msg = "密碼不正確!";
16             } else if ("kaptchaValidateFailed".equals(exception)) {
17                 System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤");
18                 msg = "驗證碼錯誤!";
19             } else {
20                 //msg = "else >> "+exception;
21                 msg = "密碼不正確!";
22                 System.out.println("else -- >" + exception);
23             }
24         }
25         redirectAttributes.addFlashAttribute("msg", msg);
26         session.setAttribute("msg", msg);
27         //return redirect("/login");
28         return "redirect:login";
29         //return msg;
30     }

此時登錄認證部門已經完成:一個頁面+後臺2個函數(1個認證函數+1個Login函數)

2.2.許可權管理

總體來說,許可權管理只需要在界面增加 Shiro 的許可權標簽即可,可以使用角色的標簽,也可以使用許可權的標簽,一般情況下2種標簽配合使用,效果最好 <@shiro.hasPermission name="xtgl-yhgl:read"> <@shiro.hasRolen name="xtgl-yhgl:read">

 

此外,在 Realm 中,需要重寫許可權認證的業務邏輯,通常情況下通過用戶 ID 找到該用戶所屬的角色,然後通過角色 ID 找到該角色擁有的許可權,並將角色或者許可權寫入的 Shiro 中即可: authorizationInfo.setStringPermissions(permissions); authorizationInfo.setRoles(role);

本項目也是通過此邏輯完成許可權管理的

上面2張截圖表示的是一個函數。

到此,Spring Boot集成Shiro框架的許可權認證已經搭建完畢,可以實現簡單的許可權管理。

3.新增文件

較上一篇博客,Shiro 部分新增加的文件

 

 



 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 創建一個計算兩個參數之和的 function。如果只有一個參數,則返回一個 function,該 function 請求一個參數然後返回求和的結果。 例如,add(2, 3) 應該返回 5,而 add(2) 應該返回一個 function。 調用這個有一個參數的返回的 function,返回求和的結 ...
  • 我居然連一月一隨筆都沒有,啊啊啊,忙死個人 這個隨筆主要是記錄基於自己學習[美]James W.Cooper著的《C# Design Patterns : A Tutorial》一書中常用設計模式的整理,既是自己整理的, 便不會與書中所講完全相同,且避免不了存在不足的地方,歡迎分享寶貴見解。隨筆中會 ...
  • Redis簡介 Redis是一個基於C語言開發的開源(BSD許可),開源高性能的高級記憶體數據結構存儲,用作資料庫、緩存和消息代理。它支持數據結構,如 字元串、散列、列表、集合,帶有範圍查詢的排序集,點陣圖,超級日誌,具有半徑查詢和流的地理空間索引。Redis具有內置複製,Lua腳本,LRU驅逐,事務和 ...
  • 簡單粗暴學建造者模式,含Mybatis框架源碼中建造者模式運用分析。 ...
  • 題意 "題目鏈接" 可持久化01Trie板子題 對於兩個操作分別開就行了 cpp include using namespace std; const int MAXN = 4e5 + 10, SS = MAXN 42 + 10; const int B = 31; inline int read( ...
  • Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境,用來方便地搭建快速的易於擴展的網路應用。Node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效,非常適合運行在分散式設備的數據密集型的實時應用。在阿裡雲的Centos系統上,可以採用NVM安 ...
  • 早些年做CRM用到的一個金額轉換函數,今天從舊項目中拿出來記錄一下。金額轉換的函數方法有很多,都很不錯。不過這個是小崔剛工作的時候寫的一個轉換函數,多少還是有點紀念意義。如有問題請朋友們指出,小崔及時修正。謝謝啦! 廢話不多說直接上代碼: 以上是基礎轉換代碼,在這個基礎上進行二次改造: 運行結果: ...
  • 登錄驗證碼 Servlet / 從請求中獲取數據,獲取驗證碼的session的值轉為String類型, 銷毀,防止返回後驗證碼不刷新,重新驗證成功 判斷驗證碼是否相同(忽略大小寫) 相同:創建user對象調用service層的方法驗證返回結果是否為空 為空:創建session:儲存錯誤信息,轉發,登 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...