5.2 验证
Spring Security可以参与进许多不同的验证环境。不过我们建议人们使用Spring Security执行验证时,不要整合进容器中已经存在的验证机制,但这仍然也是支持的,就像整合你自己的验证系统一样。
Spring Security中的验证authentication 到底是什么?
让我们考虑一个每个人都熟悉的标准验证场景:
1、一个用户被提示使用用户名和密码登录
2、系统成功的验证了用户名与密码是匹配的
3、获取到用户的上下文信息(角色列表等)
4、建立这个用户的安全上下文(security context )
5、用户可能继续进行一些受到访问控制机制保护的操作,访问控制机制会依据当前安全上下文信息检查这个操作所需的权限。
前三条组成了验证过程,因此我们要看一下在Spring Security中这是如何发生的:
1、用户名和密码被获取到,并放入一个 UsernamePasswordAuthenticationToken
实例中( Authentication接口的一个实例,我们之前已经看到过)。
2、这个token被传递到一个 AuthenticationManager
实例中进行验证
3、在成功验证后, AuthenticationManager返回一个所有字段都被赋值的 Authentication
对象实例
4、通过调用 SecurityContextHolder.getContext().setAuthentication(…)创建安全上下文,通过返回的验证对象进行传递。
从这个角度来说,用户被认为已经成功验证。让我们来看一段样例代码:
import org.springframework.security.authentication.*; import org.springframework.security.core.*; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; 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(auth.getCredentials())) { return new UsernamePasswordAuthenticationToken(auth.getName(), auth.getCredentials(), AUTHORITIES); } throw new BadCredentialsException("Bad Credentials"); } }
这里我们编写了一个小程序,要求用户输入用户名和密码并执行以上的验证流程。我们这里实现的 AuthenticationManager
将会任何用户输入的用户名和密码是否相同。为了每个用户分配一个单独的角色。上面代码输出将会类似以下:
Please enter your username: bob Please enter your password: password Authentication failed: Bad Credentials Please enter your username: bob Please enter your password: bob Successfully authenticated. Security context contains: \ org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: \ Principal: bob; Password: [PROTECTED]; \ Authenticated: true; Details: null; \ Granted Authorities: ROLE_USER |
注意你不需要编写这样的代码。这个过程通常情况下会在内部发生,例如在一个web验证的过滤器中。我们在这里介绍这段代码仅仅是为了展示在Spring Security中构建验证过程是非常简单的。用户在 SecurityContextHolder
包含了一个完全赋值的 Authentication
d的时候被验证。
直接设置SecurityContextHolder中的内容
事实上,Spring Security并不关心你如何将 Authentication
对象放入 SecurityContextHolder中。唯一的关键要求是在 AbstractSecurityInterceptor
验证一个用户请求之前确保 SecurityContextHolder
包含一个用于表示principal的 Authentication
对象。
你可以(许多用户都这样做)编写自己的Filter或者MVC controller,来提供与那些不是基于Spring Security的验证系统的互操作能力。例如,你可能会使用容器管理的验证机制,通过ThreadLocal或者JNDI地址来使当前用户可用。或者你可能在为一个有着遗留验证系统的公司工作。在这类场景下,很容易可以让Spring Security工作,并且仍然提供验证能力。所有你需要做的是编写一个过滤器,从某个位置读取第三方用户信息,构建一个特定的Spring Security Authentication
对象,并将其放入 SecurityContextHolder中。在这种情况下,你需要考虑在内置的验证基础结构上自动应用这些。例如,你可能需要在返回给客户端响应之前,预先创建一个Http Session对象来为不同线程缓存安全上下文。
如果你想知道在现实世界的案例中 AuthenticationManager
是如何被实现,阅读核心服务章节。