preface

This article is participating in the Java Theme Month – Java Debug Notes Event, see the event link for details

Learning Spring Security for a period of time, today’s general theory of Spring Security core important objects, if you have any questions, welcome to point out, learn from each other 👏🏻

SecurityContextHolder

SecurityContextHolder is one of the most core components of Spring Security. It internally encapsulates the logic that holds the Security context SecurityContext in the application and provides the core static method to expose getContext. SetContext used to set and get the current request thread security context, mainly in SecurityContextPersistenceFilter blocker SecurityContextHolder establish links with SecurityContext

The association between SecurityContextHolder and SecurityContext

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
  			// Whether it has been executed
        if (request.getAttribute("__spring_security_scpf_applied") != null) {
            chain.doFilter(request, response);
        } else{... HttpRequestResponseHolder holder =new HttpRequestResponseHolder(request, response);
          	// SpringSecurity is obtained from HttpSession by default, or a new SecurityContext is automatically generated
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var10 = false;

            try {
                var10 = true;
              	//SecurityContextHolderSecurityContextHolder.setContext(contextBeforeChainExecution); . chain.doFilter(holder.getRequest(), holder.getResponse()); var10 =false;
            } finally {
                if(var10) { ... }}/ / get SecurityContext
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
          	// Clear the SecurityContext in the context
            SecurityContextHolder.clearContext();
          	// Resets the interceptor processed SecurityContext into session so that it can be fetched again on the next request
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            request.removeAttribute("__spring_security_scpf_applied");
            this.logger.debug("Cleared SecurityContextHolder to complete request"); }}Copy the code

Core logic implementation

  • The default from theHttpSessionTo deriveSecurityContextIf not, a new one is automatically generatedSecurityContextAnd set it toSecurityContextHolder, continue to execute subsequent interceptors
  • Other interceptors and core logic are acquired after execution is completeSecurityContext, clears the context information and will be processed by the interceptorSecurityContextReset tosessionTo be obtained again in the next request

How is the SecurityContext stored internally

SecurityContextHolder holds the default three modes within the SecurityContext

  • MODE_THREADLOCAL ThreadLocal mode, which stores the context to the current requesting thread
  • MODE_INHERITABLETHREADLOCAL The parent thread ThreadLocal mode is essentially a ThreadLocal mode that stores context to the current requesting thread
  • MODE_GLOBAL Global pattern, for globally unique types

The default is ThreadLocal in the initialize method

public class SecurityContextHolder {
	/ / ThreadLocal mode
	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	
  // Parent thread ThreadLocal mode
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	// Global mode
	public static final String MODE_GLOBAL = "MODE_GLOBAL";

	public static final String SYSTEM_PROPERTY = "spring.security.strategy";

	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);

  // Storage policy
	private static SecurityContextHolderStrategy strategy;

	private static int initializeCount = 0;

	static {
		initialize();
	}

	private static void initialize(a) {
		if(! StringUtils.hasText(strategyName)) {// Set default to ThreadLocal
			strategyName = MODE_THREADLOCAL;
		}
		if (strategyName.equals(MODE_THREADLOCAL)) {
			strategy = new ThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_GLOBAL)) {
			strategy = new GlobalSecurityContextHolderStrategy();
		}
		else {
			// Try to load a custom strategy
			try{ Class<? > clazz = Class.forName(strategyName); Constructor<? > customStrategy = clazz.getConstructor(); strategy = (SecurityContextHolderStrategy) customStrategy.newInstance(); }catch (Exception ex) {
				ReflectionUtils.handleReflectionException(ex);
			}
		}
		initializeCount++;
	}
Copy the code

Authentication

Authentication is essentially an abstract interface that defines a set of service abstractions for Authentication

  • Getathorities lists the permissions authorized currently
  • GetCredentials Password information
  • GetDetails Specifies the login information. The IP address of the visitor and the value of the sessionId
  • GetPrincipal returns the implementation of UserDetails after logging inorg.springframework.security.core.userdetails.User
public interface Authentication extends Principal.Serializable {

	/**
	 * Set by an <code>AuthenticationManager</code> to indicate the authorities that the
	 * principal has been granted. Note that classes should not rely on this value as
	 * being valid unless it has been set by a trusted <code>AuthenticationManager</code>.
	 * <p>
	 * Implementations should ensure that modifications to the returned collection array
	 * do not affect the state of the Authentication object, or use an unmodifiable
	 * instance.
	 * </p>
	 * @return the authorities granted to the principal, or an empty collection if the
	 * token has not been authenticated. Never null.
	 */
	Collection<? extends GrantedAuthority> getAuthorities();

	/**
	 * The credentials that prove the principal is correct. This is usually a password,
	 * but could be anything relevant to the <code>AuthenticationManager</code>. Callers
	 * are expected to populate the credentials.
	 * @return the credentials that prove the identity of the <code>Principal</code>
	 */
	Object getCredentials(a);

	/**
	 * Stores additional details about the authentication request. These might be an IP
	 * address, certificate serial number etc.
	 * @return additional details about the authentication request, or <code>null</code>
	 * if not used
	 */
	Object getDetails(a);

	/**
	 * The identity of the principal being authenticated. In the case of an authentication
	 * request with username and password, this would be the username. Callers are
	 * expected to populate the principal for an authentication request.
	 * <p>
	 * The <tt>AuthenticationManager</tt> implementation will often return an
	 * <tt>Authentication</tt> containing richer information as the principal for use by
	 * the application. Many of the authentication providers will create a
	 * {@code UserDetails} object as the principal.
	 * @return the <code>Principal</code> being authenticated or the authenticated
	 * principal after authentication.
	 */
	Object getPrincipal(a);

	/**
	 * Used to indicate to {@code AbstractSecurityInterceptor} whether it should present
	 * the authentication token to the <code>AuthenticationManager</code>. Typically an
	 * <code>AuthenticationManager</code> (or, more often, one of its
	 * <code>AuthenticationProvider</code>s) will return an immutable authentication token
	 * after successful authentication, in which case that token can safely return
	 * <code>true</code> to this method. Returning <code>true</code> will improve
	 * performance, as calling the <code>AuthenticationManager</code> for every request
	 * will no longer be necessary.
	 * <p>
	 * For security reasons, implementations of this interface should be very careful
	 * about returning <code>true</code> from this method unless they are either
	 * immutable, or have some way of ensuring the properties have not been changed since
	 * original creation.
	 * @returntrue if the token has been authenticated and the * <code>AbstractSecurityInterceptor</code> does not need to present the  token to the * <code>AuthenticationManager</code> again for re-authentication. */
	boolean isAuthenticated(a);

	/**
	 * See {@link #isAuthenticated()} for a full description.
	 * <p>
	 * Implementations should <b>always</b> allow this method to be called with a
	 * <code>false</code> parameter, as this is used by various classes to specify the
	 * authentication token should not be trusted. If an implementation wishes to reject
	 * an invocation with a <code>true</code> parameter (which would indicate the
	 * authentication token is trusted - a potential security risk) the implementation
	 * should throw an {@link IllegalArgumentException}.
	 * @param isAuthenticated <code>true</code> if the token should be trusted (which may
	 * result in an exception) or <code>false</code> if the token should not be trusted
	 * @throws IllegalArgumentException if an attempt to make the authentication token
	 * trusted (by passing <code>true</code> as the argument) is rejected due to the
	 * implementation being immutable or implementing its own alternative approach to
	 * {@link #isAuthenticated()}
	 */
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

}

Copy the code

Authentication of abstract implementation for AbstractAuthenticationToken, mainly defines the abstract entities in the variable definitions, for example

private final Collection<GrantedAuthority> authorities;

private Object details;

private boolean authenticated = false;
Copy the code

AbstractAuthenticationToken UsernamePasswordAuthenticationToken default implementation, essentially with AbstractAuthenticationToken not too big difference, is the concrete realization

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter is SpringSecurity important Filter in the web application, its main function is used to request through to the request of the userName and password, Generate the corresponding UsernamePasswordAuthenticationToken serious entity object, through the AuthenticationManager Authentication manager to authenticate login, and returns a fill the authorization information Authentication object

UsernamePasswordAuthenticationFilter implementation lies in its core attemptAuthentication, try to certification and return a fill the authorization information Authentication object

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (this.postOnly && ! request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    } else {
    		// Get the user name and password
        String username = this.obtainUsername(request); username = username ! =null ? username : "";
        username = username.trim();
        String password = this.obtainPassword(request); password = password ! =null ? password : "";
        / / generated UsernamePasswordAuthenticationToken object
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest); }}Copy the code
  • Non-post requests are not supported and an exception is thrown
  • fromrequestObject to obtain the username and password generatedUsernamePasswordAuthenticationTokenAuthentication object,
  • Authentication is performed through the AuthenticationManager AuthenticationManager

AuthenticationManager

AuthenticationManager only has authenticate method, which defines the basic authentication service. The authenticate method’s input and output parameters are the same object. It can be seen from the description of authenticate method. Try to authenticate an Authentication object, return an object that has been filled with granted authorities, Other information about the GRANTED authorities object, including authorization and user information, needs to be filled in with the authenticate method

Authenticate may throw the following AuthenticationException

Disable * * DisabledException account lock * BadCredentialsException LockedException account password mistake there is no * * UsernameNotFoundException account, etc...Copy the code
public interface AuthenticationManager {

	/**
	 * Attempts to authenticate the passed {@link Authentication} object, returning a
	 * fully populated <code>Authentication</code> object (including granted authorities)
	 * if successful.
	 * <p>
	 * An <code>AuthenticationManager</code> must honour the following contract concerning
	 * exceptions:
	 * <ul>
	 * <li>A {@link DisabledException} must be thrown if an account is disabled and the
	 * <code>AuthenticationManager</code> can test for this state.</li>
	 * <li>A {@link LockedException} must be thrown if an account is locked and the
	 * <code>AuthenticationManager</code> can test for account locking.</li>
	 * <li>A {@link BadCredentialsException} must be thrown if incorrect credentials are
	 * presented. Whilst the above exceptions are optional, an
	 * <code>AuthenticationManager</code> must <B>always</B> test credentials.</li>
	 * </ul>
	 * Exceptions should be tested for and if applicable thrown in the order expressed
	 * above (i.e. if an account is disabled or locked, the authentication request is
	 * immediately rejected and the credentials testing process is not performed). This
	 * prevents credentials being tested against disabled or locked accounts.
	 * @param authentication the authentication request object
	 * @return a fully authenticated object including credentials
	 * @throws AuthenticationException if authentication fails
	 */
	Authentication authenticate(Authentication authentication) throws AuthenticationException;

Copy the code

ProviderManager

ProviderManager is the concrete implementation of AuthenticationManager. In the implementation of Authenticate method of providerManager, it mainly traverses all the implementation of AuthenticationProvider. The provider.supports method identifies whether the currently passed authentication object implementation is supported by the current provider. If not, it skips until a matching one is found

Class<? extends Authentication> toTest = authentication.getClass();
for (AuthenticationProvider provider : getProviders()) {
			if(! provider.supports(toTest)) {continue; }...try {
				result = provider.authenticate(authentication);
				if(result ! =null) {... }}catch() {... }}Copy the code

AuthenticationProvider

Spring Security provides a variety of authentication modes, including DAO authentication, simple user password authentication, tripartite authentication, anonymous authentication, etc. AuthenticationProvider represents the authentication mode and can be selected from various authentication modes. Several implementations are provided in Spring Security by default, including

  • DaoAuthenticationProvider
  • RemoteAuthenticationProvider
  • AnonymousAuthenticationProvider
  • TestingAuthenticationProvider
  • , etc.

In expanding the DB validation DaoAuthenticationProvider, we often used when DaoAuthenticationProvider see login name will know that dealing with the database to check identification authentication information

DaoAuthenticationProvider

RetrieveUser method mainly through DaoAuthenticationProvider

The ultimate goal, that is, according to UsernamePasswordAuthenticationToken, access to the username, and then call UserDetailsService retrieval user details. This is the familiar UserDetailsService extension

@Override
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
      // Extend the implementation
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw newInternalAuthenticationServiceException(ex.getMessage(), ex); }}Copy the code

The liver is done!