Java框架(十八)之shiro安全(許可權)框架
一、簡介
1.概念
• Apache Shiro 是 Java 的一個安全(許可權)框架。 springsecurity • Shiro
可以非常容易的開發出足夠好的應用,其不僅可以用在 JavaSE 環境,也可以用在 JavaEE 環境。 • Shiro
可以完成:認證(登入判斷)、授權(把使用者的許可權授予使用者)、加密、會話管理、與Web 整合、快取 等。 •
下載:http://shiro.apache.org/
2.功能簡介
3.核心介紹
• Authentication
:身份認證/登入,驗證使用者是不是擁有相應的身份;
• Authorization
:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權;即判斷用
戶是否能進行什麼操作,如:驗證某個使用者是否擁有某個角色。或者細粒度的驗證某個使用者
對某個資源是否具有某個許可權;
• Session Manager
資訊都在會話中;會話可以是普通 JavaSE 環境,也可以是 Web 環境的;
•
Cryptography
:加密,保護資料的安全性,如密碼加密儲存到資料庫,而不是明文儲存;•
Web Support
:Web 支援,可以非常容易的整合到Web 環境;•
Caching
:快取,比如使用者登入後,其使用者資訊、擁有的角色/許可權不必每次去查,這樣可以提高效率;
•
Concurrency
:Shiro 支援多執行緒應用的併發驗證,即如在一個執行緒中開啟另一個執行緒,能• 把許可權自動傳播過去;
•
Testing
:提供測試支援;•
Run As
•
Remember Me
:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登錄了
二、Shiro架構
1.外部架構
2.架構圖介紹
• Subject
:應用程式碼直接互動的物件是 Subject,也就是說 Shiro 的對外
API 核心就是 Subject。Subject 代表了當前“使用者”,與 Subject 的所有互動都會委託給 SecurityManager;
• SecurityManager
:安全管理器;即所有與安全有關的操作都會與SecurityManager 互動;且其管理著所有 Subject;可以看出它是 Shiro的核心,
它負責與 Shiro 的其他元件進行互動,它相當於 SpringMVC 中DispatcherServlet 的角色
• Realm
:Shiro 從 Realm 獲取安全資料(如使用者、角色、許可權),就是說SecurityManager 要驗證使用者身份,那麼它需要從 Realm 獲取相應的使用者進行比較以確定使用者身份是否合法;
也需要從 Realm 得到使用者相應的角色/許可權進行驗證使用者是否能進行操作;可以把 Realm 看成 DataSource
3.外部架構圖
4.核心名詞簡介
• Subject
:任何可以與應用互動的“使用者”;
• SecurityManager
:相當於SpringMVC 中的 DispatcherServlet;是 Shiro 的心臟;
所有具體的互動都通過 SecurityManager 進行控制;它管理著所有 Subject、且負責進
行認證、授權、會話及快取的管理。
• Authenticator
:負責 Subject 認證,是一個擴充套件點,可以自定義實現;可以使用認證
策略(Authentication Strategy),即什麼情況下算使用者認證通過了;
• Authorizer
:授權器、即訪問控制器,用來決定主體是否有許可權進行相應的操作;即控
制著使用者能訪問應用中的哪些功能;
• Realm
:可以有 1 個或多個 Realm,可以認為是安全實體資料來源,即用於獲取安全實體
的;可以是JDBC 實現,也可以是記憶體實現等等;由使用者提供;所以一般在應用中都需要
實現自己的 Realm;
• SessionManager
:管理 Session 生命週期的元件;而 Shiro 並不僅僅可以用在 Web
環境,也可以用在如普通的 JavaSE 環境
• CacheManager
:快取控制器,來管理如使用者、角色、許可權等的快取的;因為這些資料
基本上很少改變,放到快取中後可以提高訪問的效能
• Cryptography
:密碼模組,Shiro 提高了一些常見的加密元件用於如密碼加密/解密。
初步認識(maven-java project)
pom.xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc 和web專案整合-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
Quickstart.java
package com.tf;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
//使用shiro.ini檔案初始化工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//得到當前使用者物件
Subject currentUser = SecurityUtils.getSubject();
//會話
/* Session session = currentUser.getSession();
session.setAttribute("name", "xiaopang");
String value = (String) session.getAttribute("name");
if (value.equals("xiaopang")) {
log.info("name"+value);
}*/
//當前使用者認證?
if (!currentUser.isAuthenticated()) {
//將使用者名稱和密碼封裝為一個UsernamePasswordToken物件
UsernamePasswordToken token = new UsernamePasswordToken("admin", "111");
//記住功能 web工程可以用到
token.setRememberMe(true);
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.info("賬戶不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("密碼錯誤" + token.getPrincipal() );
} catch (LockedAccountException lae) {
log.info("賬戶被凍結 " + token.getPrincipal() );
}
catch (AuthenticationException ae) {
log.info("未知異常!");
}
}
log.info("User " + currentUser.getPrincipal() );
//具有role?
if (currentUser.hasRole("ceo")) {
log.info("具有ceo角色");
} else {
log.info("不具有ceo角色");
}
if (currentUser.hasRole("admin")) {
log.info("具有admin角色");
} else {
log.info("不具有admin角色");
}
//具有許可權?
if (currentUser.isPermitted("sys:user:list")) {
log.info("具有sys:user:list");
} else {
log.info("不具有sys:user:list");
}
//退出功能 web工程用到
currentUser.logout();
System.exit(0);
}
}
shiro.ini
# ----------------使用者-------------------------------------------------------------
[users]
# 定義一個root的使用者 密碼secret 角色:admin
root = secret, admin
admin =111, admin
# 使用者:lonestarr 密碼:vespa 角色:goodguy和schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------角色許可權------------------------------------------------------------
[roles]
# admin使用者具有所有許可權
admin = *
# goodguy 具有sys:user下的所有許可權
goodguy = sys:user:*
# schwartz 具有sys:user:delete的許可權
schwartz = sys:user:delete
# 後期這個資料應該從資料庫查詢
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=TRACE
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
進一步學習
初步認識後,我們會發現使用者資訊如果定義在配置檔案ini中,不是太理想,而應該定義到資料庫中。
接下來我們可以先來模擬一下資料庫的操作。需要自定義一個realm。
##自定義一個Realm類
① 編寫Realm類繼承AuthorizingRealm類
package com.tf.shiro02;
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 java.util.ArrayList;
import java.util.List;
/*
* shiro涉及最少5張表
* 使用者表 角色表 許可權表
* 使用者角色表 角色許可權表
* */
public class MyRealm extends AuthorizingRealm {
//授權
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授權===========");
//把當前使用者的角色和許可權查詢出來
// 通過shiro提供的api授權處理即可
// AuthorizationInfo 的一個子介面SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//模擬資料庫的資料
List<String> roles = new ArrayList<String >();
roles.add("admin");
roles.add("ceo");
roles.add("PD");
List<String> permissions = new ArrayList<String>();
permissions.add("admin");
permissions.add("sys:user:add");
permissions.add("sys:user:update");
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
//認證 主要是用來 比對 使用者名稱和密碼
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("認證===========");
String username = (String)authenticationToken.getPrincipal();
String password = new String((char[])authenticationToken.getCredentials());
//呼叫service層
//模擬service
String uname= "admin";
String pwd = "jbgsn";
if(username.equals(username)){
if(password.equals(pwd)){
AuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
return info;
}else{
throw new IncorrectCredentialsException("密碼不存在!");
}
}else{
throw new UnknownError("賬戶不存在!");
}
}
}
②:shiro02.ini
#自定義Realm的全路徑
customRealm=com.tf.shiro02.MyRealm
securityManager.realms=$customRealm
③: TestShiro02
package com.tf.shiro02;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestShiro02 {
public static void main(String[] args){
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro02.ini");
//import org.apache.shiro.mgt.SecurityManager; 不要導錯包了
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currrentUser = SecurityUtils.getSubject();
//使用者認證?
if(!currrentUser.isAuthenticated()){
//使用者名稱和密碼以後可以從資料庫裡面獲取
UsernamePasswordToken token = new UsernamePasswordToken("admin","jbgsn");
try{
currrentUser.login(token);
}catch (UnknownAccountException uae) {
System.out.println("賬戶不存在 " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
System.out.println("密碼錯誤" + token.getCredentials() );
} catch (LockedAccountException lae) {
System.out.println("賬戶被凍結 " + token.getPrincipal() );
}
catch (AuthenticationException ae) {
System.out.println("未知異常!");
}
//角色判斷
if(currrentUser.hasRole("ceo")){
System.out.println("具有ceo角色");
}else{
System.out.println("不具有ceo角色");
}
//許可權
if(currrentUser.isPermitted("sys:user:add")){
System.out.println("具有sys:user:add許可權");
}else{
System.out.println("不具有sys:user:add許可權");
}
}else{
System.out.println("認證成功,可以登陸了");
}
}
}
結果:
授權=========== 具有ceo角色
授權=========== 具有sys:user:add許可權
結果會發現呼叫了兩次授權,正常情況其實呼叫一次就行了,這是因為沒有用到快取,後面和spring的整合可以加上這個快取.
加密功能
shiro提供了加密功能(MD5Hash類)
package com.tf.shiro02;
import org.apache.shiro.crypto.hash.Md5Hash;
public class Md5 {
public static void main(String[] args){
Md5Hash md5Hash1 = new Md5Hash("jbgsn","admin");
Md5Hash md5Hash2 = new Md5Hash("jbgsn","admin",1024);
System.out.println(md5Hash1);
System.out.println(md5Hash2);
}
}
密碼 :jbgsn 鹽值:admin(儘量是唯一的,比如:當前使用者名稱) 加密次數:1024
加密的結果是32位
結果:
027dd732a22adc14c5e1900b7087ade9
7a85bf432ebb5bf9f96f755ba4ad6450
shiro和SSM整合(web project)
參照下載的 shiro 原始碼中的samples\spring 配置web.xml 檔案和 Spring 的配置檔案
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--載入spring的配置檔案-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:app*.xml</param-value>
</context-param>
<!--springMVC的配置檔案-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--先攔截所有路徑,再考慮放行-->
<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>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
</web-app>
• DelegatingFilterProxy 作用是自動到 Spring 容器查詢名字為 shiroFilter(filter-name)的 bean 並把所有 Filter
的操作委託給它。
URL 匹配順序
• URL 許可權採取第一次匹配優先的方式,即從頭開始
使用第一個匹配的 url 模式對應的攔截器鏈。
• 如:
– /bb/=filter1
– /bb/aa=filter2
– /=filter3
– 如果請求的url是“/bb/aa”,因為按照宣告順序進行匹
配,那麼將使用 filter1 進行攔截。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 快取 -->
<property name="cacheManager" ref="cacheManager"/>
<!--自定義realm-->
<property name="realm" ref="MyRealm"/>
</bean>
<!--快取管理-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!--自定義realm-->
<bean id="MyRealm" class="com.tf.shiro02.MyRealm">
</bean>
<!--shiro註解在spring相關類中生效 -->
<!--生命週期-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--id :shiroFilter必須和web.xml中的DelegatingFilterProxy的servlet-name的id一致-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登入頁面-->
<property name="loginUrl" value="/login.jsp"/>
<!--登入成功後的頁面-->
<property name="successUrl" value="/success.jsp"/>
<!--沒有許可權的頁面-->
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
<!--自定義設定-->
<!--anon: 匿名訪問
authc:認證後訪問
切記有先後順序
-->
<property name="filterChainDefinitions">
<value>
/favicon.ico = anon
/login = anon
/logout = anon
/css**=anon
# allow WebStart to pull the jars for the swing app:
/*.jar = anon
# everything else requires authentication:
/** = authc
</value>
</property>
</bean>
</beans>
ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir/shiro-spring-sample"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
<cache name="org.apache.shiro.realm.SimpleAccountRealm.authorization"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="600"
overflowToDisk="false"/>
</ehcache>
springmvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--掃描controller包-->
<context:component-scan base-package="com.tf.controller"></context:component-scan>
<!-- 註解驅動 -->
<mvc:annotation-driven></mvc:annotation-driven>
<!--放行靜態資源-->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- 配置檢視解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- shiro註解在spring相關類中生效
@RequiresRoles("Manager") //具有Manager角色才能訪問
如果在service層使用這些註解,則配置到spring配置檔案中即可
但,如果在controller中使用註解,需要把這幾個配置配置到springmvc的配置檔案中
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
MyController
package com.tf.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyController {
@RequestMapping("/index")
public String show(){
return "main";
}
@RequestMapping("/login")
public String login() {
//模擬
String username = "admin";
String password = "jbgsn";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try {
currentUser.login(usernamePasswordToken);
//自動呼叫Realm 進行授權
return "main";
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
}
MyRealm
package com.tf.shiro02;
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 java.util.ArrayList;
import java.util.List;
/*
* shiro涉及最少5張表
* 使用者表 角色表 許可權表
* 使用者角色表 角色許可權表
* */
public class MyRealm extends AuthorizingRealm {
//授權
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授權===========");
//把當前使用者的角色和許可權(選單表或資源表)查詢出來
// 通過shiro提供的api授權處理即可
// AuthorizationInfo 的一個子介面SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//模擬資料庫的資料
List<String> roles = new ArrayList<String >();
roles.add("admin");
roles.add("ceo");
roles.add("PD");
List<String> permissions = new ArrayList<String>();
permissions.add("admin");
permissions.add("sys:user:add");
permissions.add("sys:user:update");
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
//認證 主要是用來 比對 使用者名稱和密碼
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("認證===========");
//使用者輸入的使用者名稱和密碼
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String username = token.getUsername();
String password =new String(token.getPassword());
//呼叫service層
//模擬service
String uname= "admin";
String pwd = "jbgsn";
if(username.equals(uname)){
if(password.equals(pwd)){
/*
* 引數一:user物件 ,這裡先因為沒有封裝物件,先傳入username
* 引數二:密碼
* 引數三:自定義realm的名字
* */
AuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
return info;
}else{
throw new IncorrectCredentialsException("密碼不存在!");
}
}else{
throw new UnknownError("賬戶不存在!");
}
}
}
驗證結果:
一:先訪問/index 是不能訪問到main.jsp
二:應該先訪問/login 才能訪問到main.jsp
注意:如果這兩步做顛倒了,需要先清理一下瀏覽器的快取,再測試
授權
授權,也叫訪問控制,即在應用中控制誰訪問哪些資源(如訪問頁面/編輯資料/頁面操作等)。在授權中需瞭解的幾個關鍵物件:主體(Subject)、資源(Resource)、許可權(Permission)、角色(Role)。
主體(Subject):訪問應用的使用者,在 Shiro 中使用 Subject 代表該使用者。使用者只有授權後才允許訪問相應的資源。
資源(Resource):在應用中使用者可以訪問的 URL,比如訪問 JSP 頁面、檢視/編輯某些資料、訪問某個業務方法、列印文字等等都是資源。使用者只要授權後才能訪問。
許可權(Permission):安全策略中的原子授權單位,通過許可權我們可以表示在應用中使用者有沒有操作某個資源的權力。即許可權表示在應用中使用者能不能訪問某個資源,如:訪問使用者列表頁面檢視/新增/修改/刪除使用者資料(即很多時候都是CRUD(增查改刪)式許可權控制)等。許可權代表了使用者有沒有操作某個資源的權利,即反映在某個資源上的操作允不允許。
Shiro 支援粗粒度許可權(如使用者模組的所有許可權)和細粒度許可權(操作某個使用者的許可權,即例項級別的)
角色(Role):許可權的集合,一般情況下會賦予使用者角色而不是許可權,即這樣使用者可以擁有一組許可權,賦予許可權時比較方便。典型的如:專案經理、技術總監、CTO、開發工程師等都是角色,不同的角色擁有一組不同的許可權。
有三種方式可以實現
一:程式設計式
@RequestMapping("/login")
public String login(){
String username = "admin";
String password = "jbgsn";
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
// token.setRememberMe(true);//記住我功能 會話區間,直接能訪問成功介面
try{
//subject.isRemembered();記住我
subject.login(token);//自定義realm
System.out.println(currentUser.hasRole("admin")?"具有admin角色":"不具有admin角色");
System.out.println(currentUser.hasRole("PM")?"具有PM角色":"不具有PM角色");
System.out.println(currentUser.hasRole("CEO")?"具有CEO角色":"不具有CEO角色");
System.out.println(currentUser.isPermitted("sys:menu:add")?"具有新增選單許可權":"不具有選單新增許可權");
System.out.println(currentUser.isPermitted("sys:menu:list")?"具有查詢選單許可權":"不具有查詢選單許可權");
System.out.println(currentUser.isPermitted("sys:order:add")?"具有新增訂單許可權":"不具有新增訂單許可權");
return "redirect:index";
}catch(Exception e){
System.out.println(e.getMessage());
return null;
}
}
這個顯然很麻煩,而且可移植性不好,故不提倡使用
二:註解式
/**
* 判斷許可權的第二種方式 註解式
* @return
*/
@RequiresRoles("Manager") //具有Manager角色才能訪問
@RequestMapping("/index")
public String index(){
return "main";
}
@RequiresRoles("admin") //具有admin角色才能訪問
@RequestMapping("/index1")
public String index1(){
return "main";
}
@RequiresPermissions({"sys:menu:list","sys:user:list"})
@RequestMapping("/index2")
public String index2(){
return "main";
}
但是記得:
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
第三種:使用shiro提供的標籤
main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%--第一步匯入標籤庫 只能再jsp中使用--%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>主頁</title>
</head>
<body>
<shiro:principal></shiro:principal>你好!
<shiro:hasRole name="admin">
具有admin角色
</shiro:hasRole>
<shiro:hasPermission name="sys:user:add">
新增使用者
</shiro:hasPermission>
<shiro:hasPermission name="sys:user:delete">
刪除使用者
</shiro:hasPermission>
</body>
</html>
Shiro 標籤
• Shiro 提供了 JSTL 標籤用於在 JSP 頁面進行許可權控制,如根據登入使用者顯示相應的頁面按鈕。
guest
標籤
使用者沒有身份驗證時顯示相應資訊,即遊客訪問資訊:
user
標籤:
使用者已經經過認證/記住我登入後顯示相應的資訊。
authenticated
標籤:
使用者已經身份驗證通過,即記住我
Subject.login登入成功,不是記住我登入的
notAuthenticated
標籤:
使用者未進行身份驗證,即沒有呼叫Subject.login進行登入,包括記住我自動登入的也屬於未進行身份驗證。
pincipal
標籤:
顯示使用者身份資訊,預設呼叫
Subject.getPrincipal() 獲取,即 Primary Principal。
hasRole
標籤:
如果當前Subject有任意一個角色(或的關係)將顯示body體內容。
hasAnyRoles
標籤:
如果當前Subject有任意一個角色(或的關係)將顯示body體內容。
lacksRole
:
如果當前 Subject 沒有角色將顯示 body 體內容
hasPermission
:
如果當前 Subject 有許可權將顯示 body 體內容
lacksPermission
:
如果當前Subject沒有許可權將顯示body體內容。
許可權註解
@RequiresAuthentication:表示當前Subject已經通過login 進行了身份驗證;即 Subject. isAuthenticated() 返回 true
@RequiresUser:表示當前 Subject 已經身份驗證或者通過記住我登入的。
@RequiresGuest:表示當前Subject沒有身份驗證或通過記住我登入過,即是遊客身份。
@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示當前 Subject 需要角色 admin 和user
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示當前 Subject 需要許可權 user:a 或
user:b。
@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示當前 Subject 需要許可權 user:a 或
user:b。
身份驗證基本流程
1、收集使用者身份/憑證,即如使用者名稱/密碼
2、呼叫 Subject.login 進行登入,如果失敗將得到相應的 AuthenticationException 異常,根據異常提示使用者
錯誤資訊;否則登入成功
3、建立自定義的 Realm 類,繼承org.apache.shiro.realm.AuthorizingRealm 類,實現doGetAuthenticationInfo() 方法
AuthenticationException
如果身份驗證失敗請捕獲 AuthenticationException 或其子類
最好使用如“使用者名稱/密碼錯誤”而不是“使用者名稱錯誤”/“密碼錯誤”,防止一些惡意使用者非法掃描帳號庫;
Realm
Realm:Shiro 從 Realm 獲取安全資料(如使用者、角色、許可權),即 SecurityManager 要驗證使用者身份,那麼它需要從 Realm 獲取相應的使用者進行比較以確定使用者身份是否合法;也需要從Realm得到使用者相應的角色/許可權進行驗證使用者是否能進行操作
Realm 的繼承關係:
一般繼承 AuthorizingRealm(授權)即可;其繼承了AuthenticatingRealm(即身份驗證),而且也間接繼承了CachingRealm(帶有快取實現)。
Authenticator
Authenticator 的職責是驗證使用者帳號,是 Shiro API 中身份驗證核心的入口點:如果驗證成功,將返回AuthenticationInfo 驗證資訊;此資訊中包含了身份及憑證;如果驗證失敗將丟擲相應的 AuthenticationException 異常
SecurityManager 介面繼承了 Authenticator,另外還有一個ModularRealmAuthenticator實現,其委託給多個Realm 進行
驗證,驗證規則通過 AuthenticationStrategy 介面指定
AuthenticationStrategy
realm 的認證方式
AuthenticationStrategy 介面的預設實現:
FirstSuccessfulStrategy:只要有一個 Realm 驗證成功即可,只返回第一個 Realm 身份驗證成功的認證資訊,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一個Realm驗證成功即可,和FirstSuccessfulStrategy 不同,將返回所有Realm身份驗證成功的認證資訊
AllSuccessfulStrategy:所有Realm驗證成功才算成功,且返回所有Realm身份驗證成功的認證資訊,如果有一個失敗就失敗了。
ModularRealmAuthenticator 預設是 AtLeastOneSuccessfulStrategy策略
授權流程
流程如下:
1、首先呼叫 Subject.isPermitted*/hasRole* 介面,其會委託給SecurityManager,而 SecurityManager 接著會委託給 Authorizer;
2、Authorizer是真正的授權者,如果呼叫如isPermitted(“user:view”),其首先會通過 PermissionResolver 把字串轉換成相應的 Permission 例項;
3、在進行授權之前,其會呼叫相應的 Realm 獲取 Subject 相應的角色/許可權用於匹配傳入的角色/許可權;
4、Authorizer 會判斷 Realm 的角色/許可權是否和傳入的匹配,如果有多個Realm,會委託給 ModularRealmAuthorizer 進行迴圈判斷,
如果匹配如 isPermitted*/hasRole* 會返回true,否則返回false表示授權失敗。
ModularRealmAuthorizer
ModularRealmAuthorizer 進行多 Realm 匹配流程:
1、首先檢查相應的 Realm 是否實現了實現了Authorizer;
2、如果實現了 Authorizer,那麼接著呼叫其相應的isPermitted*/hasRole* 介面進行匹配;
3、如果有一個Realm匹配那麼將返回 true,否則返回 false。