Spring Security解析九:AuthenticationManager

Spring Security解析九:AuthenticationManager,第1张

在Spring Security中对用户进行认证的是AuthenticationManager,其只有一个方法,尝试对封装了认证信息的Authentication进行身份验证,如果成功,则返回完全填充的Authentication(包括授予的权限)。

AuthenticationManager 只关注认证成功与否而并不关心具体的认证方式。例如我们可以通过用户名及密码、短信、刷脸、OAuth2协议等方式进行认证。对于这些具体认证方式是交给了AuthenticationProvider来负责。

下面展示了AuthenticationProvider的部分实现

AuthenticationManager与AuthenticationProvider 是怎么被创建使用,以及如何自由的添加认证方式的问题,在下面的内容中将进一步分析

前面章节分析了WebSecurityConfiguration配置类里面相关的内容,现在回到@EnableWebSecurity 注解上,我们接着分析下@EnableGlobalAuthentication内部都做了些啥

其重点就是导入了AuthenticationConfiguration配置对象

AuthenticationConfiguration 中还导入了ObjectPostProcessorConfiguration配置,该配置比较简单,就是实例化了一个bean,而该Bean在前面的章节中也在不断的用到

下面,我们深入分析下AuthenticationConfiguration配置类的实现。

先简单的说下AuthenticationManager构建的主体过程

由上门可知,其默认使用DefaultPasswordEncoderAuthenticationManagerBuilder作为认证管理的构建器,下面分析其build()方法的执行过程。

DefaultPasswordEncoderAuthenticationManagerBuilder在执行build()方法时,其父类AbstractConfiguredSecurityBuilder的doBuild()方法被执行,前面有说过,这个方法是个模板方法,如下所示:

接着我们分析下这三个默认的GlobalAuthenticationConfigurerAdapter类型的实例中init和configure方法都做了啥

该类的目的纯粹是为了添加InitializeUserDetailsManagerConfigurer配置,通过在其configure方法阶段创建DaoAuthenticationProvider对象,最终被添加到ProviderManager中

Springboot的自动化配置中会默认创建InMemoryUserDetailsManager,请参考 Spring Security解析二:自动化装配

我们也可以通过配置来指定,例如:

接着进一步研究下DaoAuthenticationProvider都做了些啥,它是怎么对身份进行认证的?

可见上的 *** 作主要是从某个地方得到用户信息,然后检查用户的状态,如果检查失败则抛出相应的异常,否则返回成功的认证信息。

上面的retrieveUser与additionalAuthenticationChecks是需要继续研究的地方

上面通过userDetailsService来得到用户的信息,并通过passwordEncoder来验证密码是否正确,而这两个对象是通过上面 32小结里的InitializeUserDetailsManagerConfigurer中从ApplicationContext获得。

该类的目的纯粹是为了添加InitializeUserDetailsManagerConfigurer配置,通过在其configure方法阶段从ApplicationContext中得到AuthenticationProvider类型的Bean,并加入到ProviderManager中

小结:

经过上来的步骤后,在DefaultPasswordEncoderAuthenticationManagerBuilder的authenticationProviders属性中添加了一个或多个AuthenticationProvider,接下来的工作便是执行DefaultPasswordEncoderAuthenticationManagerBuilder的performBuild()方法完成AuthenticationManager的创建工作。当然,其实该方法是在父类AuthenticationManagerBuilder中的。

返回的其实是ProviderManager,而ProviderManager可以看成是AuthenticationManager的代理对象,里面保存了多个AuthenticationManager的实现。

Spring Security默认情况下为我们创建了一个基于用户名和密码进行验证的AuthenticationManager实例,同时收集ApplicationContext中的AuthenticationProvider类型的Bean 一起添加到ProviderManager(AuthenticationManager的子类)中供需要的地方进行使用。

一、用户名密码登录处理过程(spring security实现的处理流程):

1密码登录的请求会进入一个叫做UsernamePasswordAuthenticationFilter的过滤器

2这个过滤器会拿到用户名和密码组装成UsernamePasswordAuthenticationToken这样一个对象

3然后过滤器把这个token对象传给AuthenticationManager

4AuthenticationManager会在一堆AuthenticationProvider中挑出一个处理认证请求

这个挑选的依据是:这个authenticationProvider中有一个supports()方法会判断当前这个provider是否支持传进来的UsernamePasswordAuthenticationToken,如果支持,就用当前的Provider来认证这个token。

5在认证的过程中会调UserDetailsService来获取用户的信息,跟传进来的登录信息进行比对,如果认证通过会把UsernamePasswordAuthenticationToken 做一个标志,标记为已认证放进session中。

二、手机号验证码登录处理过程:(spring security没有实现,应该我们自己去仿照用户名密码登录去实现)

1短信验证码登录的请求进入我们自己写的SmsAuthenticationFilter过滤器

2这个过滤器会从请求中拿到手机号封装成一个SmsAuthenticationToken这样一个对象

3然后过滤器会把这个token传给系统中唯一的AuthenticationManager

4AuthenticationManager还是会检索系统中的所有的AuthenticationProvider,这里我们要实现一个SmsAuthenticationProvider,

用这个provider来检索token中的手机号。

5这个过程中还是会调用UserDetailsService来获取用户信息,来跟登录信息进行比对,如果认证通过会把SmsAuthenticationToken 标志为已认证,放进session中。

注意:为什么验证短信验证码的逻辑不像用户名密码的验证逻辑写在DaoAuthenticationProvider一样写在SmsAuthenticationProvider中呢?主要是因为我们想要重用这个短信验证,例如支付的时候需要短信验证,所以我们单独写一个过滤器来实现短信验证码的功能,放在SmsAuthenticationFilter之前。

自定义一个邮箱验证码的认证,将邮箱号码作为key,验证码作为value存放到Redis中缓存。

首先回顾下之前源码分析的认证流程,如下图:

总结

需要实现以下几个类:

在介绍之前先提一个问题:

springsecurity:Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。

oauth:OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。

从以上可以看出,security是spring的一个控制应用安全的一个框架;而oauth是一个关于授权的一个标准(之所以成为标准就是大家都认可呗),所以它俩根本就是两个不一样的东西;

之所以spring会有spring-cloud-starter-oauth2包,是因为springsecurity安全框架借鉴了Oauth的标准,把Oauth标准集成进来,来对受保护的资源进行安全授权;

大体流程图:

解释:

1用户请求资源

OAuth2ClientAuthenticationProcessingFilter 过滤器获取token,然后根据token获取Authentication(用户验证信息)

2 如果token获取不到,说明用户没有登陆,重定向到认证授权服务器登陆页面

21 用户输入用户名和密码登陆

22登陆成功重定向到授权页面进行授权;PS: 可以通过设置成默认授权模式,就不用跳转到授权页面,直接访问资源

23授权后,继续访问之前的资源(携带token)

3token有效且获取到Authentication(保存在context中),继续访问资源

会被security资源保护拦截器拦截(FilterSecurityInterceptor)校验权限

4有权限可以访问

5无权限则抛出403异常

推荐文章:

>

webxml配置过滤器了吗,下面的配置拦截所有链接

<filter>

<filter-name>springSecurityFilterChain</filter-name>

<filter-class>

orgspringframeworkwebfilterDelegatingFilterProxy

</filter-class>

</filter>

<filter-mapping>

<filter-name>springSecurityFilterChain</filter-name>

<url-pattern>/</url-pattern>

</filter-mapping>

applicationContext里关键代码

<bean id="authenticationProcessingFilter"

class="orgspringframeworksecurityuiwebappAuthenticationProcessingFilter">

<!-- 此行说明此filter会覆盖默认配置的filter,before 被覆盖filter的别名 -->

<security:custom-filter

before="AUTHENTICATION_PROCESSING_FILTER" />

<!-- 认证管理器 -->

<property name="authenticationManager"

ref="authenticationManager" />

<!-- 认证失败后跳转到的页面,/spring_security_login是ss2默认的登录页面 -->

<!-- 认证失败后跳转到的页面 -->

<property name="authenticationFailureUrl"

value="/loginjsp" />

<!-- 认证成功后跳转到的页面 -->

<property name="defaultTargetUrl" value="/indexhtml" />

</bean>

在原先dubbo+zookeeper项目中,web模块只暴露Restful接口,各服务模块只暴露duboo接口,此时用户登录后由web项目进行token的鉴权和验证,并通过dubbo的隐式传参将sessionID传递给dubbo服务模块, 拦截器再根据sessionID从Redis中获取用户信息设置到当前线程

然鹅,在springcloud中,各个微服务直接暴露的是restful接口,此时如何让各个微服务获取到当前用户信息呢?最佳的方式就是token了,token作为BS之间的会话标识(一般是原生随机token),同时也可以作为信息的载体传递一些自定义信息(jwt, 即Json web token)。

为了能更清楚的了解本文,需要对spring-security-oauth 及 jwt有一定了解,本文只关注用户信息传递这一块

认证服务器配置 AuthorizationServerConfigurerAdapter

自定义token转换器

CustomJwtAccessTokenConverter

此时按照固定格式访问授权服务器token接口获取token,如图,可以获取到jwt格式的token,并且额外信息nick_name也已经添加

直接解析jwt字符串可以获取到以下信息,即用户名和授权信息

只需要指定和授权服务器一模一样的token store 和token converter

在securiy的过滤器中 OAuth2AuthenticationProcessingFilter 会从token中获取相关信息进行鉴权

源码:

注意,资源服务器主要配置在

ResourceServerConfigurerAdapter

微服务获取jwttoken中的用户信息,两种方式,使用security上下文可以直接获取当前用户名和权限,另一种自定义拦截器获取额外信息。

这个就简单了,获取header头解析验证token

然后获取之前从授权服务器中的添加的 nick_name的额外信息放入线程变量

其中用户上下文类

启动拦截器注册webmvc配置类

在controller中获取用户信息如图

在默认的认证异常如图

假设我们做了全局异常处理,前端希望在token过期时做统一的登录跳转如何做?

实现 AuthenticationEntryPoint 接口重写 commence 方法即可

注意,直接抛出异常并不会走 @RestControllerAdvice , 因为在这里是response直接返回,并没有使用到Controller处理

此时返回我自定义的Response对象,如图

在调用资源服务器的过程中,我们会将申请的token 作为header值进行传递,携带调用者的身份信息。但是资源服务器是如何通过token对调用者的身份进行判断的呢?

Security中有一个Filter实现了对token信息的转换,将token值转换成了调用者的用户信息。该filter就是 Oauth2AuthenticationProcessingFilter

一、查看源码

查看Oauth2AuthenticationProcessingFilter的doFilter方法

通过查看Oauth2AuthenticationProcessingFilter的dofilter方法,重点有两点

(1)将request中的token提取出来封装成Authentication对象

(2)将Authentication交给authenticationManager进行鉴权处理

下面我们重点看下这两处的处理。

二、token到Authentication对象转换实现

Authentication authentication = tokenExtractor extract(request);

tokenExtractor在Oauth2AuthencationProcessingFilter中的默认实现是BearerTokenExtractor,我们查看BearerTokenExtractor的extract()方法。

三、Authentication对象的鉴权

Authentication authResult = authenticationManager authenticate(authentication);

此处的authenticationManager的实现类是Oauth2AuthenticationManager,而不是我们之前一直提到的ProvicerManager。我们看下Oauth2AuthenticationManager中的authenticate()方法。

RemoteTokenService loadAuthentication() 方法

用户认证转换类

接口层注入的 OAuth2Authentication对象中的 principal属性即在该类的extractAuthentication() 方法中实现的。

security默认使用的是 DefaultAccessTokenConverter类中的extractAuthentication()方法中使用。

通过继承UserAuthenticationConverter该类,实现其中的extractAuthentication()方法来满足我们自己构造 principal属性的需求。

在ResourceConfig类中,继续使用DefaultAccessTokenConverter,但是类中的UserAuthenticationConverter我们里换成我们自己的CustomUserAuthenticationConverter实现类。

我们构造的principal属性是map类,里面包含phone和userId两个字段。

以上就是关于Spring Security解析九:AuthenticationManager全部的内容,包括:Spring Security解析九:AuthenticationManager、springsecurity密码登录的流程分析、SpringSecurity自定义认证等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9681885.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存