天天看點

Jeesite中shiro的用法講解

    Apache Shiro 是一個架構,可用于身份驗證和授權。雖然這兩個術語代表的是不同的含義,但出于它們在應用程式安全性方面各自的角色考慮,它們有時會被交換使用。

身份驗證 指的是驗證使用者的身份。在驗證使用者身份時,需要确認使用者的身份的确如他們所聲稱的那樣。在大多數應用程式中,身份驗證是通過使用者名和密碼的組合完成的。隻要使用者選擇了他人很難猜到的密碼,那麼使用者名和密碼的組合通常就足以确立身份。但是,還有其他的身份驗證方式可用,比如指紋、證書和生成鍵。

    一旦身份驗證過程成功地建立起身份,授權 就會接管以便進行通路的限制或允許。 是以,有這樣的可能性:使用者雖然通過了身份驗證可以登入到一個系統,但是未經過授權,不準做任何事情。還有一種可能是使用者雖然具有了某種程度的授權,卻并未經過身份驗證。

在為應用程式規劃安全性模型時,必須處理好這兩個元素以確定系統具有足夠的安全性。身份驗證是應用程式常見的問題(特别是在隻有使用者和密碼組合的情況下),是以讓架構來處理這項工作是一個很好的做法。合理的架構可提供經過測試和維護的優勢,讓您可以集中精力處理業務問題,而不是解決其解決方案已經實作的問題。

    Apache Shiro 提供了一個可用的安全性架構,各種客戶機都可将這個架構應用于它們的應用程式。本文中的這些例子旨在介紹 Shiro 并着重展示對使用者進行身份驗證的基本任務。

本文隻針對Jeesite中shiro的用法進行整理,不會包括shiro環境配置和搭建等内容。

    spring-context-shiro.xml是shiro的主配置檔案,配置資訊是重點主要是安全認證過濾器的配置。它規定哪些url需要進行哪些方面的認證和過濾

<code>&lt;!-- 安全認證過濾器 --&gt;</code>

<code>&lt;bean id=</code><code>"shiroFilter"</code> <code>class</code><code>=</code><code>"org.apache.shiro.spring.web.ShiroFilterFactoryBean"</code><code>&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"securityManager"</code> <code>ref=</code><code>"securityManager"</code> <code>/&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"p"</code> <code>value=</code><code>"${adminPath}/login"</code> <code>/&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"successUrl"</code> <code>value=</code><code>"${adminPath}"</code> <code>/&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"filters"</code><code>&gt;</code>

<code>        </code><code>&lt;map&gt;</code>

<code>            </code><code>&lt;entry key=</code><code>"authc"</code> <code>value-ref=</code><code>"formAuthenticationFilter"</code><code>/&gt;</code>

<code>        </code><code>&lt;/map&gt;</code>

<code>    </code><code>&lt;/property&gt;</code>

<code>    </code><code>&lt;property name=</code><code>"filterChainDefinitions"</code><code>&gt;</code>

<code>        </code><code>&lt;value&gt;</code>

<code>            </code><code>/</code><code>static</code><code>/** = anon</code>

<code>            </code><code>/userfiles/** = anon</code>

<code>            </code><code>${adminPath}/login = authc</code>

<code>            </code><code>${adminPath}/logout = logout</code>

<code>            </code><code>${adminPath}/** = user</code>

<code>        </code><code>&lt;/value&gt;</code>

<code>&lt;/bean&gt;</code>

    shiroFilter是shiro的安全認證過濾器,其中,

securityManager:指定一個負責管理的bean,這個新的bean在接下來會定義,其中包含了認證的主要邏輯。

loginUrl:沒有登入的使用者請求需要登入的頁面時自動跳轉到登入頁面,不是必須的屬性,不輸入位址的話會自動尋找項目web項目的根目錄下的”/login.jsp”頁面。

successUrl:登入成功預設跳轉頁面,不配置則跳轉至”/”。如果登陸前點選的一個需要登入的頁面,則在登入自動跳轉到那個需要登入的頁面。不跳轉到此。

unauthorizedUrl:沒有權限預設跳轉的頁面。

map中的entry指定了authc權限所對應的過濾器實體

而屬性中的filterChainDefinitions則詳細規定啦不同的url的對應權限

anon:例子/admins/**=anon 沒有參數,表示可以匿名使用。

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是授權過濾器

<code>&lt;!-- 定義 Shiro 主要業務對象 --&gt;</code>

<code>&lt;bean id=</code><code>"securityManager"</code> 

<code>class</code><code>=</code><code>"org.apache.shiro.web.mgt.DefaultWebSecurityManager"</code><code>&gt;</code>

<code>&lt;!-- &lt;property name=</code><code>"sessionManager"</code> <code>ref=</code><code>"sessionManager"</code> <code>/&gt; --&gt;</code>

<code>&lt;property name=</code><code>"realm"</code> <code>ref=</code><code>"systemAuthorizingRealm"</code> <code>/&gt;</code>

<code>&lt;property name=</code><code>"cacheManager"</code> <code>ref=</code><code>"shiroCacheManager"</code> <code>/&gt;</code>

    這部分代碼定義了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函數:

<code>protected</code> <code>AuthenticationToken createToken(S2ervletRequest request, ServletResponse response) {</code>

<code>    </code><code>String username = getUsername(request);</code>

<code>    </code><code>String password = getPassword(request);</code>

<code>    </code><code>if</code> <code>(password==</code><code>null</code><code>){</code>

<code>        </code><code>password = </code><code>""</code><code>;</code>

<code>    </code><code>}</code>

<code>    </code><code>boolean</code> <code>rememberMe = isRememberMe(request);</code>

<code>    </code><code>String host = getHost(request);</code>

<code>    </code><code>String captcha = getCaptcha(request);</code>

<code>    </code><code>return</code> <code>new</code> <code>U</code>

    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當中有兩個函數特别重要,分别是使用者認證函數和授權函數。

<code>protected</code> <code>AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) </code><code>throws</code> <code>AuthenticationException {</code>

<code>    </code><code>UsernamePasswordToken token = (UsernamePasswordToken) authcToken;</code>

<code>     </code> 

<code>    </code><code>if</code> <code>(LoginController.isValidateCodeLogin(token.getUsername(), </code><code>false</code><code>, </code><code>false</code><code>)){</code>

<code>        </code><code>// 判斷驗證碼</code>

<code>        </code><code>Session session = SecurityUtils.getSubject().getSession();</code>

<code>        </code><code>String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE);</code>

<code>        </code><code>if</code> <code>(token.getCaptcha() == </code><code>null</code> <code>|| !token.getCaptcha().toUpperCase().equals(code)){</code>

<code>            </code><code>throw</code> <code>new</code> <code>CaptchaException(</code><code>"驗證碼錯誤."</code><code>);</code>

<code>        </code><code>}</code>

<code> </code> 

<code>    </code><code>User user = getSystemService().getUserByLoginName(token.getUsername());</code>

<code>    </code><code>if</code> <code>(user != </code><code>null</code><code>) {</code>

<code>        </code><code>byte</code><code>[] salt = Encodes.decodeHex(user.getPassword().substring(</code><code>0</code><code>,</code><code>16</code><code>));</code>

<code>        </code><code>return</code> <code>new</code> <code>SimpleAuthenticationInfo(</code><code>new</code> <code>Principal(user), </code>

<code>                </code><code>user.getPassword().substring(</code><code>16</code><code>), ByteSource.Util.bytes(salt), getName());</code>

<code>    </code><code>} </code><code>else</code> <code>{</code>

<code>        </code><code>return</code> <code>null</code><code>;</code>

<code>}</code>

    以上便是使用者認證函數,中間關于驗證碼檢測的部分我們暫且不表,其最核心的語句是

<code>&lt;br data-filtered=</code><code>"filtered"</code><code>&gt;</code>

    函數的參數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();本文轉自 Y幕徐 51CTO部落格,原文連結:http://blog.51cto.com/765133133/1947716