天天看點

【Shiro學習筆記】一、Shiro具體使用詳解(基于springboot2.x,前後端分離)一、前言二、具體步驟

PS:歡迎轉載,但請注明出處,謝謝配合。

Shiro具體使用詳解

  • 一、前言
  • 二、具體步驟
    • 1、加入shiro依賴包
    • 2、自定義認證過濾器FormAuthenticationFilter
    • 3、自定義授權過濾器RolesAuthorizationFilter
    • 4、編寫shiro配置類
    • 5、編寫Realm類
    • 6、編寫登入Controller

一、前言

基于springboot2.x,前後端分離

二、具體步驟

1、加入shiro依賴包

修改pom.xml

<!-- 引入 shiro -->
<dependency>
    <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
           

2、自定義認證過濾器FormAuthenticationFilter

目的:将原來登入校驗不通過的重定向改為傳回Json資料

做法:繼承 authc 的預設認證過濾器(FormAuthenticationFilter)

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
           

1)重寫onAccessDenied 方法

針對shiro配置認證規則為“authc”的那些url,當校驗出使用者沒有登入的話,執行該方法

/**
 * 	當shiro校驗使用者未登入時,傳回JSON資料(代替原有的跳轉到登入界面)
 */
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
	this.log.error("使用者未登入,拒絕通路");
	response.setContentType("application/json") ;
	response.setCharacterEncoding("UTF-8") ;
	PrintWriter out = response.getWriter() ;
	String resultJson = JSON.toJSONString(ResultUtil.error("IMS0000","登入已失效,請重新登入!")) ;
	out.write(resultJson) ;
	out.flush() ;
	out.close() ;
	return false ;
}
           

3、自定義授權過濾器RolesAuthorizationFilter

目的:将原來權限校驗不通過的重定向改為傳回Json資料

做法:繼承 roles 的預設授權過濾器(RolesAuthorizationFilter)

public class MyRolesAuthorizationFilter extends RolesAuthorizationFilter{
           

1)重寫 onAccessDenied 方法

針對shiro配置授權規則為“roles”的那些url,當校驗出使用者沒有對應權限的話,執行該方法。

傳回json資料格式,由前端決定授權拒絕的後續邏輯。

寫法參考上面的自定義認證過濾器MyFormAuthenticationFilter。

2)重寫 isAccessAllowed 方法(可選)

若role配置多個,預設情況下,是必須多個role同時具備,才有通路權限。但一般實際情況,是隻要擁有配置中的某個權限,即可通路。是以可以重寫isAccessAllowed 方法,将父類中的“and”邏輯改為“or”邏輯。

// 原邏輯,多個role必須同時滿足,才有權限通路
// return subject.hasAllRoles(roles);   

// 修改後邏輯 -- begin (多個role之間是“或”關系,隻有一個滿足即可)
for(String role: roles) {
	if(subject.hasRole(role)) {
		return true ;
	}
}
return false ;
// 修改後邏輯 --end
           

4、編寫shiro配置類

1)編寫一個shiro的配置類

@Configuration
public class ShiroConfig {
           

2)配置類中,定義shiro的過濾器工廠bean

在該過濾器工廠中,可以 設定自定義過濾器、配置認證規則、配置授權規則

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
	// 建立 ShiroFilterFactoryBean
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    
    // 1、建立過濾器Map,用來裝自定義過濾器
    LinkedHashMap<String, Filter> map = new LinkedHashMap<>();
    // 2、将自定義過濾器放入map中,如果實作了自定義授權過濾器,那就必須在這裡注冊,否則Shiro不會使用自定義的授權過濾器
    map.put("authc", new MyFormAuthenticationFilter());	// 認證過濾器
    map.put("roles", new MyRolesAuthorizationFilter()); // 授權過濾器  
    // 3、将過濾器綁定到shiroFilterFactoryBean上
    shiroFilterFactoryBean.setFilters(map);
    // 4.配置認證規則 // <!-- authc:所有url都必須認證通過才可以通路;  anon:所有url都都可以匿名通路-->
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/login/**", "anon");	// 類似/login/xxx請求,可以直接通路
    filterChainDefinitionMap.put("/jnl/**", "anon");	// 類似/jnl/xxx請求,可以直接通路
    // 5.配置授權規則
    filterChainDefinitionMap.put("/admin/**", "roles[admin]");	// 類似/admin/**請求,需要admin角色才可通路(可以寫多個role,預設是多個role同時滿足,可以自定義改成或關系)
    //注意:下面這行代碼必須放在所有權限設定的最後,不然會導緻所有 url 都被攔截,都需要認證(因為filterChainDefinitions 配置過濾規則,是從上到下的順序比對)
    filterChainDefinitionMap.put("/**", "authc");		// 剩下的其他請求,需要登入後才可通路
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
    return shiroFilterFactoryBean;
}
           

3)配置類中,定義權限管理bean

主要包裝我們編寫的業務Realm,該bean會注給上面的過濾器工廠。shiro過濾器攔截的請求,會通過SecurityManager,走到業務Realm的相關方法。

@Bean
public SecurityManager securityManager(UserRealm myShiroRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(myShiroRealm);
    return securityManager;
}
           

4)配置類中,定義業務Realm bean

下面代碼中的兩個參數,是我項目中的資料庫操作Service,大家根據自己的情況實作即可。或者先不用資料庫操作,直接代碼中模拟資料庫的傳回值,先試試Shiro效果,則不用注入下面兩個參數。

@Bean  
public UserRealm myShiroRealm(ShiroUserService shiroUserServiceImpl, ShiroUserRoleService shiroUserRoleServiceImpl) {
    UserRealm userRealm = new UserRealm();
    userRealm.setShiroUserService(shiroUserServiceImpl) ;
    userRealm.setShiroUserRoleService(shiroUserRoleServiceImpl) ;
    return userRealm;
}
           

5、編寫Realm類

目的:操作資料層,實作使用者的認證和授權

做法:繼承Realm父類 AuthorizingRealm

public class UserRealm extends AuthorizingRealm {
           

1)實作 doGetAuthenticationInfo(認證方法)

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	UsernamePasswordToken upToken = (UsernamePasswordToken) token;
	String userName = upToken.getPrincipal().toString() ;
	// 1.根據使用者名,查詢使用者資訊
    Map userMap = this.shiroUserService.queryUser(userName) ;
    // 2.判斷使用者名是否存在
    if(null == userMap || userMap.isEmpty()) {
    	return null ;  // 這裡傳回null後,後面邏輯會報出對應異常(賬号不存在)
    }
    // 3.根據salt,将前端上送的密碼,加鹽加密後,重新放入原token,以便後續邏輯比對兩個加密後的密碼是否一緻【我項目中資料庫存儲的使用者密碼是MD5加鹽加密後的,明文存儲的則不需要這步】
    String salt = (String) userMap.get("salt") ; 			// 密碼加密用的鹽值
    char[] oldPassword = upToken.getPassword() ;
    upToken.setPassword(this.md5withSalt(String.valueOf(oldPassword), salt).toCharArray());
    String password = (String) userMap.get("password") ; 	//DB中的密碼(加密後的)
    // 4.根據查詢出的使用者名/密碼,建構SimpleAuthenticationInfo認證對象(第一個參數: 使用者名 ;第二個參數:DB中查到的密碼 ; 第三個參數:目前Realm名字)
    return new SimpleAuthenticationInfo(userName, password, this.getClass().getName()) ;  //shiro後續會根據該對象,與原上送參數token對象,比對兩者的密碼是否一緻,不一緻則抛出IncorrectCredentialsException異常
}
           

登入交易中,會調用Subject.login(token),最終會執行到Realm 的 doGetAuthenticationInfo 方法。

2)實作 doGetAuthorizationInfo(授權方法)

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	// 1. 擷取授權的使用者
	String userName = principals.getPrimaryPrincipal().toString() ;
	// 2. 根據使用者名,從DB中查詢擁有的角色(另:實際可進行緩存優化,不應每次都查庫)
	Set<String> roles = this.shiroUserRoleService.queryUserRoles(userName) ;
	// 3. 根據角色集合Set<String>,建構SimpleAuthorizationInfo授權對象
	return new SimpleAuthorizationInfo(roles);
}
           

shiro過濾器攔截請求後,若對應了role規則配置的路徑,則會判斷是否具有role角色權限,通過RolesAuthorizationFilter的 isAccessAllowed方法,最終會執行到Realm 的 doGetAuthorizationInfo 方法。

6、編寫登入Controller

登入交易中,調用shiro相關類進行認證,主要包括如下内容:

Subject subject = SecurityUtils.getSubject() ; // 擷取Subject-使用者主體(會把操作交給SecurityManager,最後到Realm)
AuthenticationToken token = new UsernamePasswordToken(userName, passWord) ; // 将從擷取的使用者名和密碼設定到一個token中

subject.login(token); // 通過捕獲該方法抛出的異常,傳回對應報錯資訊給前端(UnknownAccountException-使用者名錯誤,IncorrectCredentialsException-密碼錯誤)