Shiro的认证原理(Subject#login的背后故事)

2024-09-03 05:32

本文主要是介绍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的背后故事)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1132161

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

hdu4407容斥原理

题意: 有一个元素为 1~n 的数列{An},有2种操作(1000次): 1、求某段区间 [a,b] 中与 p 互质的数的和。 2、将数列中某个位置元素的值改变。 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.Inpu

hdu4059容斥原理

求1-n中与n互质的数的4次方之和 import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.PrintWrit

AngularJS for login

web.xml <?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/n

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

【Shiro】Shiro 的学习教程(三)之 SpringBoot 集成 Shiro

目录 1、环境准备2、引入 Shiro3、实现认证、退出3.1、使用死数据实现3.2、引入数据库,添加注册功能后端代码前端代码 3.3、MD5、Salt 的认证流程 4.、实现授权4.1、基于角色授权4.2、基于资源授权 5、引入缓存5.1、EhCache 实现缓存5.2、集成 Redis 实现 Shiro 缓存 1、环境准备 新建一个 SpringBoot 工程,引入依赖:

寻迹模块TCRT5000的应用原理和功能实现(基于STM32)

目录 概述 1 认识TCRT5000 1.1 模块介绍 1.2 电气特性 2 系统应用 2.1 系统架构 2.2 STM32Cube创建工程 3 功能实现 3.1 代码实现 3.2 源代码文件 4 功能测试 4.1 检测黑线状态 4.2 未检测黑线状态 概述 本文主要介绍TCRT5000模块的使用原理,包括该模块的硬件实现方式,电路实现原理,还使用STM32类

TL-Tomcat中长连接的底层源码原理实现

长连接:浏览器告诉tomcat不要将请求关掉。  如果不是长连接,tomcat响应后会告诉浏览器把这个连接关掉。    tomcat中有一个缓冲区  如果发送大批量数据后 又不处理  那么会堆积缓冲区 后面的请求会越来越慢。