天天看點

shiro将session認證改成token認證_源碼分析shiro認證授權流程

shiro将session認證改成token認證_源碼分析shiro認證授權流程

1. shiro介紹

Apache Shiro是一個強大易用的Java安全架構,提供了認證、授權、加密和會話管理等功能:

  • 認證 - 使用者身份識别,常被稱為使用者“登入”;
  • 授權 - 通路控制;
  • 密碼加密 - 保護或隐藏資料防止被偷窺;
  • 會話管理 - 每使用者相關的時間敏感的狀态。

對于任何一個應用程式,Shiro都可以提供全面的安全管理服務。并且相對于其他安全架構,Shiro要簡單的多。

2. shiro源碼概況

先要了解shiro的基本架構(見http://www.cnblogs.com/davidwang456/p/4425145.html)。

然後看一下各個元件之間的關系:

shiro将session認證改成token認證_源碼分析shiro認證授權流程

一下内容參考:http://kdboy.iteye.com/blog/1154644

Subject:即“目前操作使用者”。但是,在Shiro中,Subject這一概念并不僅僅指人,也可以是第三方程序、背景帳戶(Daemon Account)或其他類似事物。它僅僅意味着“目前跟軟體互動的東西”。但考慮到大多數目的和用途,你可以把它認為是Shiro的“使用者”概念。

Subject代表了目前使用者的安全操作,SecurityManager則管理所有使用者的安全操作。

SecurityManager:它是Shiro架構的核心,典型的Facade模式,Shiro通過SecurityManager來管理内部元件執行個體,并通過它來提供安全管理的各種服務。

Realm: Realm充當了Shiro與應用安全資料間的“橋梁”或者“連接配接器”。也就是說,當對使用者執行認證(登入)和授權(通路控制)驗證時,Shiro會從應用配置的Realm中查找使用者及其權限資訊。

從這個意義上講,Realm實質上是一個安全相關的DAO:它封裝了資料源的連接配接細節,并在需要時将相關資料提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用于認證和(或)授權。配置多個Realm是可以的,但是至少需要一個。

Shiro内置了可以連接配接大量安全資料源(又名目錄)的Realm,如LDAP、關系資料庫(JDBC)、類似INI的文本配置資源以及屬性檔案等。如果預設的Realm不能滿足需求,你還可以插入代表自定義資料源的自己的Realm實作。

Shiro主要元件還包括:

Authenticator :認證就是核實使用者身份的過程。這個過程的常見例子是大家都熟悉的“使用者/密碼”組合。多數使用者在登入軟體系統時,通常提供自己的使用者名(當事人)和支援他們的密碼(證書)。如果存儲在系統中的密碼(或密碼表示)與使用者提供的比對,他們就被認為通過認證。

Authorizer :授權實質上就是通路控制 - 控制使用者能夠通路應用中的哪些内容,比如資源、Web頁面等等。

SessionManager :在安全架構領域,Apache Shiro提供了一些獨特的東西:可在任何應用或架構層一緻地使用Session API。即,Shiro為任何應用提供了一個會話程式設計範式 - 從小型背景獨立應用到大型叢集Web應用。這意味着,那些希望使用會話的應用開發者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用這些容器,開發者現在也可以選擇使用在任何層統一一緻的會話API,取代Servlet或EJB機制。

CacheManager :對Shiro的其他元件提供緩存支援。

3. 做一個demo,跑shiro的源碼,從login開始:

第一步:使用者根據表單資訊填寫使用者名和密碼,然後調用登陸按鈕。内部執行如下:

UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getUsername(), loginForm.getPassphrase()); token.setRememberMe(true); Subject currentUser = SecurityUtils.getSubject(); currentUser.login(token);
           

第二步:代理DelegatingSubject繼承Subject執行login

public void login(AuthenticationToken token) throws AuthenticationException { clearRunAsIdentitiesInternal(); Subject subject = securityManager.login(this, token); PrincipalCollection principals; String host = null; if (subject instanceof DelegatingSubject) { DelegatingSubject delegating = (DelegatingSubject) subject; //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals: principals = delegating.principals; host = delegating.host; } else { principals = subject.getPrincipals(); } if (principals == null || principals.isEmpty()) { String msg = "Principals returned from securityManager.login( token ) returned a null or " + "empty value. This value must be non null and populated with one or more elements."; throw new IllegalStateException(msg); } this.principals = principals; this.authenticated = true; if (token instanceof HostAuthenticationToken) { host = ((HostAuthenticationToken) token).getHost(); } if (host != null) { this.host = host; } Session session = subject.getSession(false); if (session != null) { this.session = decorate(session); } else { this.session = null; } }
           

第三步:調用DefaultSecurityManager繼承SessionsSecurityManager執行login方法

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = authenticate(token); } catch (AuthenticationException ae) { try { onFailedLogin(token, ae, subject); } catch (Exception e) { if (log.isInfoEnabled()) { log.info("onFailedLogin method threw an " + "exception. Logging and propagating original AuthenticationException.