Apache Shiro 是一個框架,可用於身份驗證和受權。雖然這兩個術語表明的是不一樣的含義,但出於它們在應用程序安全性方面各自的角色考慮,它們有時會被交換使用。
身份驗證 指的是驗證用戶的身份。在驗證用戶身份時,須要確認用戶的身份的確如他們所聲稱的那樣。在大多數應用程序中,身份驗證是經過用戶名和密碼的組合完成的。只要用戶選擇了他人很難猜到的密碼,那麼用戶名和密碼的組合一般就足以確立身份。可是,還有其餘的身份驗證方式可用,好比指紋、證書和生成鍵。
一旦身份驗證過程成功地創建起身份,受權 就會接管以便進行訪問的限制或容許。 因此,有這樣的可能性:用戶雖然經過了身份驗證能夠登陸到一個系統,可是未通過受權,不許作任何事情。還有一種多是用戶雖然具備了某種程度的受權,卻並未通過身份驗證。
在爲應用程序規劃安全性模型時,必須處理好這兩個元素以確保系統具備足夠的安全性。身份驗證是應用程序常見的問題(特別是在只有用戶和密碼組合的狀況下),因此讓框架來處理這項工做是一個很好的作法。合理的框架可提供通過測試和維護的優點,讓您能夠集中精力處理業務問題,而不是解決其解決方案已經實現的問題。
Apache Shiro 提供了一個可用的安全性框架,各類客戶機均可將這個框架應用於它們的應用程序。本文中的這些例子旨在介紹 Shiro 並着重展現對用戶進行身份驗證的基本任務。
本文只針對Jeesite中shiro的用法進行整理,不會包括shiro環境配置和搭建等內容。java
spring-context-shiro.xml是shiro的主配置文件,配置信息是重點主要是安全認證過濾器的配置。它規定哪些url須要進行哪些方面的認證和過濾web
<!-- 安全認證過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="p" value="${adminPath}/login" /> <property name="successUrl" value="${adminPath}" /> <property name="filters"> <map> <entry key="authc" value-ref="formAuthenticationFilter"/> </map> </property> <property name="filterChainDefinitions"> <value> /static/** = anon /userfiles/** = anon ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/** = user </value> </property> </bean>
shiroFilter是shiro的安全認證過濾器,其中,spring
securityManager:指定一個負責管理的bean,這個新的bean在接下來會定義,其中包含了認證的主要邏輯。數據庫
loginUrl:沒有登陸的用戶請求須要登陸的頁面時自動跳轉到登陸頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面。apache
successUrl:登陸成功默認跳轉頁面,不配置則跳轉至」/」。若是登錄前點擊的一個須要登陸的頁面,則在登陸自動跳轉到那個須要登陸的頁面。不跳轉到此。安全
unauthorizedUrl:沒有權限默認跳轉的頁面。session
map中的entry指定了authc權限所對應的過濾器實體框架
而屬性中的filterChainDefinitions則詳細規定啦不一樣的url的對應權限jsp
anon:例子/admins/**=anon 沒有參數,表示能夠匿名使用。ide
authc:例如/admins/user/**=authc表示須要認證(登陸)才能使用,沒有參數
roles:例子/admins/user/=roles[admin],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,當有多個參數時,例如admins/user/=roles["admin,guest"],每一個參數經過纔算經過,至關於hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,例如/admins/user/=perms["user:add:,user:modify:*"],當有多個參數時必須每一個參數都經過才經過,想當於isPermitedAll()方法。
rest:例子/admins/user/=rest[user],根據請求的方法,至關於/admins/user/=perms[user:method] ,其中method爲post,get,delete等。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裏port的端口,queryString
是你訪問的url裏的?後面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議爲https
user:例如/admins/user/**=user沒有參數表示必須存在用戶,當登入操做時不作檢查
注:anon,authcBasic,auchc,user是認證過濾器,perms,roles,ssl,rest,port是受權過濾器
<!-- 定義 Shiro 主要業務對象 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- <property name="sessionManager" ref="sessionManager" /> --> <property name="realm" ref="systemAuthorizingRealm" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean>
這部分代碼定義了securitymanager的主要屬性的實體,systemAuthorizingRealm和shiroCacheManager都是本身實現的,分別用於進行驗證管理和cache管理。從配置文件能看出,配置文件指定了做爲安全邏輯的幾個結構,除了這兩部分,還包括formAuthenticationFilter。其中realm和filter在com.thinkgem.jeesite.modules.sys.security包裏實現。
從可見都邏輯上講,FormAuthenticationFilter類是用戶驗證時所接觸的第一個類。
當用戶登陸任意界面時,shiro會對當前狀態進行檢查。若是發現須要登陸,則會自動跳轉到配置文件裏loginUrl屬性所指定的url中。
而這一url又被指定爲authc權限,即須要驗證。接着,authc的filter被指定爲formAuthenticationFilter,所以login頁面所提交的信息被改filter截獲進行處理。
其中的核心邏輯是createToken函數:
protected AuthenticationToken createToken(S2ervletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); if (password==null){ password = ""; } boolean rememberMe = isRememberMe(request); String host = getHost(request); String captcha = getCaptcha(request); return new U
UsernamePasswordToken是security包裏的第四個類,它繼承自shiro的同名類,用於shiro處理中的參數傳遞。createtoken函數接受到login網頁所接受的表單,生成一個token傳給下一個類處理。
函數中的rememberme以及captcha分別表示的是記住用戶功能和驗證碼功能,這部分也是shiro自身攜帶,咱們並不須要修改。filter是經過aop的方式結合到系統裏的,所以並無具體的接口實現。
shiro的最終處理都將交給Real進行處理。由於在Shiro中,最終是經過Realm來獲取應用程序中的用戶、角色及權限信息的。一般狀況下,在Realm中會直接從咱們的數據源中獲取Shiro須要的驗證信息。能夠說,Realm是專用於安全框架的DAO.
Realm中有個參數是systemService,這個即是spring的具體業務邏輯,其中也包含了具體的DAO,正是在這個部分,shiro與spring的接口具體的結合了起來。
realm當中有兩個函數特別重要,分別是用戶認證函數和受權函數。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){ // 判斷驗證碼 Session session = SecurityUtils.getSubject().getSession(); String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE); if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){ throw new CaptchaException("驗證碼錯誤."); } } User user = getSystemService().getUserByLoginName(token.getUsername()); if (user != null) { byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16)); return new SimpleAuthenticationInfo(new Principal(user), user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName()); } else { return null; } }
以上即是用戶認證函數,中間關於驗證碼檢測的部分咱們暫且不表,其最核心的語句是
函數的參數AuthenticationToken即是filter所截獲並生成的token實例,其內容是login表單裏的內容,一般是用戶名和密碼。在前一句話裏,getSystemService取到了systemService實例,該實例中又含有配置好的userDAO,可以直接與數據庫交互。所以將用戶id傳入,取出數據庫裏所存有的user實例,接着在SimpleAuthenticationInfo生成過程當中分別以user實例的用戶名密碼,以及token裏的用戶名密碼作爲參數,驗證密碼是否相同,以達到驗證的目的。
doGetAuthorizationInfo函數是受權的函數,其具體的權限是在實現類中以annotation的形式指派的,它負責驗證用戶是否有權限訪問。詳細的細節容我以後添加。
LoginController是本該實現登陸認證的部分。因爲shiro的引入和AOP的使用,jeesite中的LoginController只處理驗證以後的部分。
若是經過驗證,系統中存在user實例,則返回對應的主頁。不然從新定位於login頁面。
經常使用的annotation主要以下:
@RequiresAuthentication
要求當前Subject 已經在當前的session 中被驗證經過才能被註解的類/實例/方法訪問或調用。
驗證用戶是否登陸,等同於方法subject.isAuthenticated() 結果爲true時。
@RequiresUser
須要當前的Subject 是一個應用程序用戶才能被註解的類/實例/方法訪問或調用。要麼是經過驗證被確認,或者在以前session 中的'RememberMe'服務被記住。
驗證用戶是否被記憶,user有兩種含義:一種是成功登陸的(subject.isAuthenticated() 結果爲true);另一種是被記憶的(subject.isRemembered()結果爲true)。
@RequiresGuest
要求當前的Subject 是一個「guest」,也就是他們必須是在以前的session中沒有被驗證或記住才能被註解的類/實例/方法訪問或調用。
驗證是不是一個guest的請求,與@RequiresUser徹底相反。
換言之,RequiresUser == !RequiresGuest。此時subject.getPrincipal() 結果爲null.
@RequiresRoles
要求當前的Subject 擁有全部指定的角色。若是他們沒有,則該方法將不會被執行,並且AuthorizationException 異常將會被拋出。例如:@RequiresRoles("administrator")
或者@RequiresRoles("aRoleName");
void someMethod();
若是subject中有aRoleName角色才能夠訪問方法someMethod。若是沒有這個權限則會拋出異常AuthorizationException。
@RequiresPermissions 要求當前的Subject 被容許一個或多個權限,以便執行註解的方法,好比: @RequiresPermissions("account:create") 或者@RequiresPermissions({"file:read", "write:aFile.txt"} ) void someMethod();