shiro源码(二)——login方法源码解读

一、shiro认证流程源码

使用shiro框架做登录,只需调用subject的login方法即可,代码如下:

 public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
    {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
        Subject subject = SecurityUtils.getSubject();
        try
        {
            subject.login(token);
            return success();
        }
        catch (AuthenticationException e)
        {
            String msg = "用户或密码错误";
            if (StringUtils.isNotEmpty(e.getMessage()))
            {
                msg = e.getMessage();
            }
            return error(msg);
        }
    }

下面,就看subject的login方法内部实现思路。
通过debug,进入了DelegatingSubject类的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;
        }
    }

可以看出,里面调用了securityManager的login方法,继续进入该方法,最终,走到了AbstractAuthenticator类的authenticate方法,这就是Shiro的认证方法,最终追到了如下方法:

  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);
        }
    }

这里,获取realm域,如果是一个,那就是单数据库的,如果是多个,那就是多数据库的,需要从多个数据库中查询用户信息。这里我们看单realm的方法doSingleRealmAuthentication,重点代码来到如下方法:

  public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //登录操作,没有缓存,不走这个方法
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            //进入这个方法
            info = doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

doGetAuthenticationInfo方法就调用了我们自定义的Realm类的doGetAuthenticationInfo方法,从数据库查询用户信息,然后返回。
在这里插入图片描述
自定义Realm类的doGetAuthenticationInfo方法如下:

 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
    {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        String password = "";
        if (upToken.getPassword() != null)
        {
            password = new String(upToken.getPassword());
        }

        SysUser user = null;
        try
        {
            user = loginService.login(username, password);
        }
        catch (CaptchaException e)
        {
            throw new AuthenticationException(e.getMessage(), e);
        }
        catch (UserNotExistsException e)
        {
            throw new UnknownAccountException(e.getMessage(), e);
        }
        catch (UserPasswordNotMatchException e)
        {
            throw new IncorrectCredentialsException(e.getMessage(), e);
        }
        catch (UserPasswordRetryLimitExceedException e)
        {
            throw new ExcessiveAttemptsException(e.getMessage(), e);
        }
        catch (UserBlockedException e)
        {
            throw new LockedAccountException(e.getMessage(), e);
        }
        catch (RoleBlockedException e)
        {
            throw new LockedAccountException(e.getMessage(), e);
        }
        catch (Exception e)
        {
            log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
            throw new AuthenticationException(e.getMessage(), e);
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
        return info;
    }

可见,其就是通过查询数据库,获取用户信息,然后和传入的token做对比,判断认证是否通过。
至此,登录验证过程完成。通过上面的分析我们可以知道,shiro给程序员留出的口就是查询数据库,对比token,判断是否登录成功。这似乎和我们不使用shiro框架的操作一样啊,不使用shiro框架不也是查询数据库,比较用户名密码,判断是否登录吗?那使用shiro还有啥用呢?我们看认证成功后,shiro又做了哪些操作。

二、认证成功后续操作流程源码

接着上面getAuthenticationInfo方法看,

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //登录操作,没有缓存,不走这个方法
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            //进入这个方法
            info = doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
            //缓存用户信息
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

可以看到,认证成功后,会调用cacheAuthenticationInfoIfPossible方法,进行缓存,看其源码,

 private void cacheAuthenticationInfoIfPossible(AuthenticationToken token, AuthenticationInfo info) {
        if (!isAuthenticationCachingEnabled(token, info)) {
            log.debug("AuthenticationInfo caching is disabled for info [{}].  Submitted token: [{}].", info, token);
            //return quietly, caching is disabled for this token/info pair:
            return;
        }
         //获取到Cache对象
        Cache<Object, AuthenticationInfo> cache = getAvailableAuthenticationCache();
        if (cache != null) {
            //通过token,生成一个key,
            Object key = getAuthenticationCacheKey(token);
            //将key和登录信息info存入缓存中。
            cache.put(key, info);
            log.trace("Cached AuthenticationInfo for continued authentication.  key=[{}], value=[{}].", key, info);
        }
    }

这里可以知道,认证成功后,shiro将认证信息存入了缓存对象Cache中。

我们继续往上回查代码,看认证成功得到info后,还有哪些操作。
在AbstractAuthenticator的authenticate认证方法中,认证成功后,调用了如下方法:

notifySuccess(token, info);

看其方法内部:

protected void notifySuccess(AuthenticationToken token, AuthenticationInfo info) {
        for (AuthenticationListener listener : this.listeners) {
            listener.onSuccess(token, info);
        }
    }

可以看出,这个方法利用了观察者模式,用于认证成功后,通知AuthenticationListener 的实现类。这里提供了监听用户认证成功的豁口,所以,我们想在一个用户登录时做一些操作的话,可以实现AuthenticationListener 接口来做操作,如提醒谁上线的需求,就可以用这个豁口来实现。AuthenticationListener 类的研究我们单独讲解,这里看流程。
继续往上回查代码,看SecurityManager的login方法,看这行代码:

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

这里,根据info认证信息,生成了subject对象,点进去看方法,

 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);//设置SecurityManager

        //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);//设置session

        //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);//生成subject

        //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;
    }

可以看出,创建subject,就是往subject里设置了SecurityManager,session和Principals信息。
然后,看SecurityManager的login方法的这行代码;

onSuccessfulLogin(token, info, loggedIn);

这行代码是"记住我"功能的支持,这里我们分析主流程,这行代码我们单独分析。继续往回追代码,到了DelegatingSubject的login方法,看剩余的方法:

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;
        }

可以看出,就是对session,principals等一些数据的初始化赋值操作。至此,subject的login方法流程分析完成。

三、总结

下面,总结一下上面的分析过程。
在这里插入图片描述

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Shiro是一个开源的Java安全框架,用于对应用程序进行身份验证、授权和加密等安全相关功能的支持。Spring Boot是一个用于创建独立的、生产级别的Spring应用程序的框架。 Shiro Spring Boot Starter是一个与Spring Boot集成的项目,它提供了使用Shiro框架进行身份验证和授权的便捷方式。通过引入Shiro Spring Boot Starter依赖,可以轻松将Shiro集成到Spring Boot应用程序中。 Shiro Spring Boot Starter的源码包括几个主要部分: 1. 自动配置类:ShiroAutoConfiguration是Shiro Spring Boot Starter的核心配置类,它通过@EnableConfigurationProperties注解读取配置文件中的属性,并根据这些属性进行相应的自动配置。它负责创建Shiro安全管理器、认证器、授权器等实例,并通过注入的方式将它们注入到Spring容器中。 2. 属性配置类:ShiroProperties定义了Shiro在配置文件中的属性,并提供了默认值。通过@ConfigurationProperties注解,可以将这些属性与配置文件中的对应属性进行关联。 3. 注解支持类:ShiroAnnotationProcessor是一个自定义的注解处理器,它通过处理标注了@RequiresAuthentication、@RequiresUser、@RequiresRoles和@RequiresPermissions等注解的方法,实现了对方法级别的身份验证和授权支持。 4. 过滤器类:ShiroFilterChainDefinition是Shiro的过滤器链定义类,它定义了URL与过滤器的映射关系,并负责创建ShiroFilterFactoryBean的实例,将其注入到容器中。 5. 辅助类:ShiroUtils是一个工具类,提供了一些常用的方法,如获取当前登录用户的主体、判断用户是否拥有指定角色或权限等。 总的来说,Shiro Spring Boot Starter的源码实现了对Shiro框架在Spring Boot应用程序中的集成和自动配置。通过引入该Starter依赖,可以简化Shiro框架的配置和使用,提高开发效率,同时保证应用程序的安全性。 ### 回答2: Shiro是一个基于Java的开源安全框架,用于提供身份验证、授权、会话管理和密码加密等功能。它能帮助开发人员快速构建安全可靠的应用程序。而Spring Boot是一个基于Spring框架的开源项目,它简化了Spring应用程序的开发和部署。 Shiro和Spring Boot结合使用,可以使得应用程序的安全性和性能得到更好地保障。Shiro作为一个独立的框架可以和Spring Boot集成,通过配置文件和注解的方式,实现对应用程序的安全管理。 Shiro Spring Boot源码是指将Shiro和Spring Boot集成时所使用到的相关源代码。这些源码包括了配置文件、注解、代码注入、过滤器等等。通过阅读和理解这些源码,开发人员可以深入了解Shiro Spring Boot集成的工作原理和机制。 通过阅读Shiro Spring Boot源码,我们可以了解到Shiro是如何通过自定义配置文件和注解来实现各种身份验证和授权的方式。源码中可以看到一些关键的类和方法,如Realm、Subject、AuthenticationToken等等,这些类和方法对于理解Shiro Spring Boot的工作流程非常重要。 另外,Shiro Spring Boot源码还涉及到了Spring Boot的自动配置机制。Spring Boot通过自动配置,可以减少开发人员的工作量,自动完成一些基本的配置,以适应不同的应用需求。通过阅读源码,我们可以了解到Spring Boot是如何实现自动配置功能的,以及如何自定义配置来适配特定的应用场景。 总的来说,阅读Shiro Spring Boot源码有助于我们深入理解Shiro和Spring Boot的工作原理和机制,提升对应用程序的安全性和性能的把握,进而能更好地开发和调优应用程序。 ### 回答3: Shiro是一个强大的身份认证和授权框架,而Spring Boot是一个用来简化Spring应用程序开发和部署的框架。Shiro Spring Boot是Shiro和Spring Boot的结合体,提供了在Spring Boot应用中集成Shiro的功能。 Shiro Spring Boot源码包含了一系列的类和配置文件,用于配置和启动Shiro框架。在源码中,可以找到一些核心类,比如ShiroFilterFactoryBean、DefaultWebSecurityManager等。这些类负责处理Shiro的配置和初始化。 ShiroFilterFactoryBean是Shiro的核心过滤器,是Shiro的入口点。它负责创建Shiro安全过滤器链,并根据配置决定哪些请求应该经过Shiro的认证和授权。 DefaultWebSecurityManager是Shiro安全管理器,它负责管理和协调Shiro的各种组件,比如Realm、SessionManager等。它是Shiro框架中最重要的组件之一。 除了这些核心类,源码中还包含了一些配置类,比如ShiroConfig、ShiroProperties等,用于配置Shiro的相关参数和属性。这些配置类提供了灵活的配置选项,使用户可以根据自己的需求来定制Shiro的行为。 总的来说,Shiro Spring Boot源码提供了一个方便快捷地在Spring Boot应用中集成Shiro的方式。通过深入研究和理解源码,我们可以更好地掌握Shiro的工作原理,并根据自己的需求进行扩展和定制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码的小小酥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值