转载自:https://shimo.im/docs/vpHYCRCtDgPxqtwH/read 感谢军哥
核心功能
认证(身份校验,你是谁),授权(你能干什么),攻击防护(防止伪造身份)
认证过程:
核心组件
- SecurityContextHolder类
SecurityContextHolder 是最基本的对象,它负责存储当前安全上下文信息。即保存着当前用户是什么,是否已经通过认证,拥有哪些权限。。。等等。SecurityContextHolder默认使用ThreadLocal策略来存储认证信息,意味着这是一种与线程绑定的策略。在Web场景下的使用Spring Security,在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。而在非Web场景下,比如Swing环境下,Spring提供在启动时使用策略配置SecurityContextHolder。
源码中的三种策略:
MODE_THREADLOCAL:SecurityContext 存储在线程中。MODE_INHERITABLETHREADLOCAL:SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。MODE_GLOBAL:SecurityContext 在所有线程中都相同。SecurityContextHolder默认使用MODE_THREADLOCAL模式,即存储在当前线程中。
获取有关当前用户的信息
- Authentication类
authentication 直译过来是“认证”的意思,在Spring Security 中Authentication用来表示当前用户是谁,一般来讲你可以理解为authentication就是一组用户名密码信息。Authentication也是一个接口
从这个接口中,我们可以得到用户身份信息,密码,细节信息,认证信息,以及权限列表,具体的详细解读如下:
- getAuthorities(),权限列表,通常是代表权限的字符串列表;
- getCredentials(),密码信息,由用户输入的密码凭证,认证之后会移出,来保证安全性;
- getDetails(),细节信息,Web应用中一般是访问者的ip地址和sessionId;
- getPrincipal(), 最重要的身份信息,一般返回UserDetails的实现类;
- isAuthenticated: 获取当前 Authentication 是否已认证。
- setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。
官方文档里说过,当用户提交登陆信息时,会将用户名和密码进行组合成一个实例UsernamePasswordAuthenticationToken,而这个类是Authentication的一个常用的实现类,用来进行用户名和密码的认证,类似的还有RememberMeAuthenticationToken,它用于记住我功能。
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null);SecurityContextHolder.getContext().setAuthentication(token);
- UserDetails
上文提到了UserDetails接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,具体的实现类对它进行了扩展,首先来看一下源码:
它和Authentication接口类似,都包含了用户名,密码以及权限信息,而区别就是Authentication中的getCredentials来源于用户提交的密码凭证,而UserDetails中的getPassword取到的则是用户正确的密码信息,认证的第一步就是比较两者是否相同,除此之外,Authentication#getAuthorities是认证用户名和密码成功之后,由UserDetails#getAuthorities传递而来。而Authentication中的getDetails信息是经过了AuthenticationProvider认证之后填充的。 - UserDetailsService
UserDetailsService 只有一个方法,就是从特定的地方(一般是从数据库中)加载用户信息。
通常在spring security应用中,我们会自定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其public UserDetails loadUserByUsername(final String login);方法。我们在实现loadUserByUsername方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails,(通常是一个org.springframework.security.core.userdetails.User,它继承自UserDetails) 并返回。在实现loadUserByUsername方法的时候,如果我们通过查库没有查到相关记录,需要抛出一个异常来告诉spring security来“善后”。这个异常是org.springframework.security.core.userdetails.UsernameNotFoundException。 - AuthenticationManager
AuthenticationManager接口只包含一个方法,那就是认证,它是认证相关的核心接口,也是发起认证的出发点。实际业务中可能根据不同的信息进行认证,所以Spring推荐通过实现AuthenticationManager接口来自定义自己的认证方式.Spring提供了一个默认的实现,ProviderManager。AuthenticationManager 的作用就是校验Authentication,如果验证失败会抛出AuthenticationException异常。AuthenticationException是一个抽象类,因此代码逻辑并不能实例化一个AuthenticationException异常并抛出,实际上抛出的异常通常是其实现类,如DisabledException,LockedException,BadCredentialsException等。BadCredentialsException可能会比较常见,即密码错误的时候。 - AuthenticationProvider
AuthenticationProvider接口提供了两个方法,一个是真正的认证,另一个是满足什么样的身份信息才进行如上认证。Spring 提供了几种AuthenticationProvider的实现:
DaoAuthenticationProvider从数据库中读取用户信息验证身份
AnonymousAuthenticationProvider匿名用户身份认证
RememberMeAuthenticationProvider已存cookie中的用户信息身份认证
AuthByAdapterProvider使用容器的适配器验证身份
CasAuthenticationProvider根据Yale中心认证服务验证身份,用于实现单点登陆
JaasAuthenticationProvider从JASS登陆配置中获取用户信息验证身份
RemoteAuthenticationProvider根据远程服务验证用户身份
RunAsImplAuthenticationProvider对身份已被管理器替换的用户进行验证
X509AuthenticationProvider从X509认证中获取用户信息验证身份
TestingAuthenticationProvider单元测试时使用
当然也可以自己实现AuthenticationProvider接口来自定义认证。这里我们基于最常用的DaoAuthenticationProvider来详细解释一下:
在Spring Security中。提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken,而根据用户名加载用户的任务则是交给了UserDetailsService,在DaoAuthenticationProvider中,对应的方法便是retrieveUser,虽然有两个参数,但是retrieveUser只有第一个参数起主要作用,返回一个UserDetails。还需要完成UsernamePasswordAuthenticationToken和UserDetails密码的比对,这便是交给additionalAuthenticationChecks方法完成的,如果这个void方法没有抛异常,则认为比对成功。
总结: