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