SpringSecurity 源码分析

354 阅读8分钟

原理

Spring Security是一个提供了认证、授权和防止常见攻击的框架

提供了很多基础的过滤器,当客户端发起请求的时候,经过容器已经创建的过滤器。下面是图片举例

08.jpg

再来看一下流程

05.jpg

源码分析

  • 用户发起http请求首先会登陆,先来配置登陆
  • 第一个过滤器 UsernamePasswordAuthenticationFilter 继承AbstractAuthenticationProcessingFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 如果不是登陆验证的请求直接放行
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            try {
                // 登录验证的核心方法,具体实现是在UsernamePasswordAuthenticationFilter
                Authentication authenticationResult = this.attemptAuthentication(request, response);
                if (authenticationResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authenticationResult, request, response);
                if (this.continueChainBeforeSuccessfulAuthentication) {
                    chain.doFilter(request, response);
                }
                // 验证成功后的处理逻辑
                this.successfulAuthentication(request, response, chain, authenticationResult);
            } catch (InternalAuthenticationServiceException var5) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
                this.unsuccessfulAuthentication(request, response, var5);
            } catch (AuthenticationException var6) {
                // 验证失败后的处理逻辑
                this.unsuccessfulAuthentication(request, response, var6);
            }

        }
    }
  • 说下验证成功之后的逻辑
// 登陆成功
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
// 登录失败
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // SecurityContextHolder 他创建了一个线程池保存用户信息,后面可以直接获取用户信息
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authResult);
        SecurityContextHolder.setContext(context);
        this.securityContextRepository.saveContext(context, request, response);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
        }
        // 这里是记住登录状态的功能
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        // 登录成功后的相关处理,例如跳转到首页
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
  • 成功和失败的逻辑

看一下登录成功SavedRequestAwareAuthenticationSuccessHandler类中的onAuthenticationSuccess方法

它在用户成功完成身份验证后被调用。这个方法有两个参数:request 和 response,分别代表 HTTP 请求和响应。在 onAuthenticationSuccess 方法中,可以让redis保存用户信息以及生产一串token返回给前端。

可以通过实现 AuthenticationSuccessHandler 接口并覆盖 onAuthenticationSuccess 方法来自定义成功身份验证后的行为。

可以通过实现 AuthenticationFailureHandler 接口并覆盖 onAuthenticationFailure 方法来自定义身份验证失败后的行为。

  • 再来看一下身份验证的处理逻辑

在UsernamePasswordAuthenticationFilter 类中继承AbstractAuthenticationProcessingFilter

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 判断是否是POST请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username.trim() : "";
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            // 根据用户名和密码生成一个身份认证令牌,其实就是保存一些用户登录的信息
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
            this.setDetails(request, authRequest);
            // 调用这个AuthenticationManager接口类中的authenticate验证接口
            // 把令牌传到 AuthenticationManager 进行验证,我们
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
  • 来看一下验证的
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    // 用户信息
    private final Object principal;
    // 用户凭证
    private Object credentials;
    
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials, 
    // 下面两行是权限信息
    Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }
    // 第一次验证时调用此方法
    public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {
        return new UsernamePasswordAuthenticationToken(principal, credentials);
    }
    // 已经验证过了,可以调用此方法
    public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
    }
  • 返回到下面这里,继续看getAuthenticationManager
// 调用这个AuthenticationManager接口类中的authenticate验证接口
// 把令牌传到 AuthenticationManager 进行验证,我们
return this.getAuthenticationManager().authenticate(authRequest);

发现只是个接口,实现是在ProviderManager

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

springsecurity会提供许多AuthenticationProvider,而ProviderManager这个类就处理这些AuthenticationProvider

  1. 用于管理身份验证(Authentication)和授权(Authorization)提供者的集合。
  2. 它可以帮助组织、调用和协调多个身份验证和授权提供者,以便完成身份验证和授权的过程。
  3. ProviderManager 可以根据配置决策选择哪个身份验证或授权提供者
  4. 如果一个提供者无法成功完成认证或授权,它会尝试使用其他提供者来处理请求。
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    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;
        int currentPosition = 0;
        int size = this.providers.size();
        Iterator var9 = this.getProviders().iterator();
         //在容器启动时,springsecurity已经初始化了一些provider,其中就有DaoAuthenticationProvider,其主要是用户名和密码验证的
        while(var9.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            //如果provider不支持UsernamePasswordAuthenticationToken,就跳到下一个provider
            if (provider.supports(toTest)) {
                if (logger.isTraceEnabled()) {
                    Log var10000 = logger;
                    String var10002 = provider.getClass().getSimpleName();
                    ++currentPosition;
                    var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));
                }

                try {
                    // DaoAuthenticationProvider就是这个provider
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                parentResult = this.parent.authenticate(authentication);
                result = parentResult;
            } catch (ProviderNotFoundException var12) {
            } catch (AuthenticationException var13) {
                parentException = var13;
                lastException = var13;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

}

上面这个类的内容不是重点,重点是这个DaoAuthenticationProvider,而它又是继承AbstractUserDetailsAuthenticationProvider这个抽象类,因此我们先来了解一下它

  • AbstractUserDetailsAuthenticationProvider提供了一些通用的身份验证逻辑
  • 但是需要具体的子类来实现其中的一些关键功能。
  • 通常会使用DaoAuthenticationProvider等具体的子类来完成身份验证任务。
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = this.determineUsername(authentication);
        boolean cacheWasUsed = true;
        // 根据用户名从缓存中获取用户信息
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                // 调用实现类的DaoAuthenticationProvider的retrieveUser,这里会得到保存在内存中的用户信息,如密码,账号状态等
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw var6;
                }

                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            // 判断用户状态,是否是启用
            this.preAuthenticationChecks.check(user);
            // 密码比较
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
        // 创建一个成功的Authentication对象
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
}

身份验证 DaoAuthenticationProvider

  1. 它提供了一种基于用户名和密码进行身份验证的机制。
  2. 它通过UserDetailsService从数据源(如数据库)中获取用户信息
  3. 使用PasswordEncoder对用户提供的密码进行加密后与数据库中存储的密码进行比较
  4. 如果验证成功,则DaoAuthenticationProvider会返回一个包含用户详细信息的Authentication对象;
  5. 否则会抛出相应的异常。
  6. 我们可以根据需要配置DaoAuthenticationProvider的各个方面,如加密算法、是否启用账户锁定等。

因此后面我们在做手机登录的时候,就可以自定义一个provider,然后调用一个根据手机号查询用户信息的接口即可。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
            // 最终实现查询用户信息的代码在这里
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }
}

UserDetailsService 配置查询逻辑

public interface UserDetailsService {
    // 通过用户名获取用户信息
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

通过用户名获取用户信息的方法是被下面这个实现类所实现的

public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从缓存中获取用户信息
        UserDetails user = (UserDetails)this.users.get(username.toLowerCase());
        if (user == null) {
            throw new UsernameNotFoundException(username);
        } else {
            // 返回一个UserDetails
            return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
        }
    }
}

springSecurity获取用户信息,默认从缓存中换取的,然后返回一个UserDetails,我们看看其定义的User类,下面只是列出用部分

public class User implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private static final Log logger = LogFactory.getLog(User.class);
	
	private String password;

	private final String username;

	private final Set<GrantedAuthority> authorities;

	private final boolean accountNonExpired;

	private final boolean accountNonLocked;

	private final boolean credentialsNonExpired;

	private final boolean enabled;


	public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
		this(username, password, true, true, true, true, authorities);
	}

	public User(String username, String password, boolean enabled, boolean accountNonExpired,
			boolean credentialsNonExpired, boolean accountNonLocked,
			Collection<? extends GrantedAuthority> authorities) {
		Assert.isTrue(username != null && !"".equals(username) && password != null,
				"Cannot pass null or empty values to constructor");
		this.username = username;
		this.password = password;
		this.enabled = enabled;
		this.accountNonExpired = accountNonExpired;
		this.credentialsNonExpired = credentialsNonExpired;
		this.accountNonLocked = accountNonLocked;
		this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
	}

	@Override
    //权限
	public Collection<GrantedAuthority> getAuthorities() {
		return this.authorities;
	}
}

用户标识

当登录成功后,再次发送请求时,springsecurity时如何知道该用户已经通过验证的?

这时我们就要认识一下SecurityContextPersistenceFilter

它的作用主要是: 创建SecurityContext安全上下文信息和请求结束时清空SecurityContextHolder

  1. 用于在每个请求之间持久化 SecurityContext。
  2. 在用户进行身份验证后,Spring Security 将创建一个包含认证信息的 SecurityContext 对象。
  3. 该对象会被存储在当前线程的 ThreadLocal 中。
  4. 如果不使用 SecurityContextPersistenceFilter 这个过滤器,则在调用链中传递 SecurityContext 是非常困难的,并且可能导致安全问题。

SecurityContextPersistenceFilter 的作用是将当前线程中的 SecurityContext 对象持久化到适当的存储介质中(比如 HttpSession 或者数据库),以便在下一次请求时可以重新加载并再次使用。这样,在整个调用链中,即使跨多个请求处理程序,也可以始终访问相同的 SecurityContext 对象,从而实现身份验证和授权功能。

public class SecurityContextPersistenceFilter extends GenericFilterBean {
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        this.doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
    }

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 同一个请求只处理一次
        if (request.getAttribute("__spring_security_scpf_applied") != null) {
            chain.doFilter(request, response);
        } else {
            request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
            if (this.forceEagerSessionCreation) {
                // 创建一个HttpSession
                HttpSession session = request.getSession();
                if (this.logger.isDebugEnabled() && session.isNew()) {
                    this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
                }
            }

            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
            // 获取登录用户的相关信息,如果没有登录,用户信息为空
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var10 = false;

            try {
                var10 = true;
                // SecurityContextHolder绑定SecurityContext对象
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                if (contextBeforeChainExecution.getAuthentication() == null) {
                    this.logger.debug("Set SecurityContextHolder to empty SecurityContext");
                } else if (this.logger.isDebugEnabled()) {
                    this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
                }
                // 下一个过滤器处理
                chain.doFilter(holder.getRequest(), holder.getResponse());
                var10 = false;
            } finally {
                if (var10) {
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    // 在任何其他操作之前删除SecurityContextHolder内容
                    SecurityContextHolder.clearContext();
                    // 重新保存用户信息
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    this.logger.debug("Cleared SecurityContextHolder to complete request");
                }
            }

            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            SecurityContextHolder.clearContext();
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            request.removeAttribute("__spring_security_scpf_applied");
            this.logger.debug("Cleared SecurityContextHolder to complete request");
        }
    }
}