Un artículo lo lleva a través del proceso de inicio de sesión de Spring Security.

¿Por qué desea recorrer el proceso de inicio de sesión de Spring Security con todos? Esto se debe a una pregunta que me hicieron mis amigos: ¿Cómo modificar dinámicamente la información del usuario en Spring Security?

Si averigua el proceso de inicio de sesión de Spring Security, esto en realidad no es un problema.

Primero, describamos brevemente el escenario del problema:

Utiliza Spring Security para la gestión de seguridad del servidor. Después de que el usuario inicie sesión correctamente, Spring Security le ayudará a guardar la información del usuario en la sesión, pero es posible que no sepa dónde está, si no entra, esto trae un problema. Si el usuario modifica la información del usuario actual en la operación de front-end, ¿cómo puedo obtener la información del usuario más reciente sin iniciar sesión nuevamente? Esta es la pregunta que se presentará hoy al edificio.

1. Autenticación ubicua

Los amigos que han jugado a Spring Security saben que hay un objeto muy importante en Spring Security llamado Autenticación. Podemos inyectar Autenticación en cualquier lugar para obtener la información del usuario actualmente conectado. La autenticación en sí misma es una interfaz y tiene muchas clases de implementación:

Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
En esta gran clase de implementación, nuestra clase de implementación más utilizada es UsernamePasswordAuthenticationToken, pero cuando abrimos el código fuente de esta clase, pero encontramos que esta clase de mediocridad, solo tenía dos propiedades, dos constructores y varios get/setmétodos; por supuesto, Tiene más atributos en su clase padre.

Pero a partir de sus dos únicos atributos, también podemos ver aproximadamente que esta clase guarda la información básica de nuestro usuario registrado. Entonces, ¿cómo se almacena nuestra información de inicio de sesión en estos dos objetos? Esto solucionará el proceso de inicio de sesión.

Dos, proceso de inicio de sesión

En Spring Security, la verificación de autenticación y autorización se completa en una serie de cadenas de filtros. En esta serie de cadenas de filtros, los filtros relacionados con la autenticación son el UsernamePasswordAuthenticationFilterproblema de espacio, los enumeraré aquí Varios métodos importantes en esta clase:

publicclass UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
    
    
	public UsernamePasswordAuthenticationFilter() {
    
    
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
    
    
		String username = obtainUsername(request);
		String password = obtainPassword(request);
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		setDetails(request, authRequest);
		returnthis.getAuthenticationManager().authenticate(authRequest);
	}
	protected String obtainPassword(HttpServletRequest request) {
    
    
		return request.getParameter(passwordParameter);
	}
	protected String obtainUsername(HttpServletRequest request) {
    
    
		return request.getParameter(usernameParameter);
	}
	protected void setDetails(HttpServletRequest request,
			UsernamePasswordAuthenticationToken authRequest) {
    
    
		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
	}
}

Según este código fuente podemos ver:

(1) a través del primero obtainUsernamey obtainPasswordlos métodos de extracción dentro de una solicitud de nombre de usuario / contraseña, el método de extracción es request.getParameter, por lo que los parámetros de inicio de sesión predeterminados de Spring Security se pasarán en forma de clave / valor, y no pueden pasar parámetros JSON , Si desea pasar parámetros JSON, simplemente modifique la lógica aquí.

(2) Después de obtener la solicitud se pasó al usuario / contraseña, luego en un UsernamePasswordAuthenticationTokenobjeto de configuración pasó usernamey password, usernamecorresponde a UsernamePasswordAuthenticationTokenlos principalatributos, y passwordcorresponde a sus credentialsatributos.

(3) A continuación, setDetailsun método para detailsasignar un atributo, en UsernamePasswordAuthenticationTokensí mismo no es una detailspropiedad, esta propiedad en su clase padre AbstractAuthenticationTokenen. detailsEs un objeto que se coloca dentro de la WebAuthenticationDetailsinstancia de dos informaciones descritas principalmente, la solicitud remoteAddressy la solicitud sessionId.
(4) El último paso es llamar al método de autenticación para realizar la verificación.

Pues bien, a partir de este código fuente, se puede ver que toda la información solicitada básicamente ha encontrado su propia ubicación, y nos será conveniente obtenerla en el futuro.

A continuación, veamos la operación de verificación específica solicitada.

En el anterior attemptAuthenticationprocedimiento, la etapa final del proceso se inició la comprobación, verificación de funcionamiento comienza a obtener una AuthenticationManager, llegar aquí es que ProviderManager, por lo que entonces entramos en ProviderManagerel authenticateproceso, por supuesto, este método es relativamente largo, lo Estos son solo algunos puntos importantes:

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
    
    
	Class<? extends Authentication> toTest = authentication.getClass();
	for (AuthenticationProvider provider : getProviders()) {
    
    
		if (!provider.supports(toTest)) {
    
    
			continue;
		}
		result = provider.authenticate(authentication);
		if (result != null) {
    
    
			copyDetails(authentication, result);
			break;
		}
	}
	if (result == null && parent != null) {
    
    
		result = parentResult = parent.authenticate(authentication);
	}
	if (result != null) {
    
    
		if (eraseCredentialsAfterAuthentication
				&& (result instanceof CredentialsContainer)) {
    
    
			((CredentialsContainer) result).eraseCredentials();
		}
		if (parentResult == null) {
    
    
			eventPublisher.publishAuthenticationSuccess(result);
		}
		return result;
	}
	throw lastException;
}

Este método es más mágico, porque casi toda la lógica importante sobre la autenticación se completará aquí:

(1) Primero, obtenga authenticationla Clase, el proveedor actual para determinar si admite la autenticación.

(2) Si es compatible, llame al método de autenticación del proveedor para iniciar la verificación. Una vez completada la verificación, se devolverá una nueva autenticación. Discutiré la lógica específica de este método con usted en un momento.

(3) proveedor donde puede haber más, si el proveedor no puede autenticar, el método devuelve un Authenticationpadre normal del proveedor del authenticatemétodo de llamada para continuar verificando.

(4) El método copyDetails se utiliza para copiar la propiedad de detalles del Token antiguo al Token nuevo.

(5) A continuación, se llamará al método eraseCredentials para borrar la información de la credencial, que es su contraseña. Este método de borrado es relativamente simple, que consiste en vaciar el atributo de credenciales en el Token.

(6) Finalmente, publishAuthenticationSuccessel método se transmitirá con éxito en el evento de inicio de sesión.
El proceso general es el anterior. En el bucle for, el proveedor que obtiene por primera vez es un AnonymousAuthenticationProvider. Este proveedor no admite UsernamePasswordAuthenticationToken en absoluto, es decir, devuelve false directamente en el método provider.supports, finaliza el bucle for y luego Ingresará el siguiente si y llamará directamente al método de autenticación de los padres para verificar.

El padre es ProviderManager, una vez más volverá a este authenticatemétodo. Una vez más authenticate, el método de retroceso se ha convertido en un proveedor DaoAuthenticationProvider, el proveedor es compatible UsernamePasswordAuthenticationToken, estará bien dentro del authenticatemétodo de clase para realizar, y se DaoAuthenticationProviderhereda AbstractUserDetailsAuthenticationProvidery no anula el método de autenticación, por lo que finalmente hemos llegado al AbstractUserDetailsAuthenticationProvider#authenticateenfoque:

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
    
    
	String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
			: authentication.getName();
	user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
	preAuthenticationChecks.check(user);
	additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
	postAuthenticationChecks.check(user);
	Object principalToReturn = user;
	if (forcePrincipalAsString) {
    
    
		principalToReturn = user.getUsername();
	}
	return createSuccessAuthentication(principalToReturn, authentication, user);
}

La lógica aquí es relativamente simple:

(1) Primero extraiga el nombre de usuario de inicio de sesión de Autenticación.

(2) luego se tomó usernamellamando al retrieveUsermétodo para obtener el objeto de usuario actual, este paso llamará al nuestro durante el inicio de sesión para escribir el loadUserByUsernamemétodo, por lo que en realidad hay un retorno a los objetos de usuario con los que inicia sesión, puede consultar el micro personal org / javaboy /vhr/service/HrService.java#L34.

(3) A continuación, llame al preAuthenticationChecks.checkmétodo para examinar las propiedades de cada cuenta de usuario en el estado normal, por ejemplo, si la cuenta está deshabilitada, si la cuenta está bloqueada, la cuenta ha caducado, etc.

(4) El additionalAuthenticationChecksmétodo es hacer una comparación de contraseñas. Muchos amigos sienten curiosidad por saber cómo se cifran las contraseñas de Spring Security y cómo compararlas. Puedes entenderlo aquí. Debido a que la lógica de la comparación es muy simple, no publicaré el código aquí. .

(5) Finalmente, postAuthenticationChecks.checkverifique si la contraseña está vencida.

(6) Luego hay una forcePrincipalAsStringpropiedad, ya sea para forzar Authenticationla principalpropiedad a una cadena, comenzamos en esta propiedad UsernamePasswordAuthenticationFilterque en realidad está establecida en la clase cadena (es decir, nombre de usuario), pero de forma predeterminada, cuando un usuario inicia sesión Después del éxito, el valor de este atributo se convierte en el objeto del usuario actual. La razón de esto es porque forcePrincipalAsStringel valor predeterminado es falso, pero no cambie este hecho, use falso para obtener la información del usuario actual en la última parte del tiempo, pero mucho más fácil.

(7) Finalmente, createSuccessAuthenticationconstruyendo un nuevo método UsernamePasswordAuthenticationToken.

Bien, entonces el proceso de verificación de inicio de sesión ahora se repite básicamente con todos. Luego hay otra pregunta, ¿dónde buscamos la información del usuario que inició sesión?

Tres, preservación de la información del usuario

Para encontrar la información del usuario que ha iniciado sesión, primero tenemos que resolver un problema, es decir, hemos dicho tanto arriba, ¿dónde empezó a desencadenarse todo esto?

Llegamos a UsernamePasswordAuthenticationFilterla clase principal AbstractAuthenticationProcessingFilter, esta clase que vemos a menudo, porque muchas veces cuando queremos un código personalizado de Spring Security o los parámetros de inicio de sesión de autenticación de inicio de sesión se cambiarán a JSON, y todos necesitamos filtros personalizados heredados de AbstractAuthenticationProcessingFiltersin duda, UsernamePasswordAuthenticationFilter#attemptAuthenticationse activa el método de la AbstractAuthenticationProcessingFilterclase doFilter:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
    
    
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;
	Authentication authResult;
	try {
    
    
		authResult = attemptAuthentication(request, response);
		if (authResult == null) {
    
    
			return;
		}
		sessionStrategy.onAuthentication(authResult, request, response);
	}
	catch (InternalAuthenticationServiceException failed) {
    
    
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	catch (AuthenticationException failed) {
    
    
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	if (continueChainBeforeSuccessfulAuthentication) {
    
    
		chain.doFilter(request, response);
	}
	successfulAuthentication(request, response, chain, authResult);
}

A partir del código anterior, podemos ver que cuando attemptAuthenticationse llama al método, de hecho, el disparador de un UsernamePasswordAuthenticationFilter#attemptAuthenticationmétodo genera una excepción cuando se inicia sesión, unsuccessfulAuthenticationse llama al método, y cuando el inicio de sesión es exitoso, successfulAuthenticationse llamará al método, luego echamos un vistazo a los successfulAuthenticationmétodos:

protected void successfulAuthentication(HttpServletRequest request,
		HttpServletResponse response, FilterChain chain, Authentication authResult)
		throws IOException, ServletException {
    
    
	SecurityContextHolder.getContext().setAuthentication(authResult);
	rememberMeServices.loginSuccess(request, response, authResult);
	// Fire event
	if (this.eventPublisher != null) {
    
    
		eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
				authResult, this.getClass()));
	}
	successHandler.onAuthenticationSuccess(request, response, authResult);
}

Aquí hay un código muy importante, es decir SecurityContextHolder.getContext().setAuthentication(authResult);, la información del usuario se almacena en el inicio de sesión aquí es exitoso, es decir, en cualquier lugar, si queremos obtener la información de inicio de sesión del usuario, están disponibles desde el SecurityContextHolder.getContext()get in, desea modificar, también puede estar en Modificar aquí.

Finalmente, todos también vieron uno successHandler.onAuthenticationSuccess. Este es el método callback que configuramos en SecurityConfig. Se activa aquí. También puedes consultar la configuración en el micropersonal org/javaboy/vhr/config/SecurityConfig.

Supongo que te gusta

Origin blog.csdn.net/nanhuaibeian/article/details/108585792
Recomendado
Clasificación