簡(jiǎn)介
這里新開一篇文章,記錄下shiro的相關(guān)知識(shí)。
Shiro不會(huì)去維護(hù)用戶、維護(hù)權(quán)限;這些需要我們自己去設(shè)計(jì)/提供;然后通過相應(yīng)的接口注入給Shiro即可。
首先,我們從外部來看Shiro吧,即從應(yīng)用程序角度的來觀察如何使用Shiro完成工作。如下圖:

Subject:主體,代表了當(dāng)前“用戶”,這個(gè)用戶不一定是一個(gè)具體的人,與當(dāng)前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機(jī)器人等;即一個(gè)抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會(huì)委托給SecurityManager;可以把Subject認(rèn)為是一個(gè)門面;SecurityManager才是實(shí)際的執(zhí)行者;
SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會(huì)與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負(fù)責(zé)與后邊介紹的其他組件進(jìn)行交互,如果學(xué)習(xí)過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager要驗(yàn)證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。
從以上也可以看出,Shiro不提供維護(hù)用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。
身份驗(yàn)證
身份驗(yàn)證,即在應(yīng)用中誰能證明他就是他本人。一般提供如他們的身份ID一些標(biāo)識(shí)信息來表明他就是他本人,如提供身份證,用戶名/密碼來證明。
在shiro中,用戶需要提供principals (身份)和credentials(證明)給shiro,從而應(yīng)用能驗(yàn)證用戶身份:
principals:身份,即主體的標(biāo)識(shí)屬性,可以是任何東西,如用戶名、郵箱等,唯一即可。一個(gè)主體可以有多個(gè)principals,但只有一個(gè)Primary principals,一般是用戶名/密碼/手機(jī)號(hào)。
credentials:證明/憑證,即只有主體知道的安全值,如密碼/數(shù)字證書等。
最常見的principals和credentials組合就是用戶名/密碼了。接下來先進(jìn)行一個(gè)基本的身份認(rèn)證。Realm Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager要驗(yàn)證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。
多Realm配置。 會(huì)按照realm聲明的順序進(jìn)行使用
自定義的Realm一般繼承AuthorizingRealm即可。
授權(quán)
Shiro支持三種方式的權(quán)限控制
編程式:通過寫if/else授權(quán)代碼塊完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) { ... }注解式:通過在執(zhí)行的Java方法上放置相應(yīng)的注解完成: 沒有權(quán)限將拋出相應(yīng)的異常;
@RequiresRoles("admin")
public void hello() {
//有權(quán)限
}JSP/GSP標(biāo)簽:在JSP/GSP頁面通過相應(yīng)的標(biāo)簽完成:
<shiro:hasRole name="admin">
<!— 有權(quán)限 —>
</shiro:hasRole>
角色/資源
Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles,不同的地方是它在判斷為假的情況下會(huì)拋出UnauthorizedException異常
Shiro提供了isPermitted和isPermittedAll用于判斷用戶是否擁有某個(gè)權(quán)限或所有權(quán)限
與Web集成
Shiro提供了與Web集成的支持,其通過一個(gè)ShiroFilter入口來攔截需要安全控制的URL,然后進(jìn)行相應(yīng)的控制,ShiroFilter類似于如Strut2/SpringMVC這種web框架的前端控制器,其是安全控制的入口點(diǎn),其負(fù)責(zé)讀取配置(如ini配置文件),然后判斷URL是否需要登錄/權(quán)限等工作。
- mvc的話就配置filter
- springboot的話就ShiroFilterFactoryBean
會(huì)話管理
Shiro提供了完整的企業(yè)級(jí)會(huì)話管理功能,不依賴于底層容器(如web容器tomcat),不管JavaSE還是JavaEE環(huán)境都可以使用,提供了會(huì)話管理、會(huì)話事件監(jiān)聽、會(huì)話存儲(chǔ)/持久化、容器無關(guān)的集群、失效/過期支持、對(duì)Web的透明支持、SSO單點(diǎn)登錄的支持等特性。即直接使用Shiro的會(huì)話管理可以直接替換如Web容器的會(huì)話管理。
會(huì)話管理器管理著應(yīng)用中所有Subject的會(huì)話的創(chuàng)建、維護(hù)、刪除、失效、驗(yàn)證等工作。是Shiro的核心組件,頂層組件SecurityManager直接繼承了SessionManager,且提供了SessionsSecurityManager實(shí)現(xiàn)直接把會(huì)話管理委托給相應(yīng)的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默認(rèn)SecurityManager都繼承了SessionsSecurityManager
Shiro提供了三個(gè)默認(rèn)實(shí)現(xiàn):
DefaultSessionManager:DefaultSecurityManager使用的默認(rèn)實(shí)現(xiàn),用于JavaSE環(huán)境;
ServletContainerSessionManager:DefaultWebSecurityManager使用的默認(rèn)實(shí)現(xiàn),用于Web環(huán)境,其直接使用Servlet容器的會(huì)話;并且可以設(shè)置session相關(guān)cookies的name等信息。
DefaultWebSessionManager:用于Web環(huán)境的實(shí)現(xiàn),可以替代ServletContainerSessionManager,自己維護(hù)著會(huì)話,直接廢棄了Servlet容器的會(huì)話管理。
可以正常配置會(huì)話監(jiān)聽器
會(huì)話存儲(chǔ)/持久化 Shiro提供SessionDAO用于會(huì)話的CRUD,即DAO(Data Access Object)模式實(shí)現(xiàn):
會(huì)話驗(yàn)證
單點(diǎn)登錄
Shiro 1.2開始提供了Jasig CAS單點(diǎn)登錄的支持,單點(diǎn)登錄主要用于多系統(tǒng)集成。
大體流程如下:
1、訪問客戶端需要登錄的頁面http://localhost:9080/ client/,此時(shí)會(huì)跳到單點(diǎn)登錄服務(wù)器https://localhost:8443/ server/login?service=https://localhost:9443/ client/cas;
2、如果此時(shí)單點(diǎn)登錄服務(wù)器也沒有登錄的話,會(huì)顯示登錄表單頁面,輸入用戶名/密碼進(jìn)行登錄;
3、登錄成功后服務(wù)器端會(huì)回調(diào)客戶端傳入的地址:https://localhost:9443/client/cas?ticket=ST-1-eh2cIo92F9syvoMs5DOg-cas01.example.org,且?guī)е粋€(gè)ticket;
4、客戶端會(huì)把ticket提交給服務(wù)器來驗(yàn)證ticket是否有效;如果有效服務(wù)器端將返回用戶身份;
5、客戶端可以再根據(jù)這個(gè)用戶身份獲取如當(dāng)前系統(tǒng)用戶/角色/權(quán)限信息。
接入流程:
- 導(dǎo)入shiro-cas 的依賴
- 自定義CasRealm
public class MyCasRealm extends CasRealm {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
}
- 配置
<bean id="casRealm" class="com.github.zhangkaitao.shiro.chapter13.realm.MyCasRealm">
<property name="userService" ref="userService"/>
……
<property name="casServerUrlPrefix" value="https://localhost:8443/chapter14-server"/>
<property name="casService" value="https://localhost:9443/chapter14-client/cas"/>
</bean>
casServerUrlPrefix:是CAS Server服務(wù)器端地址;
casService:是當(dāng)前應(yīng)用CAS服務(wù)URL,即用于接收并處理登錄成功后的Ticket的;
- cas的token驗(yàn)證
<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
<property name="failureUrl" value="/casFailure.jsp"/>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="https://localhost:8443/chapter14-server/login?service=https://localhost:9443/chapter14-client/cas"/>
<property name="successUrl" value="/"/>
<property name="filters">
<util:map>
<entry key="cas" value-ref="casFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/casFailure.jsp = anon
/cas = cas
/logout = logout
/** = user
</value>
</property>
</bean>
loginUrl:https://localhost:8443/chapter15-server/login表示服務(wù)端端登錄地址,登錄成功后跳轉(zhuǎn)到?service參數(shù)對(duì)于的地址進(jìn)行客戶端驗(yàn)證及登錄;
“/cas=cas”:即/cas地址是服務(wù)器端回調(diào)地址,使用CasFilter獲取Ticket進(jìn)行登錄。
OAuth2集成
OAuth角色
資源擁有者(resource owner):能授權(quán)訪問受保護(hù)資源的一個(gè)實(shí)體,可以是一個(gè)人,那我們稱之為最終用戶;如新浪微博用戶zhangsan;
資源服務(wù)器(resource server):存儲(chǔ)受保護(hù)資源,客戶端通過access token請(qǐng)求資源,資源服務(wù)器響應(yīng)受保護(hù)資源給客戶端;存儲(chǔ)著用戶zhangsan的微博等信息。
授權(quán)服務(wù)器(authorization server):成功驗(yàn)證資源擁有者并獲取授權(quán)之后,授權(quán)服務(wù)器頒發(fā)授權(quán)令牌(Access Token)給客戶端。
客戶端(client):如新浪微博客戶端weico、微格等第三方應(yīng)用,也可以是它自己的官方應(yīng)用;其本身不存儲(chǔ)資源,而是資源擁有者授權(quán)通過后,使用它的授權(quán)(授權(quán)令牌)訪問受保護(hù)資源,然后客戶端把相應(yīng)的數(shù)據(jù)展示出來/提交到服務(wù)器?!翱蛻舳恕毙g(shù)語不代表任何特定實(shí)現(xiàn)(如應(yīng)用運(yùn)行在一臺(tái)服務(wù)器、桌面、手機(jī)或其他設(shè)備)。
認(rèn)證服務(wù)流程
1、首先通過如http://localhost:8080/chapter17-server/authorize
?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login訪問授權(quán)頁面;2、該控制器首先檢查clientId是否正確;如果錯(cuò)誤將返回相應(yīng)的錯(cuò)誤信息;
3、然后判斷用戶是否登錄了,如果沒有登錄首先到登錄頁面登錄;
4、登錄成功后生成相應(yīng)的auth code即授權(quán)碼,然后重定向到客戶端地址,如http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dff68122f4f00ae995da0ed;在重定向到的地址中會(huì)帶上code參數(shù)(授權(quán)碼),接著客戶端可以根據(jù)授權(quán)碼去換取access token。
資源服務(wù)流程
- 1、首先通過如http://localhost:8080/chapter17-server/userInfo? access_token=828beda907066d058584f37bcfd597b6進(jìn)行訪問;
- 2、該控制器會(huì)驗(yàn)證access token的有效性;如果無效了將返回相應(yīng)的錯(cuò)誤,客戶端再重新進(jìn)行授權(quán);
- 3、如果有效,則返回當(dāng)前登錄用戶的用戶名。
客戶端
- OAuth2AuthenticationFilter。 該filter的作用類似于FormAuthenticationFilter用于oauth2客戶端的身份驗(yàn)證控制;如果當(dāng)前用戶還沒有身份驗(yàn)證,首先會(huì)判斷url中是否有code(服務(wù)端返回的auth code),如果沒有則重定向到服務(wù)端進(jìn)行登錄并授權(quán),然后返回auth code;接著OAuth2AuthenticationFilter會(huì)用auth code創(chuàng)建OAuth2Token,然后提交給Subject.login進(jìn)行登錄;接著OAuth2Realm會(huì)根據(jù)OAuth2Token進(jìn)行相應(yīng)的登錄邏輯。
- 1、首先判斷有沒有服務(wù)端返回的error參數(shù),如果有則直接重定向到失敗頁面;
- 2、接著如果用戶還沒有身份驗(yàn)證,判斷是否有auth code參數(shù)(即是不是服務(wù)端授權(quán)之后返回的),如果沒有則重定向到服務(wù)端進(jìn)行授權(quán);
- 3、否則調(diào)用executeLogin進(jìn)行登錄,通過auth code創(chuàng)建OAuth2Token提交給Subject進(jìn)行登錄;
- 4、登錄成功將回調(diào)onLoginSuccess方法重定向到成功頁面;
- 5、登錄失敗則回調(diào)onLoginFailure重定向到失敗頁面。
OAuth2Realm
- 此Realm首先只支持OAuth2Token類型的Token;然后通過傳入的auth code去換取access token;再根據(jù)access token去獲取用戶信息(用戶名),然后根據(jù)此信息創(chuàng)建AuthenticationInfo;如果需要AuthorizationInfo信息,可以根據(jù)此處獲取的用戶名再根據(jù)自己的業(yè)務(wù)規(guī)則去獲取。
總結(jié)
- 認(rèn)證服務(wù)提供登錄功能,返回authCode
- 客戶端的Realm通過authCode去認(rèn)證服務(wù)器獲取accessToken
- 用accessToken訪問資源服務(wù)器中的資源,獲取用戶信息(這步是不是可以換成從token中提取用戶名,這樣就不必要調(diào)用資源服務(wù)接口了)
- 拿到用戶信息后客戶端shiro做登錄操作,本地處理角色等
JWT
下面介紹一下無狀態(tài)的,基于jwt的認(rèn)證處理方式。
比如使用了oauth2,得到了一個(gè)accessToken,就當(dāng)作是jwt,里邊包含了用戶名。然后沒有資源服務(wù)提供什么用戶信息接口了。
有狀態(tài)方式
- 有個(gè)jwt,每次請(qǐng)求驗(yàn)證這個(gè)jwt(可能有緩存,每次不需要真正執(zhí)行查詢等操作)
- 驗(yàn)證之后,存到session中一些東西,以后會(huì)用
無狀態(tài)方式
- 有個(gè)jwt,每次請(qǐng)求驗(yàn)證jwt,從中獲取用戶名,角色等,這些都在jwt中本身包含(也可能有緩存和上邊是一樣的)
- 每次驗(yàn)證,可以刷新token有效期
- 可以使用redis等存儲(chǔ)jwt用來驗(yàn)證