Un artículo lo lleva a obtener SpringBoot y SpringSecurity para lograr la función de inicio de sesión automático

El inicio de sesión automático es una función muy común cuando estamos desarrollando software. Por ejemplo, iniciamos sesión en el buzón de correo de QQ:

Inserte la descripción de la imagen aquí
En muchos sitios web, veremos opciones similares al iniciar sesión. Después de todo, es muy problemático dejar que los usuarios siempre ingresen su nombre de usuario y contraseña.

La función de inicio de sesión automático significa que después de que el usuario inicie sesión con éxito, dentro de un cierto período de tiempo, si el usuario cierra el navegador y lo vuelve a abrir, o si el servidor se reinicia, no es necesario que el usuario inicie sesión nuevamente, y el usuario aún puede acceder directamente a los datos de la interfaz.

Como característica común, nuestro Spring Security también debe proporcionar el soporte correspondiente.En este artículo, veremos cómo implementar esta característica en Spring Security.

1. Únase a recordarme

Para mayor comodidad de configuración, simplemente agregue dos dependencias:

Inserte la descripción de la imagen aquí
Agregue el siguiente código a la clase de configuración:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    PasswordEncoder passwordEncoder(){
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.inMemoryAuthentication()
                .withUser("yolo")
                .password("123").roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                .and()
                .csrf().disable();
    }
}

Podemos ver que aquí solo agregue una .rememberMe()lata, la función de inicio de sesión automático se agrega correctamente ingresada.

A continuación, agregamos aleatoriamente una interfaz de prueba:

@RestController
public class HelloController {
    
    
    @GetMapping("/hello")
    public String hello(){
    
    
        return "Hello Yolo !!!";
    }
}

Inserte la descripción de la imagen aquí
En este momento, todos encontraron que la página de inicio de sesión predeterminada tiene una opción más, que es recordarme. Ingresamos el nombre de usuario y la contraseña, marcamos la casilla Recordarme y luego hacemos clic en el botón de inicio de sesión para realizar la operación de inicio de sesión.

Como puede ver, en los datos de inicio de sesión, además del nombre de usuario y la contraseña, también hay un recordatorio. La razón para mostrar esto es para decirles a todos si necesitan personalizar la página de inicio de sesión, ¿qué pasa con la clave de la opción RememberMe? escribir.

Una vez que el inicio de sesión sea exitoso, saltará automáticamente a la interfaz de saludo. Observamos que cuando el sistema accede a la interfaz de saludo, la cookie lleva:

Inserte la descripción de la imagen aquí
Se señaló aquí uno más remember-me, que es el núcleo implementado aquí, sobre esto remember-meexplicaré, probemos los resultados.

A continuación, cerramos el navegador y luego lo volvemos a abrir. En circunstancias normales, el navegador se cierra y se vuelve a abrir. Si necesitamos acceder a la interfaz de saludo nuevamente, debemos iniciar sesión nuevamente. Pero en este momento, vamos a visitar la interfaz de saludo nuevamente y encontramos que podemos acceder a ella directamente sin iniciar sesión nuevamente. Esto muestra que nuestra configuración de RememberMe ha entrado en vigencia (es decir, la función de inicio de sesión automático entrará en vigencia la próxima vez).

2. Análisis de principios

Es lógico que si el navegador se cierra y luego se vuelve a abrir, es necesario volver a iniciar sesión. Ahora no hay necesidad de esperar. Entonces, ¿cómo funciona esta función?

Primero, analicemos el extra en la cookie remember-me. Este valor es una cadena transcodificada en Base64. Podemos usar algunas herramientas en línea en Internet para decodificarlo. Simplemente puede escribir dos líneas de código para decodificarlo usted mismo:


    @Test
    void contextLoads() {
    
    
        String s = new String(
                Base64.getDecoder().decode("eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"));
        System.out.println("s = " + s);
    }

Ejecute este código, el resultado es el siguiente:

s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9

Se puede ver este hecho con la cadena Base64 :separada en tres partes:

(1) El primer párrafo es el nombre de usuario, esto no necesita ser cuestionado.
(2) El segundo párrafo parece ser una marca de tiempo. Después de analizarlo a través de herramientas en línea o código Java, encontramos que se trata de un dato dos semanas después.
(3) En el tercer párrafo, no lo venderé. Este es el valor calculado usando la función hash MD5. Su formato de texto plano es
username + ":" + tokenExpiryTime + ":" + password + ":" + keyque la última clave es un valor hash salt, que se puede usar para evitar que el token sea modificado.

Aprendí una cookie remember-medespués del significado, luego tenemos que recordar mi proceso de inicio de sesión y es muy fácil adivinar el.

Después de que el navegador se cierra y luego se vuelve a abrir, los usuarios acceden a la interfaz de saludo, luego llevarán la cookie remember-meal servidor, después del servicio para obtener el valor, puede calcular fácilmente el nombre de usuario y la fecha de vencimiento, y luego, según el usuario La contraseña del usuario se recupera por nombre, y luego el valor hash se calcula a través de la función hash MD5, y luego el valor hash calculado se compara con el valor hash pasado por el navegador para confirmar si el token es válido.

El proceso es tal proceso. A continuación, verificaremos la corrección de este proceso analizando el código fuente.

Tres, análisis de código fuente

A continuación, usamos el código fuente para verificar que lo que dijimos anteriormente sea correcto.

Aquí se presentan principalmente a partir de dos aspectos, uno es el proceso de generación de tokens de recuerdo y el otro es el proceso de análisis.

1. Generar

Los principales métodos de procesamiento generados son:TokenBasedRememberMeServices#onLoginSuccess:

@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
  Authentication successfulAuthentication) {
    
    
 String username = retrieveUserName(successfulAuthentication);
 String password = retrievePassword(successfulAuthentication);
 if (!StringUtils.hasLength(password)) {
    
    
  UserDetails user = getUserDetailsService().loadUserByUsername(username);
  password = user.getPassword();
 }
 int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
 long expiryTime = System.currentTimeMillis();
 expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
 String signatureValue = makeTokenSignature(expiryTime, username, password);
 setCookie(new String[] {
    
     username, Long.toString(expiryTime), signatureValue },
   tokenLifetime, request, response);
}
protected String makeTokenSignature(long tokenExpiryTime, String username,
  String password) {
    
    
 String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
 MessageDigest digest;
 digest = MessageDigest.getInstance("MD5");
 return new String(Hex.encode(digest.digest(data.getBytes())));
}

(1) Primero, extraiga el nombre de usuario / contraseña de la autenticación para iniciar sesión correctamente.
(2) Después de que el inicio de sesión sea exitoso, la contraseña puede borrarse. Por lo tanto, si no se obtiene la contraseña al principio, vuelva a cargar el usuario desde UserDetailsService y obtenga la contraseña nuevamente.
(3) A continuación, obtenga el período de validez del token, que es de dos semanas por defecto.
(4) El siguiente makeTokenSignaturemétodo invocado para calcular un valor hash, un valor hash se calcula realmente de acuerdo con el nombre de usuario, y el token es una contraseña válida, clave. Si no poseemos para establecer esta clave, el valor predeterminado es RememberMeConfigurer#getKeyestablecer métodos, su valor es una cadena UUID.
(5) Finalmente, ingrese el nombre de usuario, el período de validez del token y el valor hash calculado en la cookie.

Respecto al cuarto punto, permítanme hablar de ello nuevamente.

Dado que no configuramos su propia clave, el valor predeterminado de la clave es una cadena UUID, esto traerá un problema, si el servidor se reinicia, la clave se convierte, lo que lleva a distribuirse antes de que todo remember-meel token de inicio de sesión automático sea inválido, por lo tanto, Podemos especificar esta clave. La forma de especificar es la siguiente:

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("yolo")
            .and()
            .csrf().disable();
}

Si configura la clave, aún puede acceder a la interfaz de saludo incluso si el servidor se reinicia, incluso si el navegador se abre y luego se cierra

Este es el proceso de generación de tokens recuérdame. En cuanto a cómo llegar al método onLoginSuccess, aquí puede darle un pequeño recordatorio:

AbstractAuthenticationProcessingFilter # doFilter -> AbstractAuthenticationProcessingFilter # SuccessAuthentication -> AbstractRememberMeServices # loginSuccess -> TokenBasedRememberMeServices # onLoginSuccess。

2. Análisis

Cuando el usuario se apaga y luego abre el navegador, vuelve a acceder a /hellola interfaz, ¿entonces el proceso de certificación es como funciona?

Como dijimos antes, una serie de funciones en Spring Security se implementan a través de una cadena de filtros, y RememberMeesta función, por supuesto, no es una excepción.

Spring Security proporciona RememberMeAuthenticationFilterclases diseñadas para hacer cosas relacionadas, miramos RememberMeAuthenticationFilterel doFiltermétodo:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  throws IOException, ServletException {
    
    
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletResponse response = (HttpServletResponse) res;
 if (SecurityContextHolder.getContext().getAuthentication() == null) {
    
    
  Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
    response);
  if (rememberMeAuth != null) {
    
    
    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
    onSuccessfulAuthentication(request, response, rememberMeAuth);
    if (this.eventPublisher != null) {
    
    
     eventPublisher
       .publishEvent(new InteractiveAuthenticationSuccessEvent(
         SecurityContextHolder.getContext()
           .getAuthentication(), this.getClass()));
    }
    if (successHandler != null) {
    
    
     successHandler.onAuthenticationSuccess(request, response,
       rememberMeAuth);
     return;
    }
   }
  chain.doFilter(request, response);
 }
 else {
    
    
  chain.doFilter(request, response);
 }
}

Este método es la parte más crítica y es que, si SecurityContextHolderno se puede hacer que el usuario inicie sesión en la instancia actual, entonces se invoca la rememberMeServices.autoLoginlógica para iniciar sesión, miramos este método:

public final Authentication autoLogin(HttpServletRequest request,
  HttpServletResponse response) {
    
    
 String rememberMeCookie = extractRememberMeCookie(request);
 if (rememberMeCookie == null) {
    
    
  return null;
 }
 logger.debug("Remember-me cookie detected");
 if (rememberMeCookie.length() == 0) {
    
    
  logger.debug("Cookie was empty");
  cancelCookie(request, response);
  return null;
 }
 UserDetails user = null;
 try {
    
    
  String[] cookieTokens = decodeCookie(rememberMeCookie);
  user = processAutoLoginCookie(cookieTokens, request, response);
  userDetailsChecker.check(user);
  logger.debug("Remember-me cookie accepted");
  return createSuccessfulAuthentication(request, user);
 }
 catch (CookieTheftException cte) {
    
    
  
  throw cte;
 }
 cancelCookie(request, response);
 return null;
}

Puede ver, esto es para extraer cookieinformación, y la cookieinformación se decodifica, después de decodificar, y luego llamar al processAutoLoginCookiemétodo para hacer la verificación, processAutoLoginCookieel enfoque del código no lo pondré, el proceso principal es obtener primero un nombre de usuario y una fecha de vencimiento, luego el nombre de usuario Se consulta la contraseña del usuario y luego se calcula el valor hash a través de la función hash MD5, y luego el valor hash obtenido se compara con el valor hash pasado por el navegador para confirmar si el token es válido y luego si iniciar sesión eficaz.

Cuatro, resumen

Lea el artículo anterior, puede haber descubierto que si abrimos la RememberMefunción, lo más importante es poner cookieun token, y el token está rompiendo los sessionlímites, incluso si se reinicia el servidor, incluso si el navegador se cierra y se vuelve a abrir. , Siempre que el token no haya caducado, se puede acceder a los datos.

Una vez que se pierde el token, otros pueden tomar este token para iniciar sesión en nuestro sistema a voluntad, lo cual es una operación muy peligrosa.

Pero en realidad esto es una paradoja: para mejorar la experiencia del usuario (menos inicio de sesión), nuestro sistema conduce inevitablemente a algunos problemas de seguridad, pero podemos reducir los riesgos de seguridad al mínimo a través de la tecnología.

Supongo que te gusta

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