本篇介绍登录时的认证流程,及其相关的重要概念接口等,现在只需要有一个基本概念,以后将以此为基础介绍自定义配置,到时结合本篇会更加清晰。
SecurityContext
用户登录时,会将用户相关信息组装成一个Authentication对象,而SecurityContext的主要作用就是保存Authentication。并且 SecurityContext会通过session来维持状态,所以登录后每次请求都可以从session中获取当前用户Authentication,这是系统内部实现的。我们可直接使用系统封装好的方法来获取当前用户Authentication对象,如下
SecurityContextHolder.getContext().getAuthentication();
Authentication
这个Authentication也是第2篇表达示中的authentication,其源码如下
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();//用户权限集合
Object getCredentials();//密码类参数
Object getDetails(); //其他登录参数
Object getPrincipal();//用户信息,如用户名
boolean isAuthenticated();//认证状态:是否认证过
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;//设置认证状态
- 第3篇的UserDetails,就是这里的getPrincipal(),用户登录后,通过以下方法就可以获取到UserDetails。
SecurityContextHolder.getContext().getAuthentication().getPrincipal()
- Object类型表明,你在自定义配置实现时自由度非常高。UserDetails只是系统默认的getPrincipal()类型,在自定义配置中可以是任何类型,以后会讲。
- getDetails()是用户/密码以外的额外参数,根据你的业务需要而定,比如登录时可能还会使用验证码参数。
- isAuthenticated用于标识当前Authentication是否认证过,下面会讲。
- UsernamePasswordAuthenticationToken是Authentication的默认实现类,了解下其源码会更清晰。
AuthenticationManager
用户登录时,首先生成的Authentication是未认证状态,交由AuthenticationManager认证。AuthenticationManager会将Authentication中的用户名/密码与UserDetails中的用户名/密码对比,完成认证工作,认证成功后会生成一个已认证状态的Authentication,此时就会写入到SecurityContext。在用户有后续请求时,可从Authentication中检查权限
认证流程
以下例子出自官方文档,这是一个最简化的认证流程,而且省略了UserDetails,只做了简单认证。
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
Authentication request = new UsernamePasswordAuthenticationToken(name, password);
//交给AuthenticationManager 认证
Authentication result = am.authenticate(request);
//将已认证的Authentication放入SecurityContext
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(auth.getCredentials())) {
//认证成功,生成已认证Authentication,比未认证多了权限
return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES);
}
throw new BadCredentialsException("Bad Credentials");
}
}
认证过滤器
main方法中的代码就是模拟认证过滤器做的事,当认证过滤器拦截到/login登录请求,就会开始执行。