Shiro之詳細入門附Demo
第一步:Apache Shiro簡介:
Apache Shiro 是一個強大而靈活的開源安全框架,它乾淨利落地處理身份認證,授權,企業會話管理和加密。
Apache Shiro 的首要目標是易於使用和理解。安全有時候是很複雜的,甚至是痛苦的,但它沒有必要這樣。框架應
該儘可能掩蓋複雜的地方,露出一個乾淨而直觀的 API,來簡化開發人員在使他們的應用程式安全上的努力。
以下是你可以用 Apache Shiro 所做的事情:
- 驗證使用者來核實他們的身份
- 對使用者執行訪問控制,如:
- 判斷使用者是否被分配了一個確定的安全形色
- 判斷使用者是否被允許做某事
- 在任何環境下使用 Session API,即使沒有 Web 或 EJB 容器。
- 在身份驗證,訪問控制期間或在會話的生命週期,對事件作出反應。
- 聚集一個或多個使用者安全資料的資料來源,並作為一個單一的複合使用者“檢視”。
- 啟用單點登入(SSO)功能。
為沒有關聯到登入的使用者啟用”Remember Me”服務
Shiro 檢視在所有應用程式環境下實現這些目標——從最簡單的命令列應用程式到最大的企業應用,不強制依賴其
他第三方框架,容器,或應用伺服器。當然,該專案的目標是儘可能地融入到這些環境,但它能夠在任何環境下立
即可用
Shiro 把 Shiro 開發團隊稱為“應用程式的四大基石” ——身份驗證,授權,會話管理和加密作為其目標。
- Authentication:有時也簡稱為“登入”,這是一個證明使用者是他們所說的他們是誰的行為。
- Authorization:訪問控制的過程,也就是絕對“誰”去訪問“什麼”。
- Session Management:管理使用者特定的會話,即使在非 Web 或 EJB 應用程式。
- Cryptography:通過使用加密演算法保持資料安全同時易於使用。
也提供了額外的功能來支援和加強在不同環境下所關注的方面,尤其是以下這些:
- Web Support: Shiro 的 web 支援的 API 能夠輕鬆地幫助保護 Web 應用程式。
- Caching:快取是 Apache Shiro 中的第一層公民,來確保安全操作快速而又高效。
- Concurrency: Apache Shiro 利用它的併發特性來支援多執行緒應用程式。
- Testing:測試支援的存在來幫助你編寫單元測試和整合測試,並確保你的能夠如預期的一樣安全。
- “Run As”:一個允許使用者假設為另一個使用者身份(如果允許)的功能,有時候在管理指令碼很有用。
- “Remember Me”:在會話中記住使用者的身份,所以他們只需要在強制時候登入。
第二步:搭建平臺
官方網站給的官方示例是基於INI的,將使用者角色資訊和許可權資訊和各種元件都統一配置在shiro.ini配置檔案中。這種方式雖然是官方推薦的,但是作為java開發人員,暫時是不能接受的。我就要XML!0.0
研究了網上很多demo例項,或簡單或複雜。比較難以理解,在看過很多示例和API之後整合了一個最簡最容易理解的一個小demo。
首先熟悉一下基本術語:
Subject(org.apache.shiro.subject.Subject):Subject 是一個安全術語,它基本上的意思是“當前正在執行的使用者的特定的安全檢視”。你可以把 Subject 看成是 Shiro 的”User”概念。
SecurityManager(org.apache.shiro.session.SessionManager):SecurityManager 是 Shiro 架構的心臟,並作為一種“保護傘”物件來協調內部的安全元件共同構成一個物件圖。然而,一旦 SecurityManager 和它的內建物件圖已經配置給一個應用程式,那麼它單獨留下來,且應用程式開發人員幾乎使用他們所有的時間來處理 Subject API。
所有 Subject 例項都被繫結到(且這是必須的)一個 SecurityManager 上。當你與一個 Subject 互動時,那些互動作用轉化為與 SecurityManager 互動的特定 subject 的互動作用
Authenticator(org.apache.shiro.authc.Authenticator):Authenticator 是一個對執行及對使用者的身份驗證(登入)嘗試負責的元件。當一個使用者嘗試登入時,該邏輯被 Authenticator 執行。 Authenticator 知道如何與一個或多個 Realm 協調來儲存相關的使用者/帳戶資訊。從這些Realm 中獲得的資料被用來驗證使用者的身份來保證使用者確實是他們所說的他們是誰。
Authorizer(org.apache.shiro.authz.Authorizer):Authorizer 是負責在應用程式中決定使用者的訪問控制的元件。它是一種最終判定使用者是否被允許做某事的機制。
與 Authenticator 相似, Authorizer 也知道如何協調多個後臺資料來源來訪問角色惡化許可權資訊。 Authorizer 使用該資訊來準確地決定使用者是否被允許執行給定的動作。
SessionManager(org.apache.shiro.session.SessionManager):SessionManager 知道如何去建立及管理使用者 Session 生命週期來為所有環境下的使用者提供一個強健的 Session體驗。這在安全框架界是一個獨有的特色——Shiro 擁有能夠在任何環境下本地化管理使用者 Session 的能力,即使沒有可用的 Web/Servlet 或 EJB 容器,它將會使用它內建的企業級會話管理來提供同樣的程式設計體驗。SessionDAO 的存在允許任何資料來源能夠在持久會話中使用。SessionDAO(org.apache.shiro.session.mgt.eis.SessionDAO)代表 SessionManager 執行 Session 持久化(CRUD)操作。這允許任何資料儲存被插入到會話管理的基礎之中
CacheManager(org.apahce.shiro.cache.CacheManager):CacheManager 建立並管理其他 Shiro 元件使用的 Cache 例項生命週期。因為 Shiro 能夠訪問許多後臺資料來源,由於身份驗證,授權和會話管理,快取在框架中一直是一流的架構功能,用來在同時使用這些資料來源時提高效能。任何現代開源和/或企業的快取產品能夠被插入到 Shiro 來提供一個快速及高效的使用者體驗
Cryptography(org.apache.shiro.crypto.*):Cryptography 是對企業安全框架的一個很自然的補充。加密
Realms(org.apache.shiro.realm.Realm):Realms 在 Shiro 和你的應用程式的安全資料之間擔當“橋樑”或“聯結器”。當它實際上與安全相關的資料如用來執行身份驗證(登入)及授權(訪問控制)的使用者帳戶互動時, Shiro 從一個或多個為應用程式配置的 Realm 中尋找許多這樣的東西。你可以按你的需要配置多個 Realm(通常一個數據源一個 Realm),且 Shiro 將為身份驗證和授權對它們進行必要的協調
有點扯遠了0.0
第三步:整合SSM和Shiro
- 搭建基本的SSM框架
引入shiro依賴肯定是必須的,區別的是在web.xml中新增Shiro的過濾器。
<!-- 配置Shiro的過慮器,如果與Spring整合,則必須要使用Shiro提供的過慮器代理 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!-- 指定SpringBean代理Bean的名稱,如果沒有指定則與過慮器名稱保持一致 -->
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
<init-param>
<!-- 配置是否啟動過慮器的init/destory方法 -->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
<!-- 配置如果沒有許可權,則跳轉到的頁面 -->
<error-page>
<exception-type>org.apache.shiro.authz.AuthorizationException</exception-type>
<location>/denied.jsp</location>
</error-page>
<error-page>
<exception-type>org.apache.shiro.authz.UnauthorizedException</exception-type>
<location>/denied2.jsp</location>
</error-page>
spring_mvc.xml:(配置在DispatcherServlet中)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:task="http://www.springframework.org/schema/task" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 自動掃描(自動注入) -->
<context:component-scan base-package="cn.shiro" />
<mvc:annotation-driven />
<bean name="springContextUtil" class="cn.shiro.utils.SpringContextUtil"
scope="singleton"></bean>
<aop:config proxy-target-class="true" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/views/" p:suffix=".jsp" />
</beans>
注:<aop:config proxy-target-class="true" />
沒有的話可能會出現身份驗證的時候驗證角色不驗證許可權。
spring_mybaits.xml:(通過mybatis連線資料庫,不需要改動,直接拉過來)
spring_shiro.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 授權 認證 -->
<bean id="sampleRealm" class="cn.shiro.realm.SampleRealm"></bean>
<!-- 配置SecurityManager並指定Realm -->
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="sampleRealm" />
</bean>
<!-- 配置與shiro的整合,此名稱與web.xml中配置的targetBeanName相同 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 引用SecurityManager -->
<property name="securityManager" ref="securityManager" />
<!-- 登入的url -->
<property name="loginUrl" value="/login.jsp" />
<!-- 登入成功的url -->
<property name="successUrl" value="/index.jsp" />
<!-- 如果訪問不成功則顯示的url為 -->
<property name="unauthorizedUrl" value="/denied.jsp" />
<!-- 定義安全的頁面及訪問規則 -->
<property name="filterChainDefinitions">
<value>
/one/**=authc
/login.jsp=authc
/logout=logout
/**=authc
</value>
</property>
</bean>
<!-- 靜態注入,相當於呼叫SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<!-- 必須配置這個類 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- 以下是兩個啟用註解的許可權控制 Enable Shiro Annotations for Spring-configured beans. the lifecycleBeanProcessor has run: -->
<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>
SampleRealm.java:
package cn.shiro.realm;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.SimplePrincipalCollection;
import cn.shiro.pojo.UUser;
public class SampleRealm extends AuthorizingRealm {
/**
* 認證資訊,主要針對使用者登入,
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authcToken) throws AuthenticationException {
String username = (String) authcToken.getPrincipal();
String password = new String((char[]) authcToken.getCredentials());
UUser user = new UUser();
user.setEmail(username);
user.setId(1L);
user.setPswd(password);
user.setStatus(1L);
System.err.println(user);
if(UUser._0.equals(user.getStatus())){
throw new DisabledAccountException("帳號已經禁止登入!");
}else{
//更新登入時間 last login time
user.setLastLoginTime(new Date());
}
return new SimpleAuthenticationInfo(user,user.getPswd(), getName());
}
/**
* 授權
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
UUser user = (UUser)principals.getPrimaryPrincipal();
//根據使用者ID查詢角色(role),放入到Authorization裡。
Set<String> roles = new HashSet<String>();
roles.add(user.getEmail());
info.setRoles(roles);
System.out.println(roles);
//根據使用者ID查詢許可權(permission),放入到Authorization裡。
Set<String> permissions = new HashSet<String>();
permissions.add("admin:insert");
permissions.add("admin:del");
System.out.println(permissions.toString());
info.setStringPermissions(permissions);
return info;
}
/**
* 清空當前使用者許可權資訊
*/
public void clearCachedAuthorizationInfo() {
PrincipalCollection principalCollection = SecurityUtils.getSubject().getPrincipals();
SimplePrincipalCollection principals = new SimplePrincipalCollection(
principalCollection, getName());
super.clearCachedAuthorizationInfo(principals);
}
/**
* 指定principalCollection 清除
*/
public void clearCachedAuthorizationInfo(PrincipalCollection principalCollection) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(
principalCollection, getName());
super.clearCachedAuthorizationInfo(principals);
}
}
注:AuthorizingRealm是必須繼承的。實現裡面的doGetAuthenticationInfo(驗證角色)doGetAuthorizationInfo(查詢許可權)方法。沒有連線資料庫,查詢資料庫驗證許可權這一步我直接使用死資料來模擬。之前研究角色表關係走了很大的彎路,透過現象看本質,本質上就是查詢出角色寫入到物件中去。
controller.java
package cn.shiro.controller;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/one/")
public class OneController {
@RequiresAuthentication // 要求使用者必須要登入才可以訪問這個資源
@RequiresRoles("admin") // 要求使用者必須是admin角色才可以訪問這個方法
@RequiresPermissions("admin:del") // 要求的許可權
@RequestMapping("show")
@ResponseBody
public String show() {
System.err.println("Show.." + this);
return "b";
}
}
UUser.java
package cn.shiro.pojo;
import java.io.Serializable;
import java.util.Date;
import net.sf.json.JSONObject;
public class UUser implements Serializable {
private static final long serialVersionUID = 1L;
// 0:禁止登入
public static final Long _0 = new Long(0);
// 1:有效
public static final Long _1 = new Long(1);
private Long id;
/** 暱稱 */
private String nickname;
/** 郵箱 | 登入帳號 */
private String email;
/** 密碼 */
private transient String pswd;
/** 建立時間 */
private Date createTime;
/** 最後登入時間 */
private Date lastLoginTime;
/** 1:有效,0:禁止登入 */
private Long status;
public UUser() {
}
public UUser(UUser user) {
this.id = user.getId();
this.nickname = user.getNickname();
this.email = user.getEmail();
this.pswd = user.getPswd();
this.createTime = user.getCreateTime();
this.lastLoginTime = user.getLastLoginTime();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
public Long getStatus() {
return status;
}
public void setStatus(Long status) {
this.status = status;
}
public void setEmail(String email) {
this.email = email;
}
public String getPswd() {
return pswd;
}
public void setPswd(String pswd) {
this.pswd = pswd;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(Date lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public String toString() {
return JSONObject.fromObject(this).toString();
}
}
login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://shiro.apache.org/tags" prefix="s"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:authenticated>
登入成功: <s:principal></s:principal>
<br>
<a href="${pageContext.request.contextPath}/logout">退出</a>
</s:authenticated>
<s:notAuthenticated>
<p>登入</p>
<form action="" method="post">
Name:<input type="text" name="username"><br> Pwd:<input
type="text" name="password"><br> <input type="submit">
</form>
</s:notAuthenticated>
</body>
</html>
這是最簡單的shiro配置,刨去了sessionManager、CacheManager、加密等配置,後面再慢慢加。
OK