網上有很多文章介紹如何使用shiro坛猪,如果你是第一次接觸shiro,各種文章可能看了好多篇也還是一頭霧水污秆。筆者當初也是這樣劈猪,想找一個簡單的入門教程看看,了解一下shiro是什么良拼,能做什么战得,以及怎么操作,結果看了很多也不太明白庸推,走了很多彎路常侦。因為網上的文章大多是以過來者的身份介紹浇冰,在學習一個新東西的時候膜蠢,如果你折騰了好長時間略号,過來了犀斋,就能明白那些文章在講什么否彩,哪些是重點,哪些是坑古沥;如果你折騰了好長時間,還是云里霧里,那么恭喜你致讥,因為你看到了這篇文章,我會用初學者的眼光器赞,帶你在最短時間內抓住shiro的本質垢袱。
在學新東西的時候,要問三個問題:這個新東西是什么港柜?能做什么请契?我怎么操作使用它?
如果有余力夏醉,最好再問一個為什么爽锥,看一看它的源碼,理解它的原理畔柔,這樣從原理到實現細節(jié)的方方面面就都掌握了氯夷。
Apache Shiro是一個強大且易用的Java安全框架,能做身份驗證、授權靶擦、密碼學和會話管理腮考。
請參考shiro官網介紹
Shiro 最主要的兩個部分就是認證和授權,Shiro通過過濾攔截請求實現認證或授權玄捕。
shiro 主要框架圖
Subject
Subject即主體踩蔚,外部應用與subject進行交互,subject記錄了當前操作用戶枚粘,將用戶的概念理解為當前操作的主體馅闽,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序赌结。 Subject在shiro中是一個接口捞蛋,接口中定義了很多認證授相關的方法,外部程序通過subject進行認證授柬姚,而subject是通過SecurityManager安全管理器進行認證授權拟杉。
SecurityManager
SecurityManager即安全管理器,對全部的subject進行安全管理量承,它是shiro的核心搬设,負責對所有的subject進行安全管理穴店。通過SecurityManager可以完成subject的認證、授權等拿穴,實質上SecurityManager是通過Authenticator進行認證泣洞,通過Authorizer進行授權,通過SessionManager進行會話管理等默色。
SecurityManager是一個接口球凰,繼承了Authenticator, Authorizer, SessionManager這三個接口。
Authenticator
Authenticator即認證器腿宰,對用戶身份進行認證呕诉,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類吃度,通過ModularRealmAuthenticator基本上可以滿足大多數需求甩挫,也可以自定義認證器。
Authorizer
Authorizer即授權器椿每,用戶通過認證器認證通過伊者,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。
realm
Realm即領域间护,相當于datasource數據源亦渗,securityManager進行安全認證需要通過Realm獲取用戶權限數據,比如:如果用戶身份數據在數據庫那么realm就需要從數據庫獲取用戶身份信息汁尺。realm不僅僅是從數據源取數據央碟,在realm中還有認證、授權校驗的相關的代碼均函。
sessionManager
sessionManager即會話管理亿虽,shiro框架定義了一套會話管理,它不依賴web容器的session苞也,所以shiro可以使用在非web應用上洛勉,也可以將分布式應用的會話集中在一點管理,此特性可使它實現單點登錄如迟。
SessionDAO
SessionDAO即會話dao收毫,是對session會話操作的一套接口,比如要將session存儲到數據庫殷勘,可以通過jdbc將會話存儲到數據庫此再。
CacheManager
CacheManager即緩存管理,將用戶權限數據存儲在緩存玲销,這樣可以提高性能输拇。
Cryptography
Cryptography即密碼管理,shiro提供了一套加密/解密的組件贤斜,方便開發(fā)策吠。比如提供常用的散列逛裤、加/解密等功能。
從應用程序的角度看猴抹,它的工作流程是這樣的:
簡單的來說带族,它的工作流程是:應用代碼被包裝成 subject,然后 SecurityManager 通過Realm獲取用戶權限數據對其實現登錄和授權的校驗蟀给,經過授權就可以正常訪問了蝙砌。
Shiro認證與授權在Web中的實現
第一步:添加jar包
第二步:配置web.xml shiro過濾器
第三步:自定義Realm ,繼承 AuthorizingRealm 跋理,重寫 AuthorizationInfo(授權) 拍霜,重寫 AuthenticationInfo(認證)
第四步:配置spring-shiro.xml
第五步:在spring-mvc.xml中配置權限的控制 異常的跳轉
第六步:在controller中使用
- 第一步:添加jar包,版本1.3.2薪介,最近的穩(wěn)定版本
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
- 第二步:配置web.xml shiro過濾器;targetFilterLifecycle 為 true 代表由 spring 控制該 filter 的生命周期越驻。shiro 源碼中的 demo 配的是/*汁政,因為我只想控制用戶訪問后端接口的權限,靜態(tài)資源可以隨便訪問缀旁,所以后端接口都用的api開頭记劈,讓shiro過濾器只對api開頭的請求生效。
<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>/api/*</url-pattern>
</filter-mapping>
- 第三步:自定義Realm 并巍;shiro 中 realm 是進行認證和授權的組件目木,自帶了幾種實現,比如 jdbcRealm 和 iniRealm懊渡,實際項目中肯定都是自己實現 realm, 這里自定義 MyRealm 繼承 AuthorizingRealm刽射,分別實現認證和授權的方法。
public class MyRealm extends AuthorizingRealm {
//用來做測試的數據剃执,實際開發(fā)中都是從數據庫中取的
private static final String USERNAME = "admin";
private static final String PASSWORD = "123456";
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//principals.getPrimaryPrincipal()獲得的就是當前用戶名誓禁,權限取值在實際中是從數據庫中取出的
Set<String> permissions = new HashSet<String>();
permissions.add("sys:page1");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
//賬號錯誤
if(!username.equals(USERNAME)) {
throw new AuthenticationException("賬號不正確");
}
//密碼錯誤
if(!password.equals(PASSWORD)) {
throw new IncorrectCredentialsException("密碼不正確");
}
//getName()返回該realm的名字,代表該認證信息的來源是該realm
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
return info;
}
}
doGetAuthenticationInfo是認證的方法肾档,當用戶登陸的時候會調用
doGetAuthorizationInfo是授權的方法摹恰,在攔截器中進行權限校驗的時候會調用
- 第四步:配置spring-shiro.xml
配置 ShiroFilterFactoryBean ,注意 id 一定要和 web.xml 中 的 filter-name 一致怒见,否則 proxyFilter 找不到實際 filter俗慈。
shiro 的內置過濾器有anon、authc遣耍、logout闺阱、perms、roles等等,可以分成兩類舵变,一類會調用 realm 中的認證方法馏颂,一類會調用 realm中 的授權方法示血,更詳細的請自行百度。
<!--配置全局權限過濾器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--設置沒有登錄時的跳轉地址-->
<property name="loginUrl" value="/static/html/login.html"/>
<!--設置權限不足時的跳轉地址-->
<property name="unauthorizedUrl" value="/static/html/error.html"/>
<!--對哪個后端接口使用哪個過濾器進行配置救拉,等號后邊是shiro內置過濾器的名字-->
<property name="filterChainDefinitions">
<value>
<!--匿名訪問难审,/api/login是登陸接口,當然可以隨便訪問-->
/api/login = anon
<!--用戶退出登錄的接口亿絮,后端不需要實現該接口告喊,logout攔截到/api/logout的url后,就自動清除登錄狀態(tài)回到首頁了-->
<!--因為在web.xml中設置的url-parttern是/api/*派昧,隨意只有api開頭的url才會被攔截-->
/api/logout = logout
<!--其他所有接口都需要認證黔姜,也就是需要之前輸入過賬號密碼登錄過-->
/** = authc
</value>
</property>
</bean>
- 在 securityManager 中注入自定義的 realm
<!--非web環(huán)境使用DefaultSecurityManager-->
<bean id="myRealm" class="cn.shirodemo.shiro.MyRealm"/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
- AOP式方法級權限檢查
<!-- AOP式方法級權限檢查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
- 第五步:在 spring-mvc.xml 中配置異常的跳轉
<!-- 未認證或未授權時跳轉必須在springmvc里面配,spring-shiro里的shirofilter配不生效 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--沒有授權的異常 -->
<prop key="org.apache.shiro.authz.UnauthorizedException">
<!--捕獲該異常時跳轉的路徑 -->
/error
</prop>
<!--沒有認證的異常 -->
<prop key="org.apache.shiro.authz.UnauthenticatedException">
<!--捕獲該異常時跳轉的路徑 -->
/error
</prop>
</props>
</property>
</bean>
- 第六步:在controller中測試使用的驗證登入
@RequestMapping(value="/login.do",method=RequestMethod.POST)
@ResponseBody
public String login(String username,String password) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
Subject subjectecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
return JSONUtils.toJSONString(result);
}
現在只需要在相應方法前加上@RequiresPermissions就可以進行權限控制了
@RequiresPermissions("sys:page1")
@RequestMapping(value="/toPage1.do",method= RequestMethod.POST)
@ResponseBody
public String toPage1() {
Map<String, Object> result = new HashMap<String, Object>();
result.put("success", true);
return JSONUtils.toJSONString(result);
}
最后補充一點蒂萎,web 項目的前臺頁面如何用 shiro 進行配置呢秆吵。
如果是jsp頁面,在 jsp 中引入 shiro 標簽
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:authenticated> <a href="xxx.jsp">xxxxxx</a> </shiro:authenticated>
<shiro:hasRole name="admin"> <a href="xxx.jsp">xxxxxx</a> </shiro:hasRole>
<shiro:hasPermission name="user:create"> <a href="xxx.jsp">xxxxxx</a> </shiro:hasPermission>
- guest標簽 :驗證當前用戶是否為“訪客”五慈,即未認證(包含未記啄杉拧)的用戶。
- user標簽 :認證通過或已記住的用戶泻拦。
- authenticated標簽 :已認證通過的用戶毙芜。不包含已記住的用戶。
- notAuthenticated標簽 :未認證通過用戶争拐,與authenticated標簽相對應腋粥。與guest標簽的區(qū)別是,該標簽包含已記住用戶架曹。
- principal 標簽 :輸出當前用戶信息隘冲,通常為登錄帳號信息。
- hasRole標簽 :驗證當前用戶是否屬于該角色绑雄。
- lacksRole標簽 :與hasRole標簽邏輯相反对嚼,當用戶不屬于該角色時驗證通過。
- hasAnyRole標簽 :驗證當前用戶是否屬于以下任意一個角色绳慎。
- hasPermission標簽 :驗證當前用戶是否擁有指定權限纵竖。
- lacksPermission標簽 :與hasPermission標簽邏輯相反,當前用戶沒有制定權限時杏愤,驗證通過靡砌。
- 如果前臺頁面是 html,且使用了 thymeleaf 的模板引擎
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
配置一下 thymeleaf
<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<property name="additionalDialects">
<set>
<bean class="at.pollux.thymeleaf.shiro.dialect.ShiroDialect"/>
</set>
</property>
</bean>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
這樣就可以在頁面中使用了
<p shiro:hasRole="admin"></p>
<p shiro:hasPermission="userInfo:add"></p>
- 如果前臺頁面是 html珊楼,沒有使用模板怎么辦通殃?
這樣就無法使用shiro標簽了,不過這里有個解決思路,前臺頁面通過ajax獲取后臺某個角色或是權限的信息画舌,然后在前臺用改變標簽樣式display:none
的方法讓相應內容隱顯堕担。