Shiro认证源码图文解析

AuthenticatingSecurityManager

AuthenticatingSecurityManager是负责认证管理的,所以前面Shiro图的认证管理器就是在这里,里面只有一个熟悉感,就是认证管理器Authenticator。

在这里插入图片描述

可以看到,认证器默认的引用类型是ModularRealmAuthenticator。

AuthorizingSecurityManager

AuthorizingSecurityManager是负责授权管理的,也就是前面的授权器所在类,里面也是只有一个属性,就是授权器Authorizer

在这里插入图片描述

也是很容易看到,授权器的默认实现方式为ModularRealmAuthorizer。

SessionSecurityManager

SessionSecurityManager是负责会话管理的,里面也只有一个会话管理器属性,默认为DefaultSessionManager

在这里插入图片描述

DefaultSecurityManager

拥有上面提到的所有SecurityManager的功能,具体的SecurityManager.login方法就是在这里实现的。

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {

//该变量存储认证信息

AuthenticationInfo info;

try {

//获取认证信息

info = this.authenticate(token);

} catch (AuthenticationException var7) {

//下面的操作都是由获取认证信息步骤引起的

//要返回上一步,看认证信息步骤

AuthenticationException ae = var7;

try {

this.onFailedLogin(token, ae, subject);

} catch (Exception var6) {

if (log.isInfoEnabled()) {

log.info(“onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.”, var6);

}

}

throw var7;

}

//根据认证信息和token创建出Subject对象,所以要先管抓住认证信息怎么拿

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

this.onSuccessfulLogin(token, info, loggedIn);

return loggedIn;

}

DefaultSecurityManager获取认证信息

来到这一步,我们首先要去认识AuthenticationInfo这个对象,因为后面的比较都与它相关。

AuthenticationInfo对象

记得Realm中,在认证方法中,我们返回的是SimpleAuthenticationInfo这个对象的,这个对象跟AuthenticationInfo接口相关,所以我们从这里入手

在这里插入图片描述

我们可以看到这个接口,被3个子接口继承,而这三个子接口,总体上被2个类去实现,一个是SimpleAccount,另一个是SimpleAuthenticationInfo。

我们先来认识一下这四个接口吧

AuthenticationInfo

在这里插入图片描述

里面只有两个方法,分别是获取认证信息和密码的。

MergableAuthenticationInfo

在这里插入图片描述

可以看到他新增了一个合并认证信息的功能(认证信息如何进行合并的呢?下面再说)

Account

在这里插入图片描述

Account没有新增抽象方法,但还多继承了一个AuthorizationInfo(用来授权的,以后再说)

SaltedAuthenticationInfo

在这里插入图片描述

里面增加了一个获取盐值的方法(ByteSouce类型)。

认识SimpleAuthenticationInfo

在这里插入图片描述

从上图源码,可以看到,SimpleAuthenticationInfo实现了MergableAuthenticationInfo和SaltedAuthenticationInfo接口,所以他就有这两个接口的所有方法,并且有三个属性

  • PrincipalCollection principals 认证信息集合

  • credentials:密码

  • credentialsSalt:盐值

我们从构造方法开始入手

在这里插入图片描述

可以看到里面包括无参构造,总共有5个构造方法,盐值(credentialsSalt可以不注入)和credentials(密码)都是注入的,而principals是通过构造方法出来的,创建一个SimplePrincipalsCollection

SimplePrincipalsCollection

下面我们进入SimplePrincipalsCollection看看其到底是什么架构,

public class SimplePrincipalCollection implements MutablePrincipalCollection {

//序列化id,可以进行序列,然后放到内存中

private static final long serialVersionUID = -6305224034025797558L;

//所有realm的认证信息,一个map集合,键是String类型,Value是Set类型

private Map<String, Set> realmPrincipals;

//内存中的对象反序列化回来变成字符串,transizent是让这个属性不被序列化

private transient String cachedToString;

public SimplePrincipalCollection() {

}

/**

  • 构造方法都是使用addAll或者add方法来进行初始化的

  • 所以下面具体看一下这两个方法

**/

public SimplePrincipalCollection(Object principal, String realmName) {

if (principal instanceof Collection) {

this.addAll((Collection)principal, realmName);

} else {

this.add(principal, realmName);

}

}

public SimplePrincipalCollection(Collection principals, String realmName) {

this.addAll(principals, realmName);

}

public SimplePrincipalCollection(PrincipalCollection principals) {

this.addAll(principals);

}

//。。。。

}

add方法

/**

  • add方法源码

**/

public void add(Object principal, String realmName) {

//如果realmName或者principal为空,就抛出异常

if (realmName == null) {

throw new IllegalArgumentException(“realmName argument cannot be null.”);

} else if (principal == null) {

throw new IllegalArgumentException(“principal argument cannot be null.”);

}

//不为空,就初始化cachedToString属性

//然后调用getPrincipalslazy方法

else {

this.cachedToString = null;

//拿到realmName对应的set集合,存放principal信息

this.getPrincipalsLazy(realmName).add(principal);

}

}

/**

  • getPrincipalsLazy方法具体实现

**/

protected Collection getPrincipalsLazy(String realmName) {

//我们可以看到,其实这个方法是用来初始化relamPrincipals的

//如果是第一次加入,先对realmPrincipals进行初始化

if (this.realmPrincipals == null) {

//第一次定义为是一个LinkedHashMap

//这个LinkedHashMap,键是realmName,值是一个set集合

this.realmPrincipals = new LinkedHashMap();

}

//通过键值对方式,获取realmPrincipals的对应realmName的set集合

Set principals = (Set)this.realmPrincipals.get(realmName);

//如果没有,证明该Realm是第一次存放认证信息,还没有进行与其他Realm的信息合并

if (principals == null) {

//set集合具体是一个LinkedHashSet

principals = new LinkedHashSet();

//往realmPrincipals中放入这realmName为key,principals为值的键值对

this.realmPrincipals.put(realmName, principals);

}

//如果已经有了,证明已经合并过了

//返回realmName对应的set集合

return (Collection)principals;

}

所以SimplePrincipalCollection的底层是一个LinkedHashMap,以RealmName为键,是一个字符串对象,值对应的是一个LinkedHashSet集合,里面存放的就是principal(账号信息)

在这里插入图片描述

我们可以看到key是Realm的全限定符,value里面存的就是是一个以账号组成的LinkedHashSet。

为什么要用LinkedHashSet呢?一个Realm里面还会存在多个账号的吗?

在这里插入图片描述

下面回到我们的认证过程

DefaultSecurityManager调用自身的authenticate方法发来获取认证信息,该方法实现具体如下

在这里插入图片描述

点进去,发现Authenticator是一个接口,而且拥有两个实现类,那么具体是哪一个呢?

在这里插入图片描述

这里,我们必须返回到上一层看一下DefaultSecurityManager的authenticator是哪一个,去看看到底调用的是哪一个实现类,前面已经提到过,DefaultSecurityManager的authenticator(认证器)是AuthenticatingSecurityManager的认证器,所以默认的类型是ModularRealmAuthenticator。

在这里插入图片描述

我们可以看到ModularRealmAuthenticator是继承AbstractAuthenticator的,而且并没有authenticate方法的实现,所以可以断定,authenticate的实现肯定在父类AbstractAuthenticator中

在这里插入图片描述

在里面,很轻易找到了对应具体实现方法

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

//如果token不存在,抛出异常

if (token == null) {

throw new IllegalArgumentException(“Method argument (authentication token) cannot be null.”);

} else {

log.trace(“Authentication attempt received for token [{}]”, token);

//该变量用于记录认证信息

AuthenticationInfo info;

try {

//调用自身的doAuthenticate方法获取认证信息

info = this.doAuthenticate(token);

if (info == null) {

String msg = “No account information found for authentication token [” + token + "] by this " + “Authenticator instance. Please check that it is configured correctly.”;

throw new AuthenticationException(msg);

}

} catch (Throwable var8) {

AuthenticationException ae = null;

if (var8 instanceof AuthenticationException) {

ae = (AuthenticationException)var8;

}

if (ae == null) {

String msg = “Authentication failed for token submission [” + token + "]. Possible unexpected " + “error? (Typical or expected login exceptions should extend from AuthenticationException).”;

ae = new AuthenticationException(msg, var8);

if (log.isWarnEnabled()) {

log.warn(msg, var8);

}

}

try {

this.notifyFailure(token, ae);

} catch (Throwable var7) {

if (log.isWarnEnabled()) {

String msg = “Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead…”;

log.warn(msg, var7);

}

}

throw ae;

}

log.debug(“Authentication successful for token [{}]. Returned account [{}]”, token, info);

this.notifySuccess(token, info);

return info;

}

}

我们可以看到,这里是调用了自身的doAuthenticate方法去获取认证信息的,所以,下面就去看看这个方法

protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken var1) throws AuthenticationException;

这个方法是一个抽象方法,所以肯定是由AbstractAuthenticator的子类ModularRealmAuthenticator去实现的

在这里插入图片描述

具体实现如下

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {

//判断是否有Realm装配

this.assertRealmsConfigured();

//获取所有Realm

Collection realms = this.getRealms();

//如果只有一个Realm,就只调用那个Realm(通过迭代器获取)

//如果有多个Realm,就多个执行

return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);

}

好了,现在弄清楚认证方法是怎样的了,具体的规则如下

  • 如果只有一个Realm,就只调用那一个Realm

  • 如果有多个Realm,都调用。

下面我们进入到Realm认证中

ModularRealmAuthenticator的doSingleRealmAuthentication

这是源码

protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {

//token类型不支持,抛出错误

if (!realm.supports(token)) {

String msg = “Realm [” + realm + “] does not support authentication token [” + token + "]. Please ensure that the appropriate Realm implementation is " + “configured correctly or that the realm accepts AuthenticationTokens of this type.”;

throw new UnsupportedTokenException(msg);

} else {

//从Realm中取出认证信息,即调用Realm的认证方法

AuthenticationInfo info = realm.getAuthenticationInfo(token);

//如果取不到,抛出找不到账号不匹配的异常

if (info == null) {

String msg = “Realm [” + realm + "] was unable to find account data for the " + “submitted AuthenticationToken [” + token + “].”;

throw new UnknownAccountException(msg);

} else {

return info;

}

}

}

进入realm.getAuthenticationInfo()

在这里插入图片描述

可以看到Realm是一个接口,并且实现该接口的类,主要有CachingRealm,AuthenticatingRealm和AuthorizingRealm,这里也是使用装饰器模式,一层一层递进,而实现getAuthenticationInfo的Realm是AuthenticatingRealm(这里三个Realm都是抽象类,实现接口是不需要去实现里面的方法的

下面是源码

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

//先从缓存中获取

AuthenticationInfo info = this.getCachedAuthenticationInfo(token);

if (info == null) {

//如果缓存中没有,就通过doGetAuthenticationInfo中获取

info = this.doGetAuthenticationInfo(token);

log.debug(“Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo”, info);

if (token != null && info != null) {

//将token和info都放入缓存(前面已经判断缓存中没有)

this.cacheAuthenticationInfoIfPossible(token, info);

}

} else {

log.debug(“Using cached authentication info [{}] to perform credentials matching.”, info);

}

if (info != null) {

//如果Info不为空,证明存在账号,然后进行校验密码

this.assertCredentialsMatch(token, info);

} else {

log.debug(“No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.”, token);

}

return info;

}

步骤总结如下

  1. 首先尝试从缓存中取出info

  2. 缓存中取不出就从doAuthenticationInfo方法中取

  3. 此时再判断info和token是否为NULL,如果不为NULL,就放入缓存中(前面已经判断缓存中没有)

  4. 然后通过assertCredentialsMatch方法进行校验密码

下面进入到doGetAuthenticationInfo里面看一下

在这里插入图片描述

这是一个抽象方法,这个方法其实就是我们自定义Realm时要去实现认证方法。

然后我们回到上一层,看一下,密码是如何校验的

下面是密码校验的源码

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {

//拿到密码匹配器

CredentialsMatcher cm = this.getCredentialsMatcher();

if (cm != null) {

//如果密码不匹配(使用token和前面获取到的认证信息进行匹配),抛出异常

if (!cm.doCredentialsMatch(token, info)) {

String msg = “Submitted credentials for token [” + token + “] did not match the expected credentials.”;

throw new IncorrectCredentialsException(msg);

}

}

//没有密码匹配器,抛出异常

else {

throw new AuthenticationException(“A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.”);

}

}

cm.doCredentialsMatch方法进行密码匹对

我们再详细去看一下cm.doCredentialsMatch方法。

可以看到,这是一个接口方法,而且有三个实现类

  1. AllowCredentialsMatcher

  2. PasswordMatcher

  3. SimpleCredentialsMatcher

在这里插入图片描述

AllowAllCredentialsMatcher

一直返回都是True,即所有匹配都是成功的,无论密码是什么都会验证成功

public class AllowAllCredentialsMatcher implements CredentialsMatcher {

public AllowAllCredentialsMatcher() {

}

/**

  • 返回的都是true

**/

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

return true;

}

}

SimpleCredentialsMatcher

这个很简单,只是进行输入的密码跟Realm认证信息里面的密码是否一致即可

public class SimpleCredentialsMatcher extends CodecSupport implements CredentialsMatcher {

private static final Logger log = LoggerFactory.getLogger(SimpleCredentialsMatcher.class);

public SimpleCredentialsMatcher() {

}

protected Object getCredentials(AuthenticationToken token) {

return token.getCredentials();

}

protected Object getCredentials(AuthenticationInfo info) {

return info.getCredentials();

}

/**

  • 这里是匹配的细节

**/

protected boolean equals(Object tokenCredentials, Object accountCredentials) {

if (log.isDebugEnabled()) {

log.debug(“Performing credentials equality check for tokenCredentials of type [” + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + “]”);

}

//判断能否被序列化字节

if (this.isByteSource(tokenCredentials) && this.isByteSource(accountCredentials)) {

//如果两个参数都可以很简单的变成字符数组

if (log.isDebugEnabled()) {

log.debug(“Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison”);

}

//将其变成字符数组进行比较

byte[] tokenBytes = this.toBytes(tokenCredentials);

byte[] accountBytes = this.toBytes(accountCredentials);

return MessageDigest.isEqual(tokenBytes, accountBytes);

} else {

//直接字符串进行匹配

return accountCredentials.equals(tokenCredentials);

}

}

/**

*进行验证

**/

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

Object tokenCredentials = this.getCredentials(token);

Object accountCredentials = this.getCredentials(info);

//将输入的密码和认证信息里面的密码进行匹配

return this.equals(tokenCredentials, accountCredentials);

}

}

既然都来到这里了,我们再看一看是怎么判断可以序列化的,点进去this.isByteSource方法,原来是由CodeSupport实现的

/**

  • 就是简单的判断是类型是否为字节数组、字符数组、字符串、文件、输入流等等

**/

protected boolean isByteSource(Object o) {

return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;

}

我们再看看,两个字节数组是怎么进行对比的。

/**

  • Compares two digests for equality. Does a simple byte compare.

  • @param digesta one of the digests to compare.

  • @param digestb the other digest to compare.

  • @return true if the digests are equal, false otherwise.

*/

public static boolean isEqual(byte[] digesta, byte[] digestb) {

//同一个对象,返回true

if (digesta == digestb) return true;

//都为空,返回false

if (digesta == null || digestb == null) {

return false;

}

//长度不一样,返回false

if (digesta.length != digestb.length) {

return false;

}

//遍历比较所有的字节

int result = 0;

// time-constant comparison

for (int i = 0; i < digesta.length; i++) {

//通过异或运算比较,两个相同就返回1

//即digesta[i]与digestb[i]是相同的,就返回0,不同返回1

//|=是result与右边值进行或运算后,将结果赋给result

//所以只要出现一次不同,result就会为1,因为或运算

result |= digesta[i] ^ digestb[i];

}

return result == 0;

}

可以看到,这里前面使用了,比较两个对象的地址是否一样,和比较数组长度是否一样,来减少运算,如果地址不一样,长度一样,就要进行遍历比较,通过异或运算和或运算来比较(使用异或比较两个字节数组,只有有一个不对应就会返回1,此时result就会一直为1,因为使用或运算,然后最后比较result是否为0即可)。

PasswordMatcher

public class PasswordMatcher implements CredentialsMatcher {

//装配一个PasswordService

private PasswordService passwordService = new DefaultPasswordService();

public PasswordMatcher() {

}

/**

  • 进行密码匹配

**/

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

//这一步是确保PasswordService装配成功,通过检验this.passwordService是否为Null

PasswordService service = this.ensurePasswordService();

//获取token里面的密码

Object submittedPassword = this.getSubmittedPassword(token);

//获取认证信息里面的密码信息

Object storedCredentials = this.getStoredPassword(info);

//判断密码的加密类型(在密码信息中有保存)

//这个加密类型是在ShiroConfig中注入Relam时有设置的

this.assertStoredCredentialsType(storedCredentials);

//如果加密算法是哈希

if (storedCredentials instanceof Hash) {

//将密码装换成哈希类型

Hash hashedPassword = (Hash)storedCredentials;

//这一步是将默认的密码服务方式强转成哈希密码服务方式

HashingPasswordService hashingService = this.assertHashingPasswordService(service);

//进行密码对比,对比Realm的密码和进行哈希加密后的密码

return hashingService.passwordsMatch(submittedPassword, hashedPassword);

} else {

//如果是普通的话,是进行特殊算法进行匹配的,没看懂

String formatted = (String)storedCredentials;

return this.passwordService.passwordsMatch(submittedPassword, formatted);

}

}

//。。。。。。

}

ModularRealmAuthenticator的doMultiRealmAuthentication

当有多个Realm时,就有问题了,如何判断认证成功呢?认证信息又该是怎样的呢?

现在我们看,当有多个Realm时,是怎么进行的

protected AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) {

//AuthenticationStrategy是认证策略

AuthenticationStrategy strategy = this.getAuthenticationStrategy();

//在经过所有的Realm进行认证时的初始化操作

AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

if (log.isTraceEnabled()) {

log.trace(“Iterating through {} realms for PAM authentication”, realms.size());

}

Iterator var5 = realms.iterator();

//使用迭代器遍历所有Realm

while(var5.hasNext()) {

Realm realm = (Realm)var5.next();

//记录进行验证当前Realm前的合计结果

aggregate = strategy.beforeAttempt(realm, token, aggregate);

if (realm.supports(token)) {

log.trace(“Attempting to authenticate token [{}] using realm [{}]”, token, realm);

AuthenticationInfo info = null;

Throwable t = null;

try {

//获取当前Realm的认证信息

info = realm.getAuthenticationInfo(token);

} catch (Throwable var11) {

t = var11;

if (log.isDebugEnabled()) {

String msg = “Realm [” + realm + “] threw an exception during a multi-realm authentication attempt:”;

log.debug(msg, var11);

}

}

//记录通过当前Realm后的合计结果

aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

} else {

log.debug(“Realm [{}] does not support token {}. Skipping realm.”, realm, token);

}

}

//已经通过所有Realm的认证,获取最终的策略认证后的信息

aggregate = strategy.afterAllAttempts(token, aggregate);

return aggregate;

}

在这部分,最重要的部分有两个

  • AuthenticationInfoStrategy:认证策略

  • beforeAllAttempts方法

  • beforeAttempt方法

  • afterAttempt方法

  • afterAllAttempts方法

  • AuthenticationInfo aggregate:合计结果的认证信息

AuthenticationInfoStrategy:认证策略

我们点进去this.getAuthenticationStrategy()方法,发现这个AuthenticationStrategy属性是在上一级的ModularRealmAuthenticator中的,默认为AtLeastOneSuccessfulStrategy,即至少有一次认证成功

在这里插入图片描述

而AuthenticationStrategy是一个接口,其实现类有如下几个

  • AbstractAuthenticationStrategy:抽象类,里面有缺省的afterAttempt与beforeAttempt

  • AtLeastOneSuccessfulStrategy:至少一次验证成功,返回所有成功的认证信息

  • AllSuccessfultStrategy:全部验证成功(只要出现一次失败就认证失败),返回所有成功的认证信息

  • FirstSuccessfulStrategy:第一个Realm认证成功,返回第一个认证成功信息

在这里插入图片描述

详细看看这几个策略的实现吧

AbstraAuthenticationStrategy

public abstract class AbstractAuthenticationStrategy implements AuthenticationStrategy {

public AbstractAuthenticationStrategy() {

}

/**

  • 没有重写就会使用的beforeAllAttempts

**/

public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {

//直接返回一个初始化的SimpleAuthenticationsInfo

return new SimpleAuthenticationInfo();

}

/**

  • 没有重写就会使用的beforeAttempt

**/

public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {

//只简单返回当前所有结果认证情况

return aggregate;

}

/**

  • 没有重写就会使用的afterAttempt

**/

public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {

//如果当前的Realm认证失败,singleRealmInfo就会为null

AuthenticationInfo info;

if (singleRealmInfo == null) {

//返回之前的所有结果认证情况即可

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
nInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {

//如果当前的Realm认证失败,singleRealmInfo就会为null

AuthenticationInfo info;

if (singleRealmInfo == null) {

//返回之前的所有结果认证情况即可

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-4nvmHpL9-1713383826711)]

[外链图片转存中…(img-pBfBdszc-1713383826711)]

[外链图片转存中…(img-Lu5R8I6P-1713383826711)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

[外链图片转存中…(img-tqmQm7r8-1713383826712)]

Mybatis面试专题

[外链图片转存中…(img-5vBnOVdg-1713383826712)]

MySQL面试专题

[外链图片转存中…(img-YYZDNUSK-1713383826712)]

并发编程面试专题

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值