这篇“Spring Security认证机制是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Spring Security认证机制是什么”文章吧。
Username & Password
OAuth3.0 Login
SAML 2.0 Login
Remember Me
JAAS Authentication
Pre-authentication Scenarios
X509 Authentication
这里使用Spring Boot 2.7.4版本,对应Spring Security 5.7.3版本
首先明确两个概念:
Principle : This interface represents the abstract notion of a principal, which can be used to represent any entity, such as an individual, a corporation, and a login id. 简单来说 可以认为是 唯一确定用户的 一个 userId ,但这个Principle是一个接口,具体参考 java.security.Principal
Credential : 通常就是一个密码,但他不是接口,而是通过接口Authentication#getCredentials来获取,返回一个Object类型。
Spring Security提供了以下几个核心类:
SecurityContextHolder : Spring Security用来保存被认证用户的详细信息(默认使用ThreadLocal保存);
SecurityContext : 从 SecurityContextHolder 获取得到,该接口提供被认证用户的 Authentication信息;
Authentication : 代表"认证",其中包含Principle和Credential,通过AuthenticationManager#authenticate来认证一个Authentication,该方法接受一个未认证的Authentication并返回一个认证后的Authentication。
GrantedAuthority : 认证后的Principle包含的权限
AuthenticationManager : 实施认证行为的接口,该类为函数式接口,只有一个方法: Authentication authenticate(Authentication authentication) throws AuthenticationException;
ProviderManager : the most common implementation of AuthenticationManager.
AuthenticationProvider : 提供认证方式的接口,组合在ProviderManager中。
先来看一个使用SecurityContextHolder和SecurityContext完成认证的案例:
SecurityContext context = SecurityContextHolder.createEmptyContext(); // 手动生成一个Authentication,实际一般是通过数据库查出来生成的 Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER"); context.setAuthentication(authentication); SecurityContextHolder.setContext(context);
默认 情况下 SecurityContext存储使用ThreadLocal方式,这个就是ThreadLocal的一个用处,避免函数间参数传递的复杂性,只要处于一个线程,就可以直接获取而不需要通过函数参数返回值来传递。
注意:在使用ThreadLocal时,由于键是其this本身,是一个弱引用,而值只能是强引用,所以ThreadLocal不用时需要手动clear。而Spring Security中,在 FilterChainProxy中完成这一清除工作,如下:
public class FilterChainProxy extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ... try { ... // 执行Spring Security 的 SecurityFilterChain doFilterInternal(request, response, chain); } catch (Exception ex) { ... } finally { // *************** // 清除ThreadLocal // *************** SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } }
但有些情形是用不了ThreadLocal的,For example, a Swing client might want all threads in a Java Virtual Machine to use the same security context 。针对其他情况,SecurityContextHolder提供了4种模式,当然也可以自定义:
MODE_THREADLOCAL
MODE_GLOBAL
MODE_INHERITABLETHREADLOCAL
MODE_PRE_INITIALIZED
可以通过2种方式去修改模式:
set a system property
a static method on SecurityContextHolder
public class SecurityContextHolder { // 存储SecurityContext的模式,默认 ThreadLocal存储 public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL"; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL"; public static final String MODE_GLOBAL = "MODE_GLOBAL"; private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED"; public static final String SYSTEM_PROPERTY = "spring.security.strategy"; // 通过系统参数指定 存储模式 private static String strategyName = System.getProperty(SYSTEM_PROPERTY); // 实际存储SecurityContext的接口(类) private static SecurityContextHolderStrategy strategy; private static int initializeCount = 0; static { // 初始化 initialize(); } private static void initialize() { initializeStrategy(); initializeCount++; } private static void initializeStrategy() { ... if (!StringUtils.hasText(strategyName)) { // Set default 默认 ThreadLocal strategyName = MODE_THREADLOCAL; } if (strategyName.equals(MODE_THREADLOCAL)) { strategy = new ThreadLocalSecurityContextHolderStrategy(); return; } ... } // 修改Strategy public static void setStrategyName(String strategyName) { SecurityContextHolder.strategyName = strategyName; initialize(); } // 自定义Strategy public static void setContextHolderStrategy(SecurityContextHolderStrategy strategy) { Assert.notNull(strategy, "securityContextHolderStrategy cannot be null"); SecurityContextHolder.strategyName = MODE_PRE_INITIALIZED; SecurityContextHolder.strategy = strategy; initialize(); } public static SecurityContext getContext() { return strategy.getContext(); } public static void setContext(SecurityContext context) { strategy.setContext(context); } public static void clearContext() { strategy.clearContext(); }
可以看到,SecurityContextHolder实际上是一个门面,具体的Context存储在SecurityContextHolderStrategy中,来看看该接口默认的实现 ThreadLocalSecurityContextHolderStrategy :
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { // ThreadLocal private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); @Override public void clearContext() { contextHolder.remove(); } @Override public SecurityContext getContext() { SecurityContext ctx = contextHolder.get(); if (ctx == null) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } @Override public void setContext(SecurityContext context) { Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); contextHolder.set(context); } @Override public SecurityContext createEmptyContext() { return new SecurityContextImpl(); } }
public interface AuthenticationManager { // 传入一个待认证的Authentication // 返回 a fully authenticated object including credentials Authentication authenticate(Authentication authentication) throws AuthenticationException; }
AuthenticationManager的最常用实现类 ProviderManager,ProviderManager调用多个AuthenticationProvider来认证传入的Authentication,只要有一个认证成功即可返回(返回nonNull),否则会抛出异常,AuthenticationManager默认支持三种异常:
DisabledException : 用户账号被disabled
LockedException : 用户账号被locked
BadCredentialsException : credentials错误(密码错误)
/** * ProviderManager中的List<AuthenticationProvider>会按顺序认证,知道有一个返回非空。 * 如果后面的 AuthenticationProvider返回非空认证结果,前面抛出的异常统统清除;如果后面还有异常,以第一个异常为准 * * 该类中有一个 parent 的字段,类型也为AuthenticationManager, * 如果该类中的List<AuthenticationProvider>都不能认证,会调用parent认证,这个不常用。 * * 事件发布: * ProviderManager中认证事件发布委托给 AuthenticationEventPublisher 实现,默认是空实现。 * parent 的 ProviderManager中不要实现 Publisher,否则会重复发布。 **/ public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { // 事件发布,默认空实现 private AuthenticationEventPublisher eventPublisher = new NullEventPublisher(); // 实际认证的AuthenticationProvider private List<AuthenticationProvider> providers = Collections.emptyList(); // 父级认证Manager private AuthenticationManager parent; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } ... result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } ... } // 该类中AuthenticationManager都判断完了,结果还是空,调用parent开始认证 if (result == null && this.parent != null) { // Allow the parent to try. parentResult = this.parent.authenticate(authentication); } ... // AbstractAuthenticationToken就是Authentication接口的实现类,模板模式 // 常用的UsernamePasswordAuthenticationToken和OAuth3AuthenticationToken都extends这个Abstract类 private void copyDetails(Authentication source, Authentication dest) { if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) { AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest; token.setDetails(source.getDetails()); } } }
可以看到 ProviderManager implements AuthenticationManager将认证工作进一步分配给 AuthenticationProvider,而这个AuthenticationProvider会根据支持的认证方式来认证,所以这个接口除了认证方法还有一个是否支持认证的判断方法:
public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication);
AuthenticationProvider常见的实现类有:
DaoAuthenticationProvider : An AuthenticationProvider implementation that retrieves user details from a UserDetailsService. 用于UsernamePassword认证方式
OAuth3AuthorizationCodeAuthenticationProvider : 在授权服务器上认证authorization_code,拿着code换accessToken
OAuth3LoginAuthenticationProvider : 在授权服务器上认证authorization_code,拿着code换accessToken(这一步委托给上面的 OAuth3AuthorizationCodeAuthenticationProvider执行了),此外,还会拿着accessToken换取UserInfo,属于上面的加强版,OAuth3.0 Login一般都是调用这个Provider。
OidcAuthorizationCodeAuthenticationProvider : 同上,不过在OAuth3.0基础上加了OpenID Connect协议。
以上就是关于“Spring Security认证机制是什么”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。