转自https://pjmike.github.io/2018/10/12/%E6%B5%85%E6%9E%90Spring-Security-%E6%A0%B8%E5%BF%83%E7%BB%84%E4%BB%B6/
上一篇我们主要讲述了如何搭项目,这里我们就来简单探究一下原理
Spring Security的核心类
Spring Security的核心类主要包括以下几个:
- SecurityContextHolder: 存放身份信息的容器
- Authentication: 身份信息的抽象接口
- AuthenticationManager: 身份认证器,认证的核心接口
- UserDetailsService: 一般用于从数据库中加载身份信息
- UserDetails: 相比Authentication,有更详细的身份信息
SecurityContextHolder、Securityontext和Authentication
SecurityContextHolder
用于存储安全上下文(security context)的信息,即一个存储身份信息,认证信息等的容器。SecurityContextHolder
默认使用 ThreadLocal
策略来存储认证信息,即一种与线程绑定的策略,每个线程执行时都可以获取该线程中的 安全上下文(security context),各个线程中的安全上下文互不影响。而且如果说要在请求结束后清除安全上下文中的信息,利用该策略Spring Security也可以轻松搞定。
因为身份信息时与线程绑定的,所以我们可以在程序的任何地方使用静态方法获取用户信息,一个获取当前登录用户的姓名的例子如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { String username = ((UserDetails)principal).getUsername(); } else { String username = principal.toString(); }
getAuthentication()
方法返回了认证信息,准确的说是一个 Authentication
实例,Authentication
是 Spring Security 中的一个重要接口,直接继承自 Principal类,该接口表示对用户身份信息的抽象,接口源码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public interface Authentication extends Principal, Serializable { //权限信息列表,默认是 GrantedAuthority接口的一些实现 Collection<? extends GrantedAuthority> getAuthorities(); //密码信息,用户输入的密码字符串,认证后通常会被移除,用于保证安全 Object getCredentials(); //细节信息,web应用中通常的接口为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值 Object getDetails(); //身份信息,返回UserDetails的实现类 Object getPrincipal(); //认证状态,默认为false,认证成功后为 true boolean isAuthenticated(); //上述身份信息是否经过身份认证 void setAuthenticated(boolean var1) throws IllegalArgumentException; }
AuthenticationManager、ProviderManager 和 AuthenticationProvider
框架用来实现登录的功能
待明确源码
UserDetailsService 和 UserDetails
UserDetailsService
简单说就是加载对应的UserDetails
的接口(一般从数据库),而UserDetails
包含了更详细的用户信息,定义如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
UserDetails 接口与 Authentication接口相似,它们都有 username、authorities。它们的区别如下:
- Authentication 的 getCredentials() 与 UserDetails 中的 getPassword() 不一样,前者是用户提交的密码凭证,后者是用户正确的密码,(一般是从数据库中载入的密码),
AuthenticationProvider
就会对两者进行对比。 - Authentication 中的 getAuthorities() 实际上是由 UserDetails 的 getAuthorities()传递形成的。
- Authentication 中的 getUserDetails() 中的 UserDetails 用户详细信息时经过
AuthenticationProvider
认证之后填充的。
认证过程的官方样例
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public class SpringSecuriryTestDemo { private static AuthenticationManager am = new SampleAuthenticationManager(); public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); while (true) { System.out.println("Please enter your username:"); String name = in.readLine(); System.out.println("Please enter your password:"); String password = in.readLine(); try { Authentication request = new UsernamePasswordAuthenticationToken(name, password); Authentication result = am.authenticate(request); SecurityContextHolder.getContext().setAuthentication(request); break; } catch (AuthenticationException e) { System.out.println("Authentication failed: " + e.getMessage()); } } System.out.println("Successfully authenticated. Security context contains: " + SecurityContextHolder.getContext().getAuthentication()); } static class SampleAuthenticationManager implements AuthenticationManager { static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>(); static { AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER")); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication.getName().equals(authentication.getCredentials())) { return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES); } throw new BadCredentialsException("Bad Credentials"); } } }
结果
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
Please enter your username: pjmike Please enter your password: 123 Authentication failed: Bad Credentials Please enter your username: pjmike Please enter your password: pjmike Successfully authenticated. Security context contains: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: Principal: pjmike; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
了解了简单的认证过程,下一篇在看一下spring security的filter chain