Un artículo lo lleva a usar SpringBoot para implementar el control de riesgos de seguridad durante el inicio de sesión automático

Han aprendido: SpringBoot coopera con SpringSecurity para realizar la función de inicio de sesión automático

Conociendo algunos de los riesgos de seguridad del inicio de sesión automático de Spring Boot, en aplicaciones prácticas, debemos minimizar estos riesgos de seguridad, hoy les hablaré sobre cómo reducir los riesgos de seguridad.

(1) Esquema de token persistente
(2) Verificación secundaria

1. Token persistente

1. Principio

Para comprender el token persistente, primero debe comprender la jugabilidad básica del inicio de sesión automático: SpringBoot coopera con SpringSecurity para realizar la función de inicio de sesión automático

El token persistente se basa en la función básica de inicio de sesión automático ySe agregaron nuevos parámetros de calibración para mejorar la seguridad del sistema., Todo esto lo realiza el desarrollador en segundo plano. Para los usuarios, la experiencia de inicio de sesión es la misma que la experiencia normal de inicio de sesión automático.

En el token persistente, se agregan dos nuevos parámetros de verificación calculados por la función hash MD5, uno es seriesy el otro es token. Entre ellos, seriessolo cuando los usuarios inicien sesión con un nombre de usuario / contraseña se generará o actualizará, pero tokenmientras haya una nueva sesión, se regenerará, por lo que puede evitar un usuario de múltiples terminales mientras está conectado como QQ móvil, un teléfono móvil. Una vez que inicie sesión, iniciará el inicio de sesión de otro teléfono móvil, de modo que el usuario podrá averiguar fácilmente si la cuenta se filtró (vi a un pequeño socio en el grupo de intercambio Song Ge discutiendo cómo prohibir el inicio de sesión en múltiples terminales, de hecho, puede aprender de la idea aquí ).

Clase de procesamiento específica de tokens persistentes PersistentTokenBasedRememberMeServices, el último artículo del que hablamos sobre el proceso de inicio de sesión automatizado específico está en clase TokenBasedRememberMeServices, tienen un padre común:

Inserte la descripción de la imagen aquí
La clase de procesamiento utilizada para guardar el token es PersistentRememberMeToken, la definición de esta clase también es un comando muy conciso:

public class PersistentRememberMeToken {
    
    
 private final String username;
 private final String series;
 private final String tokenValue;
 private final Date date;
    //省略 getter
}

Aquí, la fecha representa la hora en que se utilizó el inicio de sesión automático la última vez.

2. Demostración de código

A continuación, le mostraré el uso específico de tokens persistentes a través del código.

En primer lugar, necesitamos una tabla para registrar la información del token. Esta tabla se puede personalizar completamente, o podemos usar el JDBC predeterminado que nos proporciona el sistema para operar. Si usamos el JDBC predeterminado, es decir JdbcTokenRepositoryImpl, podemos analizar la definición de esta clase:

public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements
  PersistentTokenRepository {
    
    
 public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, "
   + "token varchar(64) not null, last_used timestamp not null)";
 public static final String DEF_TOKEN_BY_SERIES_SQL = "select username,series,token,last_used from persistent_logins where series = ?";
 public static final String DEF_INSERT_TOKEN_SQL = "insert into persistent_logins (username, series, token, last_used) values(?,?,?,?)";
 public static final String DEF_UPDATE_TOKEN_SQL = "update persistent_logins set token = ?, last_used = ? where series = ?";
 public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?";
}

De acuerdo con esta definición de SQL, podemos analizar la estructura de la tabla. Aquí hay un script SQL:

CREATE TABLE `persistent_logins` (
  `username` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `series` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `token` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Primero, preparamos esta tabla en la base de datos: copiamos el script y lo ejecutamos directamente.

Inserte la descripción de la imagen aquí
Como queremos conectarnos a la base de datos, también necesitamos preparar las dependencias jdbc y mysql, de la siguiente manera:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

Luego modifique application.properties para configurar la información de conexión de la base de datos:

spring.datasource.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

A continuación, modificamos SecurityConfig de la siguiente manera:

@Autowired
DataSource dataSource;
@Bean
JdbcTokenRepositoryImpl jdbcTokenRepository() {
    
    
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("yolo")
            .tokenRepository(jdbcTokenRepository())
            .and()
            .csrf().disable();
}

Proporcione una instancia de JdbcTokenRepositoryImpl, configure la fuente de datos DataSource para ella y, finalmente, incluya la instancia de JdbcTokenRepositoryImpl en la configuración a través del tokenRepository.

OK, después de hacer todo esto, podemos probar.

3. Prueba

Seguimos yendo a la interfaz / hola primero, luego saltará automáticamente a la página de inicio de sesión, y luego realizamos la operación de inicio de sesión, recuerde marcar la opción "recordarme", una vez que el inicio de sesión sea exitoso, podemos reiniciar el servidor y luego cerrar el navegador Ábralo de nuevo y luego visite la interfaz / hello y descubra que aún se puede acceder, lo que indica que nuestra configuración de token persistente ha entrado en vigencia.

Ver el token de recordarme:

Inserte la descripción de la imagen aquí
Una vez analizado el token, el formato es el siguiente:

@Test
void contextLoads() {
    
    
     String s = new String(
            Base64.getDecoder().decode("UE8yVWZveUxyQWxJZUJqSnNTT0I2USUzRCUzRDpQdGdHV1R5SHNWUXprdEoxNzBUNWdnJTNEJTNE"));
     System.out.println("s = " + s);
    }
PO2UfoyLrAlIeBjJsSOB6Q%3D%3D:PtgGWTyHsVQzktJ170T5gg%3D%3D

Entre ellos, %3Dsignifica =, por lo que los caracteres anteriores se pueden traducir a lo siguiente:

PO2UfoyLrAlIeBjJsSOB6Q==:PtgGWTyHsVQzktJ170T5gg==

En este punto, mirando la base de datos, encontramos que se generó un registro en la tabla anterior

Inserte la descripción de la imagen aquí
Los registros en la base de datos son consistentes con el token de recordarme que vimos después del análisis.

4. Análisis del código fuente

El análisis del código fuente aquí es básicamente el mismo que el proceso del artículo anterior, pero la clase de implementación ha cambiado, es decir, la implementación de la generación de tokens / análisis de tokens ha cambiado, así que aquí te muestro principalmente las diferencias, los problemas de proceso, todos Puede consultar el artículo anterior.

La clase de implementación principal esta vez es: PersistentTokenBasedRememberMeServices, primero veamos algunos métodos relacionados con la generación de tokens:

protected void onLoginSuccess(HttpServletRequest request,
  HttpServletResponse response, Authentication successfulAuthentication) {
    
    
 String username = successfulAuthentication.getName();
 PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
   username, generateSeriesData(), generateTokenData(), new Date());
 tokenRepository.createNewToken(persistentToken);
 addCookie(persistentToken, request, response);
}
protected String generateSeriesData() {
    
    
 byte[] newSeries = new byte[seriesLength];
 random.nextBytes(newSeries);
 return new String(Base64.getEncoder().encode(newSeries));
}
protected String generateTokenData() {
    
    
 byte[] newToken = new byte[tokenLength];
 random.nextBytes(newToken);
 return new String(Base64.getEncoder().encode(newToken));
}
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request,
  HttpServletResponse response) {
    
    
 setCookie(new String[] {
    
     token.getSeries(), token.getTokenValue() },
   getTokenValiditySeconds(), request, response);
}

puede ser visto:

(1) Después de iniciar sesión correctamente, primero obtenga el nombre de usuario, que es el nombre de usuario.
(2) A continuación, se utiliza un ejemplo de configuración PersistentRememberMeToken, generateSeriesDatay generateTokenDatase utilizan métodos para obtener seriesy token, de hecho, un proceso específico de generación de llamadas SecureRandomgenera un número aleatorio para la codificación de re-Base64, diferente de nuestro previamente utilizado Math.randomo java.util.Randomun número pseudoaleatorio, SecureRandom se utiliza Es similar a las reglas de generación de números aleatorios de la criptografía, y sus resultados de salida son más difíciles de predecir, adecuados para su uso en escenarios como el inicio de sesión.
(3) llama al método de tokenRepositoryinstancia createNewToken, tokenRepositoryde hecho, iniciamos la configuración JdbcTokenRepositoryImpl, por lo que esta línea de código se PersistentRememberMeTokenalmacenará en la base de datos.
(4) Finalmente addCookie, como puede ver, se han agregado series y token.

Este es el proceso de generación de tokens, así como el proceso de verificación de tokens. También en esta clase, el método es processAutoLoginCookie:

protected UserDetails processAutoLoginCookie(String[] cookieTokens,
  HttpServletRequest request, HttpServletResponse response) {
    
    
 final String presentedSeries = cookieTokens[0];
 final String presentedToken = cookieTokens[1];
 PersistentRememberMeToken token = tokenRepository
   .getTokenForSeries(presentedSeries);
 if (!presentedToken.equals(token.getTokenValue())) {
    
    
  tokenRepository.removeUserTokens(token.getUsername());
  throw new CookieTheftException(
    messages.getMessage(
      "PersistentTokenBasedRememberMeServices.cookieStolen",
      "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
 }
 if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
   .currentTimeMillis()) {
    
    
  throw new RememberMeAuthenticationException("Remember-me login has expired");
 }
 PersistentRememberMeToken newToken = new PersistentRememberMeToken(
   token.getUsername(), token.getSeries(), generateTokenData(), new Date());
 tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
    newToken.getDate());
 addCookie(newToken, request, response);
 return getUserDetailsService().loadUserByUsername(token.getUsername());
}

(1) Primero, analice la serie y el token de la cookie enviada desde la interfaz.
(2) Consultar una instancia de PersistentRememberMeToken de la base de datos de acuerdo con la serie.
(3) Si el token detectado es diferente del token enviado desde la interfaz, significa que la cuenta puede ser robada (después de que otra persona inicie sesión con su token, el token cambiará). En este momento, el token relevante se elimina de acuerdo con el nombre de usuario, lo que equivale a tener que volver a ingresar el nombre de usuario y la contraseña para iniciar sesión y obtener el nuevo permiso de inicio de sesión automático.
(4) A continuación, compruebe si el token ha caducado.
(5) Construya un nuevo objeto PersistentRememberMeToken y actualice el token en la base de datos (esto es lo que dijimos al principio del artículo, una nueva sesión corresponderá a un nuevo token).
(6) Vuelva a agregar el nuevo token a la cookie y regrese.
(7) Consulte la información del usuario según el nombre de usuario y luego realice el proceso de inicio de sesión.

Segundo, el segundo cheque

En comparación con el artículo anterior, el método de persistencia de tokens es mucho más seguro, pero aún existe el problema del robo de la identidad del usuario. Este problema es realmente difícil de resolver perfectamente. Lo que podemos hacer es solo cuando se produce la identidad del usuario. Cuando se roban tales cosas, la pérdida se minimiza.

Por lo tanto, veamos otra solución, que es la segunda verificación.

La segunda comprobación es un poco más complicada de implementar, así que déjame hablar primero de las ideas.

Para facilitar el uso a los usuarios, hemos habilitado la función de inicio de sesión automático, pero la función de inicio de sesión automático conlleva riesgos de seguridad. Una forma de evitarlo es que si el usuario utiliza la función de inicio de sesión automático, solo podemos permitirle realizar algunas operaciones insensibles convencionales. Por ejemplo, navegación y visualización de datos, pero no se le permite realizar ninguna operación de modificación o eliminación. Si el usuario hace clic en el botón modificar o eliminar, podemos volver a la página de inicio de sesión, dejar que el usuario vuelva a ingresar la contraseña para confirmar su identidad y luego permitirle realizar operaciones sensibles.

Esta función tiene un filtro más conveniente para configurar en Shiro, y Spring Security es, por supuesto, lo mismo. Por ejemplo, ahora proporciono tres interfaces de acceso:

(1) La primera /hellointerfaz después de la certificación, siempre que pueda acceder, ya sea a través de autenticación automática o de inicio de sesión, siempre que tenga la certificación, puede acceder mediante autenticación de nombre de usuario y contraseña.
(2) Se /adminpuede acceder a la segunda interfaz después de una autenticación de nombre de usuario y contraseña, si el usuario se registra automáticamente por certificado, debe volver a ingresar su nombre de usuario y contraseña para acceder a la interfaz.
(3) La tercera /remembermeinterfaz para acceder debe ser a través de la autenticación de inicio de sesión automática, si el usuario es un nombre de usuario / autenticación de contraseña, no puede acceder a la interfaz.

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .antMatchers("/rememberme").rememberMe()
            .antMatchers("/admin").fullyAuthenticated()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("javaboy")
            .tokenRepository(jdbcTokenRepository())
            .and()
            .csrf().disable();
}

(1) la /remembermeinterfaz es una necesidad rememberMede acceso.
(2) /admines obligatorio fullyAuthenticated, a fullyAuthenticateddiferencia de authenticated, fullyAuthenticatedno contiene un formulario de inicio de sesión automático y authenticatedcomprende un formulario de inicio de sesión automático.
(3) La última interfaz restante (/ hola) authenticatedpuede acceder.

Bien, una vez completada la configuración, reinicie la prueba

Inserte la descripción de la imagen aquí

Supongo que te gusta

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