Springboot+shiro基於url身份認證和授權認證
- 2019 年 10 月 6 日
- 筆記
你還不會shiro嗎?
- 前奏
- shiro核心配置文件(rolesFilter可選)。
- 身份認證
- 多表登錄源如何操作?
- 授權管理
- 如何解決介面多角色/資源問題
- 訪問效果
許可權管理在日常開發中很重要,所以硬著頭皮也要啃下來。

實現功能:
- 身份認證
- 對不同頁面進行url授權
- 多表登錄解決
- 同一個頁面多role訪問
項目完整github地址 歡迎star
springboot一些學習整合完整地址
shiro的四大組件:
- 身份認證(Authentication)-證明用戶身份,通常叫做登陸(login)。
- 授權(Authorization)-訪問控制
- 加密(Cryptography)-保護或隱藏數據
- 會話管理(session management)每個用戶時間敏感狀態
三個核心組件:Subject, SecurityManager 和 Realms.
- Subject:即「當前操作用戶」。但是,在Shiro中,Subject這一概念並不僅僅指人,也可以是第三方進程、後台帳戶(Daemon Account)或其他類似事物。它僅僅意味著「當前跟軟體交互的東西」。但考慮到大多數目的和用途,你可以把它認為是Shiro的「用戶」概念。 Subject代表了當前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。
- SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通過SecurityManager來管理內部組件實例,並通過它來提供安全管理的各種服務。
- Realm: Realm充當了Shiro與應用安全數據間的「橋樑」或者「連接器」。也就是說,當對用戶執行認證(登錄)和授權(訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶及其許可權資訊。 從這個意義上講,Realm實質上是一個安全相關的DAO:它封裝了數據源的連接細節,並在需要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(或)授權。配置多個Realm是可以的,但是至少需要一個。
- Shiro內置了可以連接大量安全數據源(又名目錄)的Realm,如LDAP、關係資料庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果預設的Realm不能滿足需求,你還可以插入代表自定義數據源的自己的Realm實現。
Shiro內置過濾器,可以實現許可權相關的攔截器
- 常用的過濾器:
anon: 無需認證(登錄)可以訪問
authc: 必須認證才可以訪問
user: 如果使用rememberMe的功能可以直接訪問
perm: 該資源必須得到資源許可權才可以訪問
role: 該資源必須得到角色許可權才可以訪問
這裡面只用到了身份認證和授權,許可權認證只用到了一點點,shiro的原理是封裝的過濾器,他能夠在訪問瀏覽器前能過自動完成一些內容。
shiro配置主要兩部分——shiroconfig和自定義的Realm(繼承AuthorizingRealm)
。其中,shiroconfig是shiro的主要配置文件,而自定義的Realm主要是重寫AuthorizingRealm
的兩個方法,分別是身份認證和授權認證調用資料庫查詢比對。而如果需要role訪問則需要重寫一個filter。
前奏
項目結構:

環境:
- Springboot2
- mybatis
- shiro
新建表:

對應的bean:
package com.shiro.bean; public class student { private String username; private String password; private String role; private String perm; //省略get set
mybatis簡單查詢:
package com.shiro.mapper; import com.shiro.bean.student; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; @Mapper public interface studentMapper { @Select("select * from student where username=#{name}") student findByName(String name); }
省略html和sql,詳細可以到GitHub下載
頁面目錄,:
shiro核心配置文件(rolesFilter可選)。
UserRealm.java
package com.shiro.config; import com.shiro.bean.student; import com.shiro.mapper.studentMapper; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; /** * 自定義Realm * @author bigsai * */ public class UserRealm extends AuthorizingRealm{ @Autowired(required = false) private studentMapper studentMapper; private final Logger logger= LoggerFactory.getLogger(UserRealm.class); /** * 執行授權邏輯 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { logger.info("執行邏輯授權"); //給資源進行授權 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加資源的授權字元串 //到資料庫查詢當前登錄用戶的授權字元串 //獲取當前登錄用戶 Subject subject = SecurityUtils.getSubject(); student user = (student) subject.getPrincipal(); student dbUser = studentMapper.findByName(user.getUsername()); info.addRole(user.getRole());//添加role 和perms role代表角色 perms代表操作,或者動作等。用於顆粒化許可權管理 info.addStringPermission(dbUser.getPerm()); System.out.println("user:"+dbUser.getPerm()); return info; } /** * 執行認證邏輯 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { System.out.println("執行認證邏輯"); //編寫shiro判斷邏輯,判斷用戶名和密碼 //1.判斷用戶名 UsernamePasswordToken token = (UsernamePasswordToken)arg0; student user = studentMapper.findByName(token.getUsername()); if(user==null){ //用戶名不存在 return null;//shiro底層會拋出UnKnowAccountException } //2.判斷密碼 return new SimpleAuthenticationInfo(user,user.getPassword(),""); } }
rolesFilter
package com.shiro.config; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; // AuthorizationFilter抽象類事項了javax.servlet.Filter介面,它是個過濾器。 public class rolesFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception { Subject subject = getSubject(req, resp); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //沒有角色限制,有許可權訪問 return true; } for (int i = 0; i < rolesArray.length; i++) { if (subject.hasRole(rolesArray[i])) { //若當前用戶是rolesArray中的任何一個,則有許可權訪問 return true; } } return false; } }
shiroConfig:shiro的主要配置
package com.shiro.config; 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 javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; /** * Shiro的配置類 * * @author bigsai */ @Configuration public class ShiroConfig { /** * 創建ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filtersMap = new LinkedHashMap<>(); filtersMap.put("rolesFilter",new rolesFilter()); shiroFilterFactoryBean.setFilters(filtersMap);//使用自定義fitter //添加Shiro內置過濾器 /** * Shiro內置過濾器,可以實現許可權相關的攔截器 * 常用的過濾器: * anon: 無需認證(登錄)可以訪問 * authc: 必須認證才可以訪問 * user: 如果使用rememberMe的功能可以直接訪問 * perm: 該資源必須得到資源許可權才可以訪問 * role: 該資源必須得到角色許可權才可以訪問 */ Map<String, String> filterMap = new LinkedHashMap<String, String>(); filterMap.put("/login", "anon");//要將登陸的介面放出來,不然沒許可權訪問登陸的介面 filterMap.put("/getcontroller", "anon"); // //授權過濾器 //注意:當前授權攔截後,shiro會自動跳轉到未授權頁面 filterMap.put("/add", "perms[add]"); filterMap.put("/update", "perms[update]"); // filterMap.put("/test1.html","rolesFilter[admin,user]"); filterMap.put("/*", "authc");//authc即為認證登陸後即可訪問 //修改調整的登錄頁面 shiroFilterFactoryBean.setLoginUrl("/index"); //設置未授權提示頁面 shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap); return shiroFilterFactoryBean; } /** * 創建DefaultWebSecurityManager */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //關聯realm securityManager.setRealm(userRealm); return securityManager; } /** * 創建Realm */ @Bean(name = "userRealm") public UserRealm getRealm() { return new UserRealm(); } }
身份認證
身份認證,就是登錄校檢。這是第一層過濾,並且當用戶沒有登錄的時候,回退到沒登陸的介面。在controller中,login的核心為:
@RequestMapping("/login") public String login(String name, String password, Model model, HttpServletRequest request) { model.addAttribute("nama", "給個star"); /** * 使用Shiro編寫認證操作 */ //1.獲取Subject Subject subject = SecurityUtils.getSubject(); //2.封裝用戶數據 UsernamePasswordToken token = new UsernamePasswordToken(name, password); //3.執行登錄方法 try { subject.login(token); //登錄成功 //跳轉 return "redirect:/index2"; } catch (UnknownAccountException e) { //e.printStackTrace(); //登錄失敗:用戶名不存在 model.addAttribute("msg", "用戶名不存在"); return "login"; } catch (IncorrectCredentialsException e) { //e.printStackTrace(); //登錄失敗:密碼錯誤 model.addAttribute("msg", "密碼錯誤"); return "login"; } }
releam中
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { System.out.println("執行認證邏輯"); //編寫shiro判斷邏輯,判斷用戶名和密碼 //1.判斷用戶名 UsernamePasswordToken token = (UsernamePasswordToken)arg0; student user = studentMapper.findByName(token.getUsername()); if(user==null){ //用戶名不存在 return null;//shiro底層會拋出UnKnowAccountException } //2.判斷密碼 return new SimpleAuthenticationInfo(user,user.getPassword(),""); }
而這只是表象的處理過程,而在releam(繼承AuthorizingRealm
)中需要充血doGetAuthenticationInfo()
方法.
大致流程為:登錄
——>拿帳號密碼檢驗
———>用著token的帳號通過你的sql查詢對象
——>比對數據是否一致
——>通過還是拋各種異常
而在shiroConfig中,基於url過濾時authc
即可訪問
多表登錄源如何操作?
可能會遇到如下情況:教師端,學生端來自兩張表,兩個登錄介面,我該如何使用shiro身份認證。對於這種問題,你可以配置多個releam,但是我覺得如果簡單你可以在不同的登錄介面下傳遞一個參數過來,這個參數就用session傳遞。因為,shiro的session和網頁httprequest獲得的session是同一個session
。
所以當你在login傳遞一個屬性到releam中,可用 if else判斷然後不同登錄介面執行不同的查詢方法即可。
授權管理
接上流程 是否登錄
——>是/否
——(是)—>查詢role/perm添加到subject
——>過濾器校驗該url需要許可權
——>可以訪問/許可權不足
shiro主要url可以根據角色(role)和資源(perm)的管理。對於role,可以是管理員,教師等,而perm,可能是一個動作,一個操作,等等。並且可能一個角色擁有多個role和perm。 同理,授權就是查詢資料庫的role或者perm欄位添加到角色中。當然具體api不做介紹。 主要方法為上述:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { logger.info("執行邏輯授權"); //給資源進行授權 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //添加資源的授權字元串 //到資料庫查詢當前登錄用戶的授權字元串 //獲取當前登錄用戶 Subject subject = SecurityUtils.getSubject(); student user = (student) subject.getPrincipal(); student dbUser = studentMapper.findByName(user.getUsername()); info.addRole(user.getRole());//添加role 和perms role代表角色 perms代表操作,或者動作等。用於顆粒化許可權管理 info.addStringPermission(dbUser.getPerm()); System.out.println("user:"+dbUser.getPerm()); return info; }
而url中也是
filterMap.put("/add", "perms[add]"); filterMap.put("/update", "roles[admin]");
如何解決介面多角色/資源問題
常常遇到這種情況:一個介面/頁面,有兩個或者以上角色可以訪問。然後再後台的過濾器配置總。shiro默認的配置是and而不是or。這就需要我們自己定義filter繼承AuthorizationFilter
從寫對應方法。
以多角色訪問為例子。從寫上述就是文件rolesFilter。在使用的時候也要首先聲明filter才能使用。

訪問效果
在頁面授權的
運行測試:訪問其他介面都被返回到這個介面


登陸成功後,介面可以訪問




有個小注意點:如果mybatis2.0版本回和spring-start-web有衝突。我用1.3.2版本沒問題。 參考:百度百科 項目github地址
springboot一些學習整合完整地址
https://github.com/javasmall/SpringbootDemo/tree/master/springboot_shiro