Shiro 認證授權詳解


1     權限管理

1.1用戶身份認證

1.1.1  概念

         身份認證,就是判斷一個用戶是否爲合法用戶的處理過程。最常用的簡單身份認證方式是系統通過覈對用戶輸入的用戶名和口令,看其是否與系統中存儲的該用戶的用戶名和口令一致,來判斷用戶身份是否正確。對於採用指紋等系統,則出示指紋;對於硬件Key等刷卡系統,則需要刷卡。

1.1.2  用戶名密碼身份認證流程


1.1.3  關鍵對象

         上邊的流程圖中需要理解以下關鍵對象:

 

  •   Subject:主體

         訪問系統的用戶,主體可以是用戶、程序等,進行認證的都稱爲主體;

 

         是主體(subject)進行身份認證的標識,標識必須具有唯一性,如用戶名、手機號、郵箱地址等,一個主體可以有多個身份,但是必須有一個主身份(Primary Principal)。

 

  • Credential:憑證信息

         是隻有主體自己知道的安全信息,如密碼、證書等。

 

1.2   授權

1.2.1  概念

         授權,即訪問控制,控制誰能訪問哪些資源。主體進行身份認證後需要分配權限方可訪問系統的資源,對於某些資源沒有權限是無法訪問的。

 

1.2.2 授權流程

 下圖中橙色爲授權流程。

 

1.2.3 關鍵對象

    授權可簡單理解爲who對what(which)進行How操作:

  • Who,即主體(Subject),主體需要訪問系統中的資源。
  • What,即資源(Resource),如系統菜單、頁面、按鈕、類方法、系統商品信息等。資源包括資源類型和資源實例,比如商品信息爲資源類型,類型爲t01的商品爲資源實例,編號爲001的商品信息也屬於資源實例。
  • How,權限/許可(Permission),規定了主體對資源的操作許可,權限離開資源沒有意義,如用戶查詢權限、用戶添加權限、某個類方法的調用權限、編號爲001用戶的修改權限等,通過權限可知主體對哪些資源都有哪些操作許可。

權限分爲粗顆粒和細顆粒,粗顆粒權限是指對資源類型的權限,細顆粒權限是對資源實例的權限。

 

主體、資源、權限關係如下圖:


1.2.4 權限模型

對上節中的主體、資源、權限通過數據模型表示。

 

主體(賬號、密碼)

資源(資源名稱、訪問地址)

權限(權限名稱、資源id)

角色(角色名稱)

角色和權限關係(角色id、權限id)

主體和角色關係(主體id、角色id)

 

如下圖:

 

1.2.5  權限分配

         對主體分配權限,主體只允許在權限範圍內對資源進行操作,比如:對u01用戶分配商品修改權限,u01用戶只能對商品進行修改。

         權限分配的數據通常需要持久化,根據上邊的數據模型創建表並將用戶的權限信息存儲在數據庫中。

 

1.2.6  權限控制

         用戶擁有了權限即可操作權限範圍內的資源,系統不知道主體是否具有訪問權限需要對用戶的訪問進行控制。

 

1.2.6.1 基於角色的訪問控制

         RBAC基於角色的訪問控制(Role-BasedAccess Control)是以角色爲中心進行訪問控制,比如:主體的角色爲總經理可以查詢企業運營報表,查詢員工工資信息等,訪問控制流程如下:

上圖中的判斷邏輯代碼可以理解爲:

if(主體.hasRole("總經理角色id")){

        查詢工資

}


缺點:以角色進行訪問控制粒度較粗,如果上圖中查詢工資所需要的角色變化爲總經理和部門經理,此時就需要修改判斷邏輯爲“判斷主體的角色是否是總經理或部門經理”,系統可擴展性差。

修改代碼如下:

if(主體.hasRole(總經理角色id") ||  主體.hasRole("部門經理角色id")){

         查詢工資

}

 

 

1.2.6.2 基於資源的訪問控制

         RBAC基於資源的訪問控制(Resource-BasedAccess Control)是以資源爲中心進行訪問控制,比如:主體必須具有查詢工資權限纔可以查詢員工工資信息等,訪問控制流程如下:

 

 

上圖中的判斷邏輯代碼可以理解爲:

if(主體.hasPermission("查詢工資權限標識")){

         查詢工資

}

 

優點:系統設計時定義好查詢工資的權限標識,即使查詢工資所需要的角色變化爲總經理和部門經理也只需要將“查詢工資信息權限”添加到“部門經理角色”的權限列表中,判斷邏輯不用修改,系統可擴展性強。

 

1.3   Shiro架構

 

 


1.3.1  Subject

         Subject即主體,外部應用與subject進行交互,subject記錄了當前操作用戶,將用戶的概念理解爲當前操作的主體,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序。         Subject在shiro中是一個接口,接口中定義了很多認證授相關的方法,外部程序通過subject進行認證授,而subject是通過SecurityManager安全管理器進行認證授權

 

1.3.2  SecurityManager

        SecurityManager即安全管理器,對全部的subject進行安全管理,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,實質上SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。

         SecurityManager是一個接口,繼承了Authenticator,Authorizer, SessionManager這三個接口。

 

1.3.3  Authenticator

        Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,通過ModularRealmAuthenticator基本上可以滿足大多數需求,也可以自定義認證器。

1.3.4  Authorizer

         Autorizer即授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。

 

1.3.5  Realm

         Realm即領域,相當於datasource數據源,securityManager進行安全認證需要通過Realm獲取用戶權限數據,比如:如果用戶身份數據在數據庫那麼realm就需要從數據庫獲取用戶身份信息。

         注意:不要把realm理解成只是從數據源取數據,在realm中還有認證授權校驗的相關的代碼。

 

1.3.6  sessionManager

        sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上,也可以將分佈式應用的會話集中在一點管理,此特性可使它實現單點登錄。

1.3.7  SessionDAO

        SessionDAO即會話dao,是對session會話操作的一套接口,比如要將session存儲到數據庫,可以通過jdbc將會話存儲到數據庫。

1.3.8  CacheManager

        CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣可以提高性能。

1.3.9  Cryptography

         Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能。

 

1.4   shiro的jar包

         與其它java開源框架類似,將shiro的jar包加入項目就可以使用shiro提供的功能了。shiro-core是核心包必須選用,還提供了與web整合的shiro-web、與spring整合的shiro-spring、與任務調度quartz整合的shiro-quartz等,下邊是shiro各jar包的maven座標。

     <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-core</artifactId>

          <version>1.2.3</version>

      </dependency>

      <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-web</artifactId>

          <version>1.2.3</version>

      </dependency>

      <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-spring</artifactId>

          <version>1.2.3</version>

      </dependency>

      <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-ehcache</artifactId>

          <version>1.2.3</version>

      </dependency>

    <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-quartz</artifactId>

          <version>1.2.3</version>

      </dependency>

也可以通過引入shiro-all包括shiro所有的包:

  <dependency>

          <groupId>org.apache.shiro</groupId>

          <artifactId>shiro-all</artifactId>

          <version>1.2.3</version>

   </dependency>

 

1.5   shiro認證流程


 

 

1.5.1  認證代碼

        // 用戶登陸、用戶退出
	@Test
	public void testLoginLogout() {

		// 構建SecurityManager工廠,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環境
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro.ini");

		// 通過工廠創建SecurityManager
		SecurityManager securityManager = factory.getInstance();
		
		// 將securityManager設置到運行環境中
		SecurityUtils.setSecurityManager(securityManager);

		// 創建一個Subject實例,該實例認證要使用上邊創建的securityManager進行
		Subject subject = SecurityUtils.getSubject();

		// 創建token令牌,記錄用戶認證的身份和憑證即賬號和密碼 
		UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

		try {
			// 用戶登陸
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// 用戶認證狀態

		Boolean isAuthenticated = subject.isAuthenticated();

		System.out.println("用戶認證狀態:" + isAuthenticated);

		// 用戶退出

		subject.logout();

		isAuthenticated = subject.isAuthenticated();

		System.out.println("用戶認證狀態:" + isAuthenticated);

	}

 

1.5.2  認證執行流程

 

1、 創建token令牌,token中有用戶提交的認證信息即賬號和密碼

2、 執行subject.login(token),最終由securityManager通過Authenticator進行認證

3、 Authenticator的實現 ModularRealmAuthenticator調用realm從ini配置文件取用戶真實的賬號和密碼,這裏使用的是IniRealm(shiro自帶)

4、 IniRealm先根據token中的賬號去ini中找該賬號,如果找不到則給ModularRealmAuthenticator返回null,如果找到則匹配密碼,匹配密碼成功則認證通過。

 

1.5.3  常見的異常

  • UnknownAccountException

    賬號不存在異常如下:org.apache.shiro.authc.UnknownAccountException: No account found for user。。。。

  • IncorrectCredentialsException

    當輸入密碼錯誤會拋此異常,如下:org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for    token[org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] didnot match the expected credentials.

 

 

更多如下:

DisabledAccountException(帳號被禁用)

LockedAccountException(帳號被鎖定)

ExcessiveAttemptsException(登錄失敗次數過多)

ExpiredCredentialsException(憑證過期)等

 

 

1.6 自定義Realm

1.6.1 Shiro提供的Realm

 

最基礎的是Realm接口,CachingRealm負責緩存處理,AuthenticationRealm負責認證,AuthorizingRealm負責授權,通常自定義的realm繼承AuthorizingRealm。

 

1.6.2  自定義Realm


public class CustomRealm1 extends AuthorizingRealm {

	@Override
	public String getName() {
		return "customRealm1";
	}

	//支持UsernamePasswordToken
	@Override
	public boolean supports(AuthenticationToken token) {
		return token instanceof UsernamePasswordToken;
	}

	//認證
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		
		//從token中 獲取用戶身份信息
		String username = (String) token.getPrincipal();
		//拿username從數據庫中查詢
		//....
		//如果查詢不到則返回null
		if(!username.equals("zhang")){//這裏模擬查詢不到
			return null;
		}
		
		//獲取從數據庫查詢出來的用戶密碼 
		String password = "123";//這裏使用靜態數據模擬。。
		
		//返回認證信息由父類AuthenticatingRealm進行認證
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				username, password, getName());

		return simpleAuthenticationInfo;
	}
	
//授權
@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// 獲取身份信息
		String username = (String) principals.getPrimaryPrincipal();
		// 根據身份信息從數據庫中查詢權限數據
		//....這裏使用靜態數據模擬
		List<String> permissions = new ArrayList<String>();
		permissions.add("user:create");
		permissions.add("user.delete");
		
		//將權限信息封閉爲AuthorizationInfo
		
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		for(String permission:permissions){
			simpleAuthorizationInfo.addStringPermission(permission);
		}
		
		return simpleAuthorizationInfo;

}

1.7   shiro授權流程

1.8   授權方式

Shiro 支持三種方式的授權:

  • 編程式:通過寫if/else授權代碼塊完成:

Subject subject =SecurityUtils.getSubject();

if(subject.hasRole(“admin”)) {

//有權限

} else {

//無權限

}

  • 註解式:通過在執行的Java方法上放置相應的註解完成:

@RequiresRoles("admin")

public void hello() {

//有權限

}

  • JSP/GSP 標籤:在JSP/GSP 頁面通過相應的標籤完成:

<shiro:hasRolename="admin">

<!— 有權限—>

</shiro:hasRole>

1.8.1  權限字符串規則

         權限字符串的規則是:“資源標識符:操作:資源實例標識符”,意思是對哪個資源的哪個實例具有什麼操作,“:”是資源/操作/實例的分割符,權限字符串也可以使用*通配符。

 

例子:

用戶創建權限:user:create,或user:create:*

用戶修改實例001的權限:user:update:001

用戶實例001的所有權限:user*001

 

 

1.8.2  基於角色的授權

// 用戶授權檢測 基於角色授權
// 是否有某一個角色
System.out.println("用戶是否擁有一個角色:" + subject.hasRole("role1"));
// 是否有多個角色
System.out.println("用戶是否擁有多個角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

對應的check方法:

subject.checkRole("role1");
subject.checkRoles(Arrays.asList("role1", "role2"));

上邊check方法如果授權失敗則拋出異常:

org.apache.shiro.authz.UnauthorizedException:Subject does not have role [.....]

        

 

 

1.8.3  基於資源授權

 

// 基於資源授權
System.out.println("是否擁有某一個權限:" + subject.isPermitted("user:delete"));
System.out.println("是否擁有多個權限:" + subject.isPermittedAll("user:create:1",	"user:delete"));

對應的check方法:

subject.checkPermission("sys:user:delete");
subject.checkPermissions("user:create:1","user:delete");

上邊check方法如果授權失敗則拋出異常:

org.apache.shiro.authz.UnauthorizedException:Subject does not have permission [....]

        

 

 

1.8.4  授權執行流程

 

1、 執行subject.isPermitted("user:create")

2、 securityManager通過ModularRealmAuthorizer進行授權

3、ModularRealmAuthorizer調用realm獲取權限信息

4、 ModularRealmAuthorizer再通過permissionResolver解析權限字符串,校驗是否匹配

 

1.9   shiro與spring web項目整合

1.9.1  web.xml添加shiro Filter

<!-- shiro過慮器,DelegatingFilterProx會從spring容器中找shiroFilter -->

    <filter>

    <filter-name>shiroFilter</filter-name>

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

       <init-param>

           <param-name>targetFilterLifecycle</param-name>

           <param-value>true</param-value>

       </init-param>

    </filter>

    <filter-mapping>

       <filter-name>shiroFilter</filter-name>

       <url-pattern>/*</url-pattern>

    </filter-mapping>

 

1.9.2  spring-shiro.xml

<!-- Shiro 的Web過濾器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- 如果沒有認證將要跳轉的登陸地址,http可訪問的url,如果不在表單認證過慮器FormAuthenticationFilter中指定此地址就爲身份認證地址 -->
		<property name="loginUrl" value="/login.action" />
<!-- 沒有權限跳轉的地址 -->
		<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- shiro攔截器配置 -->
		<property name="filters">
			<map>
				<entry key="authc" value-ref="formAuthenticationFilter" />
			</map>
		</property>
		<property name="filterChainDefinitions">
			<value>
				<!-- 必須通過身份認證方可訪問,身份認 證的url必須和過慮器中指定的loginUrl一致 -->
				/loginsubmit.action = authc
				<!-- 退出攔截,請求logout.action執行退出操作 -->
				/logout.action = logout
				<!-- 無權訪問頁面 -->
				/refuse.jsp = anon
				<!-- roles[XX]表示有XX角色纔可訪問 -->
				/item/list.action = roles[item],authc
				/js/** anon
				/images/** anon
				/styles/** anon
				<!-- user表示身份認證通過或通過記住我認證通過的可以訪問 -->
				/** = user
				<!-- /**放在最下邊,如果一個url有多個過慮器則多個過慮器中間用逗號分隔,如:/** = user,roles[admin] -->

			</value>
		</property>
	</bean>


	<!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="userRealm" />
		
	</bean>

	<!-- 自定義 realm -->
	<bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
	</bean>
	<!-- 基於Form表單的身份驗證過濾器,不配置將也會註冊此過慮器,表單中的用戶賬號、密碼及loginurl將採用默認值,建議配置 -->
	<bean id="formAuthenticationFilter"
		class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
		<!-- 表單中賬號的input名稱 -->
		<property name="usernameParam" value="usercode" />
		<!-- 表單中密碼的input名稱 -->
		<property name="passwordParam" value="password" />
		<!-- <property name="rememberMeParam" value="rememberMe"/> -->
		<!-- loginurl:用戶登陸地址,此地址是可以http訪問的url地址 -->
		<property name="loginUrl" value="/loginsubmit.action" />
	</bean>

securityManager:這個屬性是必須的。

loginUrl:沒有登錄認證的用戶請求將跳轉到此地址,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的”/login.jsp”頁面。

unauthorizedUrl:沒有權限默認跳轉的頁面。

 

 

1.9.3使用shiro註解授權

 

在springmvc.xml中配置shiro註解支持,可在controller方法中使用shiro註解配置權限:

        <!-- 開啓aop,對類代理 -->
	<aop:config proxy-target-class="true"></aop:config>
	<!-- 開啓shiro註解支持 -->
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
	    <property name="securityManager" ref="securityManager" />
        </bean> 

修改Controller代碼,在方法上添加授權註解,如下:

        // 查詢商品列表
	@RequestMapping("/queryItem")
	@RequiresPermissions("item:query")
	public ModelAndView queryItem() throws Exception {

上邊代碼@RequiresPermissions("item:query")表示必須擁有“item:query”權限方可執行。

 

1.10     session管理

在applicationContext-shiro.xml中配置sessionManager:

        <!-- 安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="userRealm" />
		<property name="sessionManager" ref="sessionManager" />
	</bean>
    <!-- 會話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- session的失效時長,單位毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 刪除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
    </bean>

2     附:

2.1   shiro過慮器

過濾器簡稱

對應的java

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

port

org.apache.shiro.web.filter.authz.PortFilter

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

ssl

org.apache.shiro.web.filter.authz.SslFilter

user

org.apache.shiro.web.filter.authc.UserFilter

logout

org.apache.shiro.web.filter.authc.LogoutFilter

 

anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。

authc:例如/admins/user/**=authc表示需要認證(登錄)才能使用,沒有參數

roles:例子/admins/user/**=roles[admin],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,當有多個參數時,例如admins/user/**=roles["admin,guest"],每個參數通過纔算通過,相當於hasAllRoles()方法。

perms:例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。

rest:例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method],其中method爲post,get,delete等。

port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString

是你訪問的url裏的?後面的參數。

authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證

 

ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https

user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操作時不做檢查

注:

anon,authcBasic,auchc,user是認證過濾器,

perms,roles,ssl,rest,port是授權過濾器

 

2.2   shiro的jsp標籤

 

Jsp頁面添加:<%@tagliburi="http://shiro.apache.org/tags" prefix="shiro"%>

 

標籤名稱

標籤條件(均是顯示標籤內容)

<shiro:authenticated>

登錄之後

<shiro:notAuthenticated>

不在登錄狀態時

<shiro:guest>

用戶在沒有RememberMe

<shiro:user>

用戶在RememberMe

<shiro:hasAnyRoles name="abc,123" >

在有abc或者123角色時

<shiro:hasRole name="abc">

擁有角色abc

<shiro:lacksRole name="abc">

沒有角色abc

<shiro:hasPermission name="abc">

擁有權限資源abc

<shiro:lacksPermission name="abc">

沒有abc權限資源

<shiro:principal>

顯示用戶身份名稱

 <shiro:principalproperty="username"/>顯示用戶身份中的屬性值

 版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/chen_changying/article/details/79714112

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章