前面几篇直接讲了Demo,但是可能还是有点混乱,这里再将整个认证过程梳理一下,加深对前面的理解
对一个系统来说,标准的安全身份验证方案应该按如下步骤:
其中,前四个步骤构成了认证过程,在SpringSecurity中流程如下:
来看一下涉及到的类
继承了Principal类和序列化
public interface Authentication extends Principal, Serializable {
/**
* 获得用户的权限信息列表
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 密码信息,但认证后通常会被移除
*/
Object getCredentials();
/**
*
* 存储额外的认证请求信息,可能是IP地址等
*/
Object getDetails();
/**
* 犯规身份信息
*/
Object getPrincipal();
/**
* @return true if the token has been authenticated and the
*/
boolean isAuthenticated();
/**
*/
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
从上面的源码中可以看出,由这个接口的实现类,我们可以得到用户相关的
权限列表信息,密码、用户其他详细信息等重要信息
AuthenticationManager是一个接口,正常系统应用中,登录是可以通过多种密码验证方式,如手机号+验证码,用户名/邮箱+密码等。这个时候就需要调用SpringSecurity对它的默认实现ProviderManager,但是它本身并不处理身份验证请求,而是委托给已经配置的AuthenticationProvider列表,依次查看是否可以执行身份验证,每个AuthenticationProvider程序都会引发一场或返回完全填充的Authentication对象。如上面所说的几种验证方式,只要通过一个AuthenticationProvider并返回Authentication对象,即可通过登录验证
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, null);
}
public ProviderManager(List<AuthenticationProvider> providers,
AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
private void checkState() {
if (parent == null && providers.isEmpty()) {
throw new IllegalArgumentException(
"A parent AuthenticationManager or a list "
+ "of AuthenticationProviders is required");
}
}
/**
*
*/
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
//遍历所有的AuthenticationProvider,因此进行认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
//如果有authentication信息
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// 擦除密码
((CredentialsContainer) result).eraseCredentials();
}
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// 抛出异常
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
public List<AuthenticationProvider> getProviders() {
return providers;
}
}
SecurityContextHolder用来存储应用程序当前安全上下文的详细信息,其中包括当前使用该应用程序的认证详细信息。默认情况下,其使用ThreadLocal来存储这些详细信息,将其与线程绑定。当用户请求被处理后,ThreadLocal中的认证信息会被清除,SpringSecurity会自动解决此问题。
因为当前的认证信息与线程绑定,所以获取当前用户信息可以使用以下方式:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
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");
}
}
以上是用Code做一个简单的总结,整个过程以此流程在内部发生
从上面可以看出,所有的Authentication实现都存储了GrantedAuthority对象列表,它代表了已授予委托人的权限。GrantedAuthority对象列表在AuthenticationManager中被插入,并在稍后由AccessDecisionManager读取并作出授权决定
GrantedAuthority接口只有一个方法:
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
这个方法使AccessDecisionManager获得一个代表GrantedAuthority的String。通过返回的String,GrantedAuthority可以很容易读取到信息
Spring Security提供了拦截器,该拦截器控制对安全对象(例如方法调用或Web请求)的访问。AccessDecisionManager会做出一个调用前处理来决定是否允许继续调用请求。
AccessDecisionManager
AccessDecisionManager负责做出最终的访问控制决策,接口包含以下三种方法:
public interface AccessDecisionManager {
/**
* 通过传入的参数,对访问权限决策(投票)
*/
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;
/**
* 根据传入的类决定是否对该类做决策
*/
boolean supports(ConfigAttribute attribute);
/**
* 根据传入的类决定是否对该类做决策
*/
boolean supports(Class<?> clazz);
}
其中decide方法中的参数,authentication代表请求访问的主体;object指代可以对其实施安全性(例如授权决策)的任何对象,最常见的示例是方法调用和Web请求;configAttributes代指属性列表,如配置的ROLE_xxx等属性
基于投票机制的实现
一系列的AccessDecisionVoter实现将投票决定授权决策,AccessDecisionManager将基于投票决定是否抛出AccessDeniedException异常
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
vote的实现返回一个int值,对应其中的静态常量ACCESS_GRANTED,ACCESS_ABSTAIN和ACCESS_DENIED 。如果vote实现对授权无意见,则返回ACCESS_ABSTAIN。如果有意见,必须返回ACCESS_GRANTED或ACCESS_DENIED。
由Spring提供的最常用的AccessDecisionVoter是简单的RoleVoter,它将配置属性视为简单的角色名称,如果用户被赋予了该角色,则投票将授予访问限制
如:如果属性以ROLE_开头,如果GrantedAuthority返回的String中含有一个或多个相同的,则投票授予访问权限
AuthenticatedVoter用来区分匿名,完全认证和记住我的认证用户。如有的站点使用记住我身份验证时进行某些访问限制,但是要求用户通过登录以惊醒完全访问来确认身份
调用前处理是SpringSecurity鉴权的重要过程,这里通过源码来分析下
调用入口在FilterSecurityInterceptor类中,继承了AbstractSecurityInterceptor,实现了Filter接口,其他方法省略,仅保留了调用前处理的方法源码
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
//调用入口,查看下面的invoke方法
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
//调用beforeInvocation方法,详见下面的AbstractSecurityInterceptor类源码
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
}
beforeInvocation方法源码如下
protected InterceptorStatusToken beforeInvocation(Object object) {
......
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
......
Authentication authenticated = authenticateIfRequired();
......
// Attempt authorization
try {
//可以看到,在这里进入了decide方法
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
默认的AccessDecisionManager在SpringSecurity中由AffirmativeBased实现,当然也其他实现,现在先看下这个
public class AffirmativeBased extends AbstractAccessDecisionManager {
public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}
//decide过程
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
//调用voter进行投票
int result = voter.vote(authentication, object, configAttributes);
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}
分析下RoleVoter,其他是类似的
public class RoleVoter implements AccessDecisionVoter<Object> {
private String rolePrefix = "ROLE_";
public String getRolePrefix() {
return rolePrefix;
}
......
//判断是否基于ROLE_开头
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
}
else {
return false;
}
}
.......
//进入角色判断
public int vote(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) {
if (authentication == null) {
//请求访问的主体为null,拒绝
return ACCESS_DENIED;
}
int result = ACCESS_ABSTAIN;
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {
//如果角色符合
if (this.supports(attribute)) {
//暂时仍是拒绝,需再判断权限
result = ACCESS_DENIED;
// Attempt to find a matching granted authority
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
//权限通过后,则改为授权通过
return ACCESS_GRANTED;
}
}
}
}
return result;
}
//获取权限
Collection<? extends GrantedAuthority> extractAuthorities(
Authentication authentication) {
return authentication.getAuthorities();
}
}