认识 Spring Security 中常用的几个类

1. SecurityContextHolder
SecurityContextHolder 默认使用ThreadLocal策略来存储认证信息

ThreadLocal:与当前线程绑定,用来存储用户信息

获取当前用户信息


Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

2. Authentication
Authentication包含用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息,通过SecurityContextHolder类的getContext().getAuthentication()返回一个认证信息。

Authentication 包含以下几个方法:

接口源码

package org.springframework.security.core;

public interface Authentication extends Principal, Serializable { 
    Collection<? extends GrantedAuthority> getAuthorities(); 

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

getAuthorities():权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。
getCredentials():密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
getDetails():细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的 ip 地址和 sessionId 的值。
getPrincipal():大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一。

3. UserDetails
身份信息封装的一个接口,可以通过 Authentication.getPrincipal()获得相关的实现类。

4. AuthenticationManager

AuthenticationManager认证相关的核心接口,身份管理器负责验证这个Authentication,认证成功后, AuthenticationManager身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication实例。

代码示例

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");
}
}

5. ProviderManager
ProviderManager实现了AuthenticationManager接口,内部会维护一个List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate),不同的认证方式使用不同的AuthenticationProvider

ProviderManager 源码

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {

    // 维护一个 AuthenticationProvider 列表
    private List<AuthenticationProvider> providers = Collections.emptyList();
          
    public Authentication authenticate(Authentication authentication)
          throws AuthenticationException {
       Class<? extends Authentication> toTest = authentication.getClass();
       AuthenticationException lastException = null;
       Authentication result = null;

       // 依次认证
       for (AuthenticationProvider provider : getProviders()) {
          if (!provider.supports(toTest)) {
             continue;
          }
          try {
             result = provider.authenticate(authentication);

             if (result != null) {
                copyDetails(authentication, result);
                break;
             }
          }
          ...
          catch (AuthenticationException e) {
             lastException = e;
          }
       }
       // 如果有 Authentication 信息,则直接返回
       if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
              	 // 移除密码
				((CredentialsContainer) result).eraseCredentials();
			}
             // 发布登录成功事件
			eventPublisher.publishAuthenticationSuccess(result);
			return result;
	   }
	   ...
       // 执行到此,说明没有认证成功,包装异常信息
       if (lastException == null) {
          lastException = new ProviderNotFoundException(messages.getMessage(
                "ProviderManager.providerNotFound",
                new Object[] { toTest.getName() },
                "No AuthenticationProvider found for {0}"));
       }
       prepareException(lastException, authentication);
       throw lastException;
    }
}

6. DaoAuthenticationProvider
DaoAuthenticationProvider实现了AuthenticationProvider类,UserDetailsService加载用户,提交的用户名和密码,被封装成了 UsernamePasswordAuthenticationToken,与 retrieveUser方法返回的UserDetails类进行密码对比,比对密码的过程,用到了 PasswordEncoderSaltSource

7. UML 结构图

Reference
https://www.cnkirito.moe/spring-security-1/