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;
}
父類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-元件--使用者認證Auth(auth_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語言(一個真實的故事)
曾經有一個簡單又不失牛逼的小技巧,老師教的時候我沒有珍惜,等到同事因為用了這個技巧升任總工才後悔莫及,如果上天再給我一次機會,我會對老師說:我要學!如果非要定個學習時間,我希望是:立馬! 拓展: 這是一個真實的故事。 一個以往的學生就職東莞易事特,一家做新