2.shiro工作原理(以集成springboot为例)
Shiro 提供了與 Web 集成的支持,其通過一個 ShiroFilter 入口來攔截需要安全控制的URL,然后進行相應的控制
ShiroFilter 類似于如 Strut2/SpringMVC 這種 web 框架的前端控制器,是安全控制的入口點,其負責讀取配置(如ini 配置文件;springboot可使用注解配置類),然后判斷URL 是否需要登錄/權限等工作。
一、攔截請求鏈接
1.shiro提供了一系列的鏈接過濾器:
注:過濾器一般實現org.apache.shiro.web.filter.authc.AuthenticatingFilter類
2.注入Shiro攔截器工廠類(ShiroFilterFactoryBean),配置鏈接
攔截器類入口方法是createInstance(),該類的主要作用是:一、 創建了FilterChainManager,即過濾器管理類,包括2個重要屬性
1.1 filters:管理全部鏈接過濾器,包括身份驗證的過濾器,有anon,authcBasic,auchc,user和權限驗證的過濾器,有perms,roles,ssl,rest,port。同時自定的過濾器也在FilterChainManager里。值得注意的是,過濾器都是單例的。
1.2 filterChains:過濾鏈。是一個Map對象,其中key為請求的url,value是一個NamedFilterList對象,存放與該url對應的一系列過濾器
二、將過濾器管理類設置到PathMatchingFilterChainResolver類里,該類負責路徑和過濾器鏈的解析與匹配。根據url找到過濾器鏈。
@Configuration public class ShiroConfig {/*** ShiroFilterFactoryBean 處理攔截資源文件問題。* 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager* Filter Chain定義說明 1、一個URL可以配置多個Filter,使用逗號分隔* 2、當設置多個過濾器時,全部驗證通過,才視為通過* 3、部分過濾器可指定參數,如perms,roles*/@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必須設置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);shiroFilterFactoryBean.setLoginUrl("/toLogin");// 登錄成功后要跳轉的鏈接shiroFilterFactoryBean.setSuccessUrl("/Home");// 未授權界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");/*** 攔截器.* 定義shiro過濾鏈 Map結構* Map中key(xml中是指value值)*/Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置不會被攔截的鏈接 順序判斷,優先匹配filterChainDefinitionMap.put("/static/**", "anon");//配置需要認證才能訪問的鏈接filterChainDefinitionMap.put("/**", "authc");// 配置退出過濾器filterChainDefinitionMap.put("/logout", "logout");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);}}3.請求鏈接解析
Shiro會替代org.springframework.web.filter.DelegatingFilterProxy來實現動態代理。DelegatingFilterProxy過濾器的代理類會實現攔截請求,任何請求都會先經過shiro先過濾,直到成功才會執行javaweb本身的過濾器。源碼級講解。
二、登錄認證
1、首先調用Subject.login(token) 進行登錄,其會自動委托給SecurityManager
2、SecurityManager負責真正的身份驗證邏輯;它會委托給Authenticator 進行身份驗證;SecurityManager接口繼承了Authenticator,另外還有一個ModularRealmAuthenticator實現,其委托給多個Realm 進行驗證,驗證規則通過AuthenticationStrategy接口指定
3、Authenticator 才是真正的身份驗證者,ShiroAPI 中核心的身份認證入口點,此處可以自定義插入自己的實現;Authenticator 的職責是驗證用戶帳號,是ShiroAPI 中身份驗證核心的入口點:如果驗證成功,將返回AuthenticationInfo驗證信息;此信息中包含了身份及憑證;如果驗證失敗將拋出相應的AuthenticationException異常
4、Authenticator 可能會委托給相應的AuthenticationStrategy進行多Realm 身份驗證,默認ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm 身份驗證;
5、Authenticator 會把相應的token 傳入Realm,從Realm 獲取身份驗證信息,如果沒有返回/拋出異常表示身份驗證失敗了。此處可以配置多個Realm,將按照相應的順序及策略進行訪問。
1.認證思路
程序先獲當前用戶的Subject對象,然后判斷用戶是否已經登錄,如果登錄則不用做認證,若沒有登錄,則創建 UsernamePasswordToken對象,將用戶名密碼傳入Subject 的login對象進行檢驗。
@RequestMapping("/toLogin")public String loginLogin(Model model, String username, String password, HttpSession userSession) {// 判斷用戶名和密碼是否為空if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {// 用戶名或者密碼為空model.addAttribute("errorInfo", "用戶名或者密碼為空");return "/login";}//通過subject進行登錄操作Subject subject = SecurityUtils.getSubject();if(!subject.isAuthenticated()){//創建封裝了用戶名和密碼的UsernamePasswordToken對象UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {subject.login(token);User user = (User) subject.getPrincipal();subject.getSession().setAttribute("user", user);subject.getSession().setAttribute("userRole", userRole);return "redirect:/Home";} catch (AuthenticationException e) {e.printStackTrace(); //打印異常錯誤// 用戶名或者密碼為空model.addAttribute("errorInfo", "用戶名或者密碼不正確");return "/login";}}else return "redirect:/Home";}2.深入探究
之前的簡介中,已經知道了Realm就是shiro與數據庫打交道的對象。
Shiro 從 Realm 獲取安全數據(如用戶、角色、 權限),即 SecurityManager 要驗證用戶身份,那么它需要從 Realm 獲取相應的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作。
先講Realm的認證方面:
簡單地說:**subject.login(token);**這句代碼調用到最后,就是調用AuthenticationRealm抽象類中的抽象方法doGetAuthenticationInfo方法,doGetAuthenticationInfo方法會返回SimpleAuthenticationInfo對象,源碼級講解。而該方法就是上面的校驗過程中,實現認證的自定義Realm需要實現的方法。我們可以通過繼承該類,實現該方法達到自定義。
值得注意的是: 一般繼承 AuthorizingRealm(授權)即可;其繼承了 AuthenticatingRealm(即身份驗證),而且也間接繼承了 CachingRealm(帶有緩存實現)
還有一點要強調的就是,shiro如何完成密碼的比對?
我們知道此時,保存有用戶信息的有UsernamePasswordToken和SimpleAuthenticationInfo兩個對象,shiro肯定會去取出這兩個對象中的信息進行比對。
簡單地說:密碼的具體比對工作是我們自定義的繼承了AuthenticatingRealm父類的自定義
Realm類調用CredentialsMatcher的doCredentialsMatch方法完成的。源碼級講解。
3.多Realm認證
場景:假設某需求涉及使用兩個角色分別是:學生、教師。要兩者實現分開登錄。即需要兩個個Realm——StudentRealm和TeacherRealm,分別處理學生、教師的驗證功能。
分析:正常情況下,當定義了多個Realm,無論是學生登錄,教師登錄,都會由這兩個Realm共同處理。因為當配置了多個Realm時,我們通常使用的認證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用哪個Realm的是doAuthenticate()方法,該方法中通過getRealms()獲取Realm集合,如果realm只有一個,執行的是doSingleRealmAuthentication方法,如果有多個,走的是doMultiRealmAuthentication方法。所以當我們使用ModularRealmAuthenticator類來配置多個Realm的時候,Shiro會使用我們配置的多個Realm進行認證。
補充:modularRealmAuthenticator是shiro提供的realm管理器,在這里可以設置realm的生效。通過setAuthenticationStrategy來設置多realm的使用規則。如果想自己進一步控制多realm,可以自己實現ModularRealmAuthenticator 。
實現方法:創建一個org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類,并重寫doAuthenticate()方法,讓特定的Realm完成特定的功能。
1.通過創建一個org.apache.shiro.authc.UsernamePasswordToken的子類,在其中添加一個字段loginType,用來標識登錄的類型,即是學生登錄、教師登錄。
enum LoginType {STUDENT("Student"), TEACHER("Teacher")private String typeprivate LoginType(String type) {this.type = type}@Overridepublic String toString() {return this.type.toString()} }2.新建org.apache.shiro.authc.UsernamePasswordToken的子類UserToken
import org.apache.shiro.authc.UsernamePasswordToken class UserToken extends UsernamePasswordToken {//登錄類型,判斷是學生登錄,教師登錄private String loginTypepublic UserToken(final String username, final String password,String loginType) {super(username,password)this.loginType = loginType}public String getLoginType() {return loginType}public void setLoginType(String loginType) {this.loginType = loginType} }3.新建org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類UserModularRealmAuthenticator:
/*** 當配置了多個Realm時,我們通常使用的認證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法** 自定義Authenticator* 注意,當需要分別定義處理學生和教師和管理員驗證的Realm時,對應Realm的全類名應該包含字符串“Student”“Teacher”。* 并且,他們不能相互包含,例如,處理學生驗證的Realm的全類名中不應該包含字符串"Teacher"。*/ class UserModularRealmAuthenticator extends ModularRealmAuthenticator {private static final Logger logger = LoggerFactory.getLogger(UserModularRealmAuthenticator.class)@Overrideprotected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {logger.info("UserModularRealmAuthenticator:method doAuthenticate() execute ")// 判斷getRealms()是否返回為空assertRealmsConfigured()// 強制轉換回自定義的CustomizedTokenUserToken userToken = (UserToken) authenticationToken// 登錄類型String loginType = userToken?.getLoginType()// 所有RealmCollection<Realm> realms = getRealms()// 登錄類型對應的所有RealmCollection<Realm> typeRealms = new ArrayList<>()for (Realm realm : realms) {if (realm?.getName()?.contains(loginType))typeRealms?.add(realm)}// 判斷是單Realm還是多Realmif (typeRealms?.size() == 1){logger.info("doSingleRealmAuthentication() execute ")return doSingleRealmAuthentication(typeRealms?.get(0), userToken)}else{logger.info("doMultiRealmAuthentication() execute ")return doMultiRealmAuthentication(typeRealms, userToken)}} }4.創建分別處理學生登錄和教師登錄的StudentShiroRealm,TeacherShiroRealm (即自定義 Realm,這里自行編寫代碼):
5.在ShiroConfig類中的SecurityManager方法進行相應的配置,當然,以下只是ShiroConfig類中的少部分配置,還有屬性的配置沒有展示出來。
更多關于多Realm認證的細節,可以參考這位博主的文章。
三、授權(以注解為例)
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表示授權失敗。
1.開啟授權注解使用方式,在shiroconfig類中:
2.在自定義的realm中實現授權方法
public class AuthRealm extends AuthorizingRealm{@Autowiredprivate UserService userService;@Override//權限授權模塊protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {User user = (User) principals.getPrimaryPrincipal();List<String> listPermission = userService.findAdminRole(user.getUserId());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//授予admin權限info.addRole("admin");return info;}}3.編寫控制器(測試訪問’/user/query’有權限,訪問’/user/update’就沒有權限)
@Controller @RequestMapping("/user") public class UserController{@RequiresRoles(value = {admin})@RequestMapping("/query")public String query(){return "/user";}@RequiresRoles(value = {user})@RequestMapping("/update")public String query(){return "/user";} }未完待續。。。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的2.shiro工作原理(以集成springboot为例)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 订单结算中最佳优惠券组合推荐策略分析
- 下一篇: 2021新媒体内容生态数据报告