1. 程式人生 > >Shiro的認證原理(Subject#login的背後故事)

Shiro的認證原理(Subject#login的背後故事)

登入操作一般都是我們觸發的:

Subject subject = SecurityUtils.getSubject();
AuthenticationToken authenticationToken = new ...
subject.login(authenticationToken);

Subject的登入將委託給SecurityManager,SecurityManager的login方法實際上是產生了一個新的Subject,然後將相關屬性賦予當前呼叫者Subject:

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實現了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.", e);
            }
        }
        throw ae; //propagate
    }

    Subject loggedIn = createSubject(token, info, subject);

    onSuccessfulLogin(token, info, loggedIn);

    return loggedIn;
}

image

父類AuthenticatingSecurityManager實現authenticate方法:

public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {

    private Authenticator authenticator;

    public AuthenticatingSecurityManager() {
        super();
        this.authenticator = new ModularRealmAuthenticator();
    }

    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

    //......
}

利用一個ModularRealmAuthenticator型別的authenticator來實現:

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    assertRealmsConfigured();
    Collection<Realm> realms = getRealms();
    if (realms.size() == 1) {
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
    } else {
        return doMultiRealmAuthentication(realms, authenticationToken);
    }
}

然後根據realms集合是單個還是多個分別處理,最終無非是這樣:

AuthenticationInfo info = realm.getAuthenticationInfo(token);

父類AuthenticatingRealm的getAuthenticationInfo方法實現了info的獲取和身份的校驗,僅僅呼叫自己實現的realm的doGetAuthenticationInfo方法,初步驗證後構造一個SimpleAuthenticationInfo:

SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(principals, this.getName());
return new SimpleAuthenticationInfo(principalCollection, authenticationInfo.getCredentials());

AuthenticatingRealm的getAuthenticationInfo方法邏輯如下:

首先去快取找info:

AuthenticationInfo info = getCachedAuthenticationInfo(token);

快取沒有則呼叫子類實現的方法:

info = doGetAuthenticationInfo(token);

info不為null的時候就要驗證了(這裡還可以加密驗證):

assertCredentialsMatch(token, info);

兩次建立Subject

進入AbstractShiroFilter的時候,會預設建立一個Subject,這個是在Subject介面中的內部類實現的,但是同樣也是呼叫了DefaultSecurityManager中的createSubject方法:

public Subject createSubject(SubjectContext subjectContext) {
    //create a copy so we don't modify the argument's backing map:
    SubjectContext context = copy(subjectContext);

    //ensure that the context has a SecurityManager instance, and if not, add one:
    context = ensureSecurityManager(context);

    //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before
    //sending to the SubjectFactory.  The SubjectFactory should not need to know how to acquire sessions as the
    //process is often environment specific - better to shield the SF from these details:
    context = resolveSession(context);

    //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first
    //if possible before handing off to the SubjectFactory:
    context = resolvePrincipals(context);

    Subject subject = doCreateSubject(context);

    //save this subject for future reference if necessary:
    //(this is needed here in case rememberMe principals were resolved and they need to be stored in the
    //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation).
    //Added in 1.2:
    save(subject);

    return subject;
}

初次進入shiroFilter,會建立一個Subject,這個Subject沒有驗證通過,保留了三個屬性:request,response,securityManager。

當我們呼叫subject.login的時候,我們在DefaultSecurityManager中為subjectContext設定了相關屬性:

protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
    SubjectContext context = createSubjectContext();
    context.setAuthenticated(true);
    context.setAuthenticationToken(token);
    context.setAuthenticationInfo(info);
    if (existing != null) {
        context.setSubject(existing);
    }
    // 這個方法的具體實現在上面
    return createSubject(context);
}

一個save方法為我們構造了session:

save(subject);

這個session是根據我們的principals(放在登入成功返回的那個AuthenticationInfo中)構造的。

下一次進入的時候,依然是:

final Subject subject = createSubject(request, response);

但是下面的方法為我們找回了session,通過request.getSession(false)就可以取到。

context = resolveSession(context);

有了session後其他的東西都可以恢復,這樣就可以識別並維持一個subject的狀態,即使每次都重新建立了Subject物件。

具體是在DefaultWebSubjectFactory這個方法裡恢復的,並且通過構造器賦值給下一個新的subject了:

public Subject createSubject(SubjectContext context) {
    if (!(context instanceof WebSubjectContext)) {
        return super.createSubject(context);
    }
    WebSubjectContext wsc = (WebSubjectContext) context;
    SecurityManager securityManager = wsc.resolveSecurityManager();
    Session session = wsc.resolveSession();
    boolean sessionEnabled = wsc.isSessionCreationEnabled();
    PrincipalCollection principals = wsc.resolvePrincipals();
    boolean authenticated = wsc.resolveAuthenticated();
    String host = wsc.resolveHost();
    ServletRequest request = wsc.resolveServletRequest();
    ServletResponse response = wsc.resolveServletResponse();

    return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,
            request, response, securityManager);
}

舉兩個例子:

PrincipalCollection principals = wsc.resolvePrincipals();
-> principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);

boolean authenticated = wsc.resolveAuthenticated();
-> Session session = resolveSession();
if (session != null) {
    Boolean sessionAuthc = (Boolean) session.getAttribute(AUTHENTICATED_SESSION_KEY);
    authc = sessionAuthc != null && sessionAuthc;
}

通過上面兩個方法就恢復了Subject的兩個屬性,其實都是存放在session中。

其實只要你使用了Shiro,不管你是否登入,核心過濾器都會為我們構造Subject例項,當我們主動呼叫subject.login方法時,會間接呼叫我們自己實現的realm的doGetAuthenticationInfo,根據我們在資料庫中獲取的資訊(存放在info中)和呼叫login方法時傳遞的AuthenticationToken中的資訊對比。

當info為null或者丟擲了AuthenticationException異常,都視為登入失敗。

相關推薦

Shiro認證原理Subject#login背後故事

登入操作一般都是我們觸發的: Subject subject = SecurityUtils.getSubject(); AuthenticationToken authenticationToken = new ... subject.login(aut

Shiro認證-初學

1:前後端分離:更改以前的ajax請求,由於目前的專案基本上都是前後端分離的,所以我們所有的資料都已JSON格式返回給前端,對沒有登入的請求進行攔截,覆蓋掉shiro原本的跳轉到login.jsp頁面,繼承FormAuthenticationFilter public class AjaxPer

Apache Shiro學習----配置與SpringMVC集成

async 匹配 過濾 -i fig hit http struct 找到 1.web.xml文件中配置 <!--將shiro的配置文件交給Spring監聽器初始化--> <context-param> <param

圖解PCIE原理從軟件角度

配置 gist 分享圖片 iop 轉載 strong base byte bsp 1 PCIE基本概念 1.1 PCIE拓撲架構圖 1.2 PCIE Switch內部結構圖 1.3 PCIE協議結構圖 2 PCIE枚舉原理 2.1 T

Handler消息機制的一些原理直接用code講解——Android開發

over blog 線程 控件 android 開發 處理 發送消息 關聯 package com.example.handlertest; import android.os.Bundle; import android.os.Handler; import a

Mybatis工作原理含部分源碼

context off params 判斷 new trace app name res MyBatis的初始化 1、讀取配置文件,形成InputStream String resource = "mybatis.xml"; // 加載mybatis的配置文件(它也加載關

Django-元件--使用者認證Authauth_user增加欄位

引入: from django.db import models from django.contrib.auth.models import AbstractBaseUser 原始碼 : from django.contrib.auth.models import User 

Apache Kafka核心元件和流程-協調器消費者和組協調器-設計-原理入門教程輕鬆學

作者:稀有氣體 來源:CSDN 原文:https://blog.csdn.net/liyiming2017/article/details/82805479 版權宣告:本文為博主原創文章,轉載請附上博文連結! 本入門教程,涵蓋Kafka核心內容,通過例項和大量圖表,幫助學習

Java的跨平臺實現原理Write Once,Run Anywhere

Java的跨平臺實現原理 為什麼要跨平臺 在不同點作業系統之間,使用不同的指令集對計算機進行控制。如果沒有跨平臺,我們需要對window,Linux,unix等作業系統的指令集分別進行特定的語言開發 Java如何實現 在不同的作業系統之間,提供不同的虛擬機器,讓虛擬機器實

HashMap實現原理jdk1.7/jdk1.8

HashMap的底層實現:  1、簡單回答    JDK1.7:HashMap的底層實現是:陣列+連結串列  JDK1.8:HashMap的底層實現是:陣列+連結串列/紅黑樹     為什麼要紅黑樹?  紅黑樹:一個自平衡的二

一種異常值檢測方法、原理 基於箱線圖

先介紹使用到的方法原理,也就是一種異常檢測的方法。  首先要先了解箱線圖。 箱線圖 箱線圖(Boxplot)也稱箱須圖(Box-whisker Plot),是利用資料中的五個統計量:最小值、第一四分位數、中位數、第三四分位數與最大值來描述資料的一種方法,它也可以粗略地看

淺談C++多型實現原理虛繼承的奧祕

大夥都知道,如果要實現C++的多型,那麼,基類中相應的函式必須被宣告為虛擬函式(或純虛擬函式)。舉個例子: class Point { public: Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) { } virtual fl

中山大學路由器如何通過h3c認證上網極路由設定方法

我在前一陣子因為Linux系統難以用yah3c上網的關係,所以花了錢狠心地買了個極路由。本文所提及的安裝方法是在極路由的基礎下進行安裝的,若需要刷系統等,請自行尋找方法刷系統 1. 確認自己交了網費和netID已經啟用用網線可以正常上網 2. 按照極路由設定

Hibernate的工作原理優質,最全

從四個方面介紹Hibernate工作原理: 一、Hibernate如何連結資料庫? 配置檔案Hibernate.cfg.xml檔案中定義了和資料庫進行連線的資訊,包括資料庫方言,jdbc驅動,使用者名稱,密碼和URL等。Configuration類藉助dom4j的xml解

Apache Kafka 核心元件和流程-日誌管理器-設計-原理入門教程輕鬆學

本入門教程,涵蓋Kafka核心內容,通過例項和大量圖表,幫助學習者理解,任何問題歡迎留言。 目錄: 上一節介紹了協調器。協調器主要負責消費者和kafka叢集間的協調。那麼消費者消費時,如何定位訊息呢?訊息是如何儲存呢?本節將為

Apache Kafka 核心元件和流程-控制器-設計-原理入門教程輕鬆學

本入門教程,涵蓋Kafka核心內容,通過例項和大量圖表,幫助學習者理解,任何問題歡迎留言。 目錄: 通過前幾章的學習,我們已經從巨集觀層面瞭解了kafka的設計理念。包括kafka叢集的組成、訊息的主題、主題的分割槽、分割槽的

外掛化原理以DL框架說明

1. 外掛化原理 DL框架的原理很簡單: 在宿主apk中,有一個ProxyActivity,即代理Activity,這個Activity相當於一個空殼,外掛中的Activity依靠ProxyActivity來對生命週期回撥、資源載入以及啟動另一個Act

Apache Kafka-核心元件和流程-副本管理器-設計-原理入門教程輕鬆學

本入門教程,涵蓋Kafka核心內容,通過例項和大量圖表,幫助學習者理解,任何問題歡迎留言。 目錄: 本章簡單介紹了副本管理器,副本管理器負責分割槽及其副本的管理。副本管理器具體的工作流程可以參考牟大恩所著的《Kafka入門與實

詳解openstack命令啟動實現流程及原理nova --debug image-list

第二個引數就是entry_points.txt檔案group名稱 nova就是傳遞進來的引數,實際指向novaclient.shell模組的main函式 跟進程式碼: 上述程式碼從命令列接收引數,或者從環境變數中獲取引數值,進行驗證等操作。  nova --debug im

C語言一個真實的故事

曾經有一個簡單又不失牛逼的小技巧,老師教的時候我沒有珍惜,等到同事因為用了這個技巧升任總工才後悔莫及,如果上天再給我一次機會,我會對老師說:我要學!如果非要定個學習時間,我希望是:立馬! 拓展: 這是一個真實的故事。 一個以往的學生就職東莞易事特,一家做新