一直对项目里的安全配置比较陌生,这次总结一下项目里的那些关于security配置
1、 核心组件
这一节主要介绍一些在 Spring Security 中常见且核心的 Java 类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。
1.1 SecurityContextHolder
SecurityContextHolder 用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限… 这些都被保存在 SecurityContextHolder 中。SecurityContextHolder 默认使用 ThreadLocal 策略来存储认证信息。看到 ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security 在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。但这一切的前提,是你在 web 场景下使用 Spring Security,而如果是 Swing 界面,Spring 也提供了支持,SecurityContextHolder 的策略则需要被替换,鉴于我的初衷是基于 web 来介绍 Spring Security,所以这里以及后续,非 web 的相关的内容都一笔带过。
获取当前用户的信息
因为身份信息是与线程绑定的,所以可以在程序的任何地方使用静态方法获取用户信息。一个典型的获取当前登录用户的姓名的例子如下所示:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
getAuthentication()返回了认证信息,再次 getPrincipal() 返回了身份信息,UserDetails 便是 Spring 对身份信息封装的一个接口。Authentication 和 UserDetails 的介绍在下面的小节具体讲解,本节重要的内容是介绍 SecurityContextHolder 这个容器。
etAuthentication()返回了认证信息,再次 getPrincipal() 返回了身份信息,UserDetails 便是 Spring 对身份信息封装的一个接口。Authentication 和 UserDetails 的介绍在后面的小节具体讲解,现在重要的内容是介绍 SecurityContextHolder 这个容器。
1.2 Authentication
package org.springframework.security.core;// <1>
public interface Authentication extends Principal, Serializable {
// <1>
Collection<? extends GrantedAuthority> getAuthorities(); // <2>
Object getCredentials();// <2>
Object getDetails();// <2>
Object getPrincipal();// <2>
boolean isAuthenticated();// <2>
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
<1> Authentication 是 spring security 包中的接口,直接继承自 Principal 类,而 Principal 是位于 java.security 包中的。可以见得,Authentication 在 spring security 中是最高级别的身份 / 认证的抽象。
<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
1.1 节中,authentication.getPrincipal() 返回了一个 Object,我们将 Principal 强转成了 Spring Security 中最常用的 UserDetails,这在 Spring Security 中非常常见,接口返回 Object,使用 instanceof 判断类型,强转成对应的具体实现类。接口详细解读如下:
getAuthorities(),权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。
getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
getDetails(),细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的 ip 地址和 sessionId 的值。
getPrincipal(),敲黑板!!!最重要的身份信息,大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一。UserDetails 接口将会在下面的小节重点介绍。
Spring Security 是如何完成身份认证的?
1 用户名和密码被过滤器获取到,封装成 Authentication, 通常情况下是 UsernamePasswordAuthenticationToken 这个实现类。
2 AuthenticationManager 身份管理器负责验证这个 Authentication
3 认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication 实例。
4 SecurityContextHolder 安全上下文容器将第 3 步填充了信息的 Authentication,通过 SecurityContextHolder.getContext().setAuthentication(…) 方法,设置到其中。
这是一个抽象的认证流程,而整个过程中,如果不纠结于细节,其实只剩下一个 AuthenticationManager 是我们没有接触过的了,这个身份管理器我们在后面的小节介绍。将上述的流程转换成代码,便是如下的流程:
public class AuthenticationExample {
private static AuthenticationManager am = new SampleAuthenticationManager();
public static void main(String[] args) throws Exception {
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(result);
break;
} catch(AuthenticationException e) {
System.out.println("Authentication failed:" + e.getMessage());
}
}
System.out.println("Successfully authenticated. Security context contains:" +
SecurityContextHolder.getContext().getAuthentication());
}
}
class SampleAuthenticationManager implements AuthenticationManager {
static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(