5.2 验证

2017-02-03 21:43:27 9,522 0

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 是如何被实现,阅读核心服务章节。