spring security学习总结

这段时间了解了一下spring security以及oauth 2.0的相关内容,特在此总结,以供之后查阅。spring oauth 2.0会另开一篇文章。

首先声明,这段时间看了很多资料,但是最后只是在宏观上大致的把握了一下方向,对于绝大部分的技术细节都没有去了解,毕竟时间有限。所以本文也就是能说明白spring security的简单工作原理,大概也就这个程度了。废话到此为止。

本文参考的文章很多,这里只列出比较有关键影响的。
spring security reference 5.0.6.RELEASE
徐靖峰的spring security系列
spring security——Spring Security Architecture
以及一篇可以用来提升英语阅读能力的文章 ;-)

Spring Security

我们首先看spring security,让我们从一篇官方文档开始看起,这篇文档给了我们一个非常简单的spring security的例子。
这篇文章中和安全有关的只有这一处的配置:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/","/home").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //inMemoryAuthentication 从内存中获取
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("jason").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
    }

}

在configure(HttpSecurity http)方法中我们进行了路径匹配等需要进行认证的资源等,还指定了对应的login页面什么的,可以说,这个方法的配置是使用spring security的最核心部分。
下面的configure(AuthenticationManagerBuilder auth)方法主要和认证管理相关,这里是对用户进行身份认证的地方,不过例子中我们仅仅在内存中设置了一个用户,所以只有这一个用户可以通过认证。

至此,一个简单的spring security其实已经完成了,很不可思议对吧,只做了这么点配置。

不过接下来,我们才应该真正看看spring security是如何工作的。

过滤器链

首先,spring security是由一系列过滤器链组成的,我想这个可能大家都从各方面文档了解到了。我们可以通过启动spring security的项目来查看相应的拦截器链都有什么:
spring security学习总结_第1张图片
这一行显示了加载的默认spring security拦截器链,其中较重要的有SecurityContextPersisitenceFilterLogoutFilterUsernamePasswordAuthenticationFilterAnonymousAuthenticationFilterFilterSecurityInterceptor等。其他的相比之下不是那么重要。

关于这部分过滤器的解读可以参阅这篇文章。

仅仅知道这个过滤器链还是不够,我们需要在深入一点,最起码看看这一切到底是如何实现的。

解析

首先我们需要了解一下SecurityContextHolder,这个用于存储安全上下文的类,最常用的方式就是:

Authentication authentication = (Authentication)SecurityContextHolder.getContext().getAuthentication();

和安全有关的所有信息都由它保存,我们可以在需要的时候从这里取出相应的内容。注意,从SecurityContextHolder中取出的也可能是匿名认证类AnonymousAuthenticationToken;所以这里的处理需要注意,上面的写法就会出现类型转换异常。

接着我们可以通过AuthenticationgetPrincipal()方法得到一个UserDetails,很多人可能对于Principal一直感觉有点难以理解,我就是这样,不过我们可以看一些它的源码注释:
spring security学习总结_第2张图片

它表示的是一种主体的抽象概念,可以用来表示物体,个体,法人/公司,及登陆id。

用法类似下面:

if (authentication.getPrincipal() instanceof UserDetails) {
  UserDetails details = (UserDetails) authentication.getPrincipal();
  String username = details.getUsername();
  ......
}

我们可以看到,使用getPrincipal()方法取出了一个UserDetails;而UserDetails是spring对身份信息的一个封装。
spring security学习总结_第3张图片

我们再看之前涉及的另一个接口,也是贯穿始终的Authentication
spring security学习总结_第4张图片

它继承了Principal,是另一种抽象,代表身份认证的抽象。从这个接口我们可以获得用户的权限信息列表、密码、用户细节信息、用户身份信息、是否认证等。其中最常用的方法为上面介绍的getPrincipal()

我们尝试简单的模拟一下登陆的过程。

首先,我们在网站上输入用户名和密码,然后发送请求,用户名和密码被UsernamePasswordAuthenticationFilter截获,而这个类继承了一个抽象类AbstractAuthenticationProcessingFilter,这个抽象类才有过滤器的方法doFilter(),我们可以看一下它的实现:
spring security学习总结_第5张图片
这两个划线部分是重点,第一部分是由UsernamePasswordAuthenticationFilter实现的,我们看下它的实现:
spring security学习总结_第6张图片

我们看到这个方法返回了一个Authentication,具体的过程是封装一个UsernamePasswordAuthenticationToken,然后使用setDetails()方法将具体信息都塞到这个Authentication中(这部分可能有点绕,因为UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken,而它是Authentication的一个抽象实现类),接着就是用AuthenticationManager通过authenticate()方法认证这个UsernamePasswordAuthenticationToken,并返回一个Authentication

接着我们看第一张图的第二个划线部分,这里的successfulAuthentication()方法。
spring security学习总结_第7张图片
可以看到,认证成功后,我们就将得到的Authentication通过SecurityContextHolder.getContext().setAuthentication()方法设置到SecurityContextHolder中,这也是为什么我们在前面可以通过SecurityContextHolder.getContext().getAuthentication()得到Authentication

这样,认证就结束了,不过这里我们再看一下认证过程中比较关键的方法authenticate()

首先我们知道这个方法是AuthenticationManager接口的方法,而实现这个接口的常用类为ProviderManager,接着我们看一下它的authenticate()方法:
spring security学习总结_第8张图片
我们看一下上面三个划线的位置,第一处是AuthenticationProvider.authenticate()方法,我们看一下,找到它的具体实现类AbstractUserDetailsAuthenticationProvider
spring security学习总结_第9张图片

这些check()方法是UserDetailsChecker接口的
这里写图片描述

可以看到没有返回值,随便找一个实现类,可以看到它的原理就是不抛异常即成功。
spring security学习总结_第10张图片

provider的authenticate()方法绕来绕去,最后又返回了UsernamePasswordAuthenticationToken(它是Authentication的一个实现类),然后擦除凭证并发布认证成功事件。之后就是后面的放到SecurityContextHolder中了。

到这里还没有结束,我们刚才并没有提到认证中的信息比对,直接说认证成功了,那么信息比对是发生在哪里的呢?

我们刚才提到过AbstractUserDetailsAuthenticationProvider,我们回过头看它的authenticate()方法,它是从缓存中取出UserDetails的,那如果缓存中没有呢?
它将执行下面的retrieveUser()方法,具体实现是由DaoAuthenticationProvider来完成的:
spring security学习总结_第11张图片
到这里,我们就快要接触到核心了,我们看到了UserDetailsService接口,它的实现类分别为:
这里写图片描述
好,我们就到此为止吧,不再深入了,基本在这里已经可以看出端倪了,它负责加载用户信息,可以从内存、缓存或数据库中。

在最后,我们还要强调一点概念,Authentication中的信息是认证信息,它的getCredentials()方法获取的是之前提交的密码,而UserDetails是真正的用户信息,它的getPassword()方法才是用户正确的密码;之后的Authentication中的getAuthorities()是通过UserDetailsgetAuthorities()传递而来的。
spring security学习总结_第12张图片

那么我们现在完整的捋一遍整个认证过程:
用户提交帐密,被封装为UsernamePasswordAuthenticationToken,然后我们对它进行认证,比对相应的UserDetails中的相关信息,认证成功后,我们将填充了信息的Authentication(其实这里就是UsernamePasswordAuthenticationToken,当然如果认证失败,这里的Token就会变成AnonymousAuthenticationToken)放到了SecurityContextHolder中,以供我们以后使用。

这里我就偷两张UML类图,可以更清晰的看到整个过程:

spring security学习总结_第13张图片

下面这张可以结合上面那张看,更加清晰:
spring security学习总结_第14张图片

这两张图都引自徐靖峰的文章。

相应的权限关系都是在我们配置的configure(HttpSecurity http)方法中完成的,到时只需验证有无权限即可,这里权限认证没有找到比较好的资料,自己也实在是看不太懂,所以关于spring security的内容就写到这了。

下一篇,我们来看看spring security oauth 2.0.

你可能感兴趣的:(编程之路)