前言
Spring Security作为Spring家族里的一个重要成员,目的是对用户认证和鉴权进行处理。用过Spring Security的人应该会觉得配置很多,运行机制很复杂,难以驾驭。下面我们从一个简单的小程序开始,慢慢的揭开Spring Security的面纱。
从一个小程序开始
下面是一个来自Spring官方的样例,代码很简单。真实情况会更加复杂,我们先从简单的开始。
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<>();
static {
AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
}
public Authentication authenticate(Authentication auth) throws AuthenticationException {
if (auth.getName().equals(auth.getCredentials())) {
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
运行后,如果输入的用户名和密码一样,则验证通过。运行结果如下:
Please enter your username:
admin
Please enter your password:
123456
Authentication failed: Bad Credentials
Please enter your username:
admin
Please enter your password:
admin
Successfully authenticated. Security context contains: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: Principal: admin; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
AuthenticationManager和Authentication
在上面的小程序中,使用到SampleAuthenticationManager类,实现了AuthenticationManager接口。AuthenticationManager接口中只定义了一个authenticate方法,该方法的输入和输出类型都是Authentication。Authentication接口,存储了用户输入的用户名(Principal.getName)、密码(getCredentials)和权限(getAuthorities)等认证相关信息。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
此处的验证是判断用户名和密码是否相等,而真实环境会拿用户输入的用户和密码,与数据库中的用户名和密码进行比对,如果一样,则验证通过。当验证通过时,就返回一个新实例化的authentication,包含了用户名、密码和权限,并存放到SecurityContext中;当验证失败,则抛出一个BadCredentialsException异常,表示认证失败。
SecurityContext和SecurityContextHolder
SecurityContext是用来提供和存放Authentication认证信息的。SecurityContextHolder是用来提供和存放SecurityContext的。SecurityContextHolder是提供了context的get和set的静态方法,这样我们就可以后程序非常方便的获取SecurityContext,再获取SecurityContext中的Authentication,也就是认证信息。
需要注意的是,SecurityContext有三种存放策略,默认使用ThreadLocal方式,也就是说对于每个线程都会有一个SecurityContext副本。
final class ThreadLocalSecurityContextHolderStrategy implements
SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
public void clearContext() {
contextHolder.remove();
}
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
过程
从一个小程序开始,可以了解到认证过程经过了以下几步
- 用户输入用户名和密码
- 生成一个Authentication
- AuthenticationManager对Authentication进行判断是否认证通过
- 如果认证失败,抛出一个AuthenticationException
- 如果认证成功,则返回一个新的Authentication,它包含了用户名、密码和权限
- 将新的认证信息,存放到SecurityContext中
源代码
github源代码:https://github.com/camellibby/security-demo
结语
上面是一个简单的小程序,真实的情况会更加复杂,后面的文章在此基础上演变,逐步靠近真实的情况。