springboot整合shiro——前后端分离模式(浅析登录流程)

1,205 阅读5分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。

前两天已经把前后端分离的整个流程讲完了,感兴趣的小伙伴可以点进去看一看 # springboot整合shiro——前后端分离模式(上篇) # springboot整合shiro——前后端分离模式(下篇)

在整合shiro的时候也碰到过不少问题,主要就是登录认证这一块,把认证搞好了授权基本上没啥问题,我自己也打了断点跟着流程走了十几遍差不多清楚了整个的认证流程,以下是我自己的理解如果有不对的地方欢迎指正。

image.png

首先还是从这个官方的登录流程图开始讲起,我看了下官方文档总结如下:

  1. 应用程序代码调用 Subject.login 方法,并传入代表最终用户主体和凭据的构造 AuthenticationToken 实例。
  2. Subject 实例,通常是 DelegatingSubject (或子类)通过调用 SecurityManager.login (令牌)委托给应用程序的 SecurityManager,实际的身份验证工作从这里开始。
  3. SecurityManager 作为一个基本的“保护伞”组件,接收令牌并通过调用 Authenticator.authenticate (token)简单地委托给它的内部 Authenticator 实例。基本上都是 ModularRealmAuthenticator 实例,它支持在身份验证期间协调一个或多个 Realm 实例。Modularrealmauthentator 实质上为 Apache Shiro 提供了一个 PAM风格的范例(在 PAM 术语中,每个realm都是一个“模块”)。
  4. 如果为应用程序配置了多个realm,ModularRealmAuthenticator 实例将利用其配置的 AuthenticationStrategy 启动多领域身份验证尝试。在调用领域进行身份验证之前、期间和之后,将调用 AuthenticationStrategy,以允许它对每个realm的结果做出反应。
  5. 查看每个配置的 Realm 是否支持提交的 AuthenticationToken。支持 Realm 的 getawatitationinfo 方法将被提交的令牌调用。getAuthenticationInfo方法有效地代表了针对特定realm的单一身份验证尝试。

然后再来看一下我的程序的流程图:

image.png

由于前后端分离的原因我重写了shiro认证的过滤器,所以我的认证是从过滤器开始的,正常情况下最后都会走到executeLogin(request, response)这个方法中,也就是说这个方法就是我们认证的起点。

image.png

点进去这个方法看一下,可以看到第一步就是创建token,在前一篇里面我讲到了我重写过这个方法,因为这和后面在realm中认证有关,所以我们要自己决定创建啥样的token以及在realm中的认证方式。

image.png

我重写的createToken方法里就是把前端发来的token值拿到放进去就是我需要的token对象了,realm里认证就是根据token的值来判断的。

@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
    //获取请求token(dataId)
    String token = TokenUtil.getRequestToken((HttpServletRequest) request);

    return new AuthToken(token);
}

当我们的token对象创建好后就来到了subject.login(token)这个方法,继续跟进,里面是通过securityManager安全管理器接着在调用securityManager.login(this, token)

image.png

在这个方法里面就是要获取我们的AuthenticationInfo对象,也就是认证信息的对象,这个对象其实就是我们自定义的UserRealm重写的认证方法所返回的对象

image.png

我们接着进到authenticate(token)方法里面,可以看到info是通过doAuthenticate(token)方法返回的,再跟进去

image.png

这里会先判断一下我们配置的realm个数然后决定走哪个逻辑,我们在只配了一个realm就走single的方法,多个realm逻辑应该会更复杂一点

image.png

然后就走到了doSingleRealmAuthentication方法里面,在这里就是根据我们的自定义的realm来获取Info信息了

image.png

获取info信息前也会判断一下,第一步是从shiro缓存里拿info信息,然后判断info是不是空的

image.png

我们先看一下如果是空的话就是走doGetAuthenticationInfo(token)方法,而这个方法就是我们在UserRealm中重写的方法,我们这里进行认证然后返回info信息就可以了,这就是第一次调用的结果,当第一个请求被过滤器拦截然后一直走到这里,前提是我们是调用过登录接口然后拿到过token值的。因为登陆那一块我是放行的不会走过滤器,所以先登录拿到代表身份信息的token,然后别的请求第一次带着这个代表身份信息的token一直走到这里然后通过我们的认证逻辑的时候就会生成一个info对象,这个info对象的第二个参数也就是代表身份信息的token值,然后返回,返回以后就会存在shiro的缓存里面了

image.png

然后我们再来看info不为空的情况,说明已经不是第一次了,之前已经认证过了缓存里面有我们的info信息了,然后就走assertCredentialsMatch这个方法了,这个方法就会先获取一个类似匹配器的东西来校验我们传进去的token和缓存里的info对不对的上,我们继续点进去看

image.png

他有很多种实现,我们这里用的是最后一种,就是比较一下info和token一不一样,那具体以比较的是什么呢?打个断点跑一边就知道了

image.png

可以看到其实比较的就是token值,知道了他的认证逻辑以后就可以根据自己的需求来了,不想用token的那你改成用户名密码校验也可以,创建token的时候把密码放进去,生成info信息的时候也把密码放进去,道理都是一样的

image.png

到此为止用户认证的逻辑就结束了,搞定了认证以后授权这块一般不会有啥问题,就是从数据库里查出来权限跟角色然后套到用户身上,我这里用的是注解式的权限检查,在接口上写个注解就可以做权限限制了。