[Texto extenso de Wanzi] SpringBoot integra el tutorial completo de SpringSecurity + JWT + Redis (se proporciona el código fuente de Gitee)

Prólogo: En el proceso de aprendizaje de SpringSecurity recientemente, me referí a muchos tutoriales en línea y también a algunos marcos de código abierto convencionales actuales, por lo que, combinado con mis propias ideas, escribí un proyecto SpringSecurity + JWT + Redis completo integrado con SpringBoot, desde 0. a 1 Después de escribir, siento que he ganado mucho, así que escribí mis notas completas como un blog para compartir con ustedes. ¡Es un proyecto relativamente completo y es solo para su referencia y estudio!

Tabla de contenido

1. Introducción a la seguridad de Spring

Dos, proceso de autenticación de Spring Security

3. Registro de problemas (importante)

4. Explicación del código central del proyecto.

4.1 Importar dependencias pom

4.2, archivo de configuración yml

4.3, clase de entidad

4.3.1, clase de entidad de inicio de sesión LoginBody

4.3.2, clase de rol de rol

4.3.3, clase de usuario usuario

4.3.4, información del usuario de inicio de sesión LoginUser

4.4, clase de servicio TokenService

4.4.1 Generar código central del token

4.4.2 Lógica clave para generar tokens

4.4.3 Analizar el código central del token

4.4.4 Obtener el token incluido en el encabezado de la solicitud

4.4.5 Obtener la clave token almacenada en Redis

4.4.6 Período de validez del token de actualización

4.4.7 Período de validez del token de verificación

4.4.8 Obtener información de identidad del usuario

4.4.9 Eliminar información de identidad del usuario

4.5, clase de procesamiento de fallas de autenticación de configuración AuthenticationEntryPointImpl

4.6, filtro de autenticación JwtAuthenticationTokenFilter

4.7, serialización FastJson

4.8, serialización personalizada de Redis 

4.9, clase de herramienta Redis

4.10, clase de configuración principal SecurityConfig

4.11, almacenamiento local del subproceso AuthenticationContextHolder

4.12 Interfaz de usuario de consulta UserServiceImpl

4.13, clase de servicio de verificación de contraseña PasswordServiceImpl 

4.14, clase de servicio de usuario de autenticación UserDetailsServiceImpl

4.15, interfaz de inicio de sesión de LoginController

4.16, lógica central de la interfaz de inicio de sesión LoginServiceImpl

4.17, lógica central de cierre de sesión de LogoutSuccessHandlerImpl 

4.18 Anotación @PreAuthorize

4.19, interfaz de prueba HelloController

4.20 Resumen

5. Ejecute el proyecto 

5.1. Inicio de sesión exitoso

5.2 Acceso a interfaces no autorizadas

5.3 Acceso a interfaces que requieren autorización de USUARIO

5.4 Acceso a interfaces que requieren permisos COMUNES

5.5 Cerrar sesión

5.6 Fallo de acceso 

5.7. Error al iniciar sesión

6. Dirección del código fuente de Gitee

7. Resumen


1. Introducción a la seguridad de Spring

Spring Security es un marco de gestión de seguridad en el ecosistema Spring que proporciona una solución completa para la seguridad de aplicaciones web.

Tiene las siguientes características:

1. Integralidad: Spring Security proporciona todas las funciones de gestión de seguridad, como autenticación, autorización y protección contra ataques.

2. Extensibilidad: las funciones de Spring Security se pueden ampliar fácilmente heredando clases e implementando interfaces.

3. Integración perfecta con Spring: se puede integrar perfectamente con el marco Spring y administrar los componentes SpringSecurity a través del contenedor SpringIoC.

4. Prevenir ataques comunes: puede prevenir ataques web comunes, como inyección de scripts, fijación de sesiones e inyección de SQL.

5. Configuración simple: las funciones de seguridad que ofrece Spring Security se pueden aplicar rápidamente a través de archivos de configuración.

Las principales funciones de Spring Security incluyen:

1. Autenticación: verificar la legitimidad de la información de identidad del usuario.

2. Autorización: Verifique que el usuario tenga permiso para realizar la operación.

3. Protección contra ataques: defensa contra ataques como CSRF, fijación de sesión e inyección SQL.

4. Seguridad del método: implementar control de acceso de seguridad con métodos del sistema.

5. Encabezado de respuesta de seguridad: agregue encabezados de respuesta relacionados con la seguridad del navegador para mejorar la seguridad.

En resumen, Spring Security es un marco de protección de seguridad indispensable para aplicaciones MVC, que proporciona soporte de seguridad integral para aplicaciones Java. Está estrechamente integrado con el marco Spring, es fácil de configurar y de usar.

Dos, proceso de autenticación de Spring Security

Varias clases principales y sus funciones en el proceso de autenticación de Spring Security son las siguientes:

1. Autenticación : interfaz de información de autenticación, que indica la información de autenticación del usuario actual, generalmente implementada mediante UsernamePasswordAuthenticationToken .

2. AuthenticationManager : la interfaz del administrador de autenticación, el método authenticate () se utiliza para ejecutar el proceso de autenticación.

3. ProviderManager : una implementación común de la interfaz del administrador de autenticación, que encapsula múltiples AuthenticationProviders .

4. AuthenticationProvider : un procesador de autenticación específico, que completa un mecanismo de autenticación específico .

5. UserDetailsService : carga información del usuario según el nombre de usuario y devuelve la implementación de la interfaz UserDetails .

6. UserDetails : una interfaz que contiene información del usuario y el marco representa la información del usuario .

7. UsernamePasswordAuthenticationFilter : un filtro que maneja la autenticación de inicio de sesión .

8. AbstractAuthenticationProcessingFilter : la clase base del filtro de procesamiento de autenticación .

9. SecurityContextHolder : contenedor de contexto de seguridad , acceso al objeto de autenticación . 

Este es el proceso completo de autenticación de SpringSecurity:

1. El usuario envía el nombre de usuario y la contraseña al sistema para su autenticación.

2. AuthenticationFilter interceptará la solicitud y extraerá el nombre de usuario y la contraseña de la solicitud para construir un UsernamePasswordAuthenticationToken .

3. AuthenticationFilter transfiere UsernamePasswordAuthenticationToken a AuthenticationManager .

4. AuthenticationManager encontrará un AuthenticationProvider coincidente para la autenticación.

5. AuthenticationProvider primero llamará al método loadUserByUsername() de UserDetailsService para cargar la información del usuario de acuerdo con el nombre de usuario.

6. UserDetailsService consulta la base de datos según el nombre de usuario y construye un objeto UserDetails , que incluye información del usuario, permisos, etc.

7. AuthenticationProvider utiliza UserDetails y la contraseña ingresada por el usuario para realizar la verificación coincidente . Si coincide, la verificación es exitosa.

8. Si la verificación es exitosa, AuthenticationProvider construirá un objeto de autenticación autenticado .

9. AuthenticationProvider devuelve la autenticación a AuthenticationManager .

10. AuthenticationManager establece la autenticación en SecurityContextHolder .

11. El control de acceso posterior utilizará la información de autenticación en SecurityContextHolder para verificar la identidad y autoridad del usuario.

12. El inicio de sesión se realiza correctamente y el usuario accede a los recursos protegidos del sistema.

El proceso completo se muestra en la figura:

3. Registro de problemas (importante)

Lea lo siguiente, asegúrese de comprender este error primero:

autoType no es el registro de error support.org.springframework.security.core.authority.SimpleGrantedAuthority (prueba profesional disponible)

4. Explicación del código central del proyecto.

Debido a que la cantidad de código es relativamente grande, sacaré el código clave de todo el proyecto y lo explicaré por separado, y no publicaré los otros menores, la razón principal es que todos comprendan más el proceso de ejecución de Spring Security. Fácilmente abriré el código fuente en Gitee y lo proporcionaré al final del artículo.

4.1 Importar dependencias pom

Las dependencias completas están publicadas.

<dependencies>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- lombok依赖包 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.16.10</version>
			<scope>provided</scope>
		</dependency>


		<!-- spring security 安全认证 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

		<!-- 单元测试 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- jwt -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

		<!-- redis依赖 对象池 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<!-- pool 对象池 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-pool2</artifactId>
			<version>2.11.1</version>
		</dependency>

		<!-- 常用工具类 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.9</version>
		</dependency>

		<!-- 阿里JSON解析器 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>2.0.22</version>
		</dependency>

	</dependencies>

4.2, archivo de configuración yml

Configura principalmente una conexión Redis y una constante Token.

spring:
  redis:
    host: localhost
    port: 6379
    database: 0
    password:
    timeout: 10s
    lettuce:
      pool:
        min-idle: 0
        max-idle: 8
        max-active: 8
        max-wait: -1ms

token:
  header: Authorization
  secret: oqwe9sdladwosqwqs
  expireTime: 30

4.3, clase de entidad

Están involucradas un total de cuatro clases de entidades y el diseño principal de algunos campos clave no es muy completo.

4.3.1, clase de entidad de inicio de sesión LoginBody

Esta clase se utiliza principalmente para recibir el nombre de usuario y la contraseña pasados ​​por el front-end y luego para verificar la información de inicio de sesión.

Código completo:

package com.example.security.domain;

import lombok.Data;

@Data
public class LoginBody
{
    /**
     * 用户名
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

}

4.3.2, clase de rol de rol

Para almacenar la información de rol de cada usuario, es necesario implementar la interfaz de serialización.

Código completo:

package com.example.security.domain;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class Role implements Serializable {

  /**
   * 角色主键
   */
  private Long id;

  /**
   * 角色名称
   */
  private String name;

}

4.3.3, clase de usuario usuario

Almacena principalmente información del usuario y necesita implementar una interfaz de serialización.

Código completo:

package com.example.security.domain;

import lombok.Data;

import java.io.Serializable;
import java.util.Set;

@Data
public class User implements Serializable {

    /**
     * 主键
     */
    private String id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 角色集合
     */
    private Set<Role> roles;


}

4.3.4, información del usuario de inicio de sesión LoginUser

Es necesario implementar la interfaz UserDetails que viene con Spring Security e implementar todos sus métodos. En Spring Security podemos expresar los permisos que posee un usuario a través de la interfaz GrantedAuthority.

método explicar
esCuentaNoExpired() ¿Ha caducado la cuenta?
esCuentaNonLocked() ¿La cuenta está bloqueada?
isCredentialsNonExpired() Si la credencial (contraseña) ha caducado
está habilitado() ¿Está disponible la cuenta?

El propósito de estos métodos que devuelven verdadero es simplificar la lógica. Cuando no se implementa el juicio de estado correspondiente, la configuración predeterminada es verdadera, lo que puede evitar fallas innecesarias de autenticación/autorización. 

Código completo:

package com.example.security.domain;

import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Set;

@Data
public class LoginUser implements UserDetails {

    public LoginUser(User user,Set<GrantedAuthority> authorities)
    {
        this.user = user;
        this.authorities = authorities;

    }

    /**
     * 用户信息
     */
    private User user;

    /**
     * 权限信息
     */
    private Set<GrantedAuthority> authorities;

    /**
     * token信息
     */
    private String token;

    /**
     * 登录时间
     */
    private Long loginTime;
    /**
     * 过期时间
     */
    private Long expireTime;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

4.4, clase de servicio TokenService

Primero inyecte algunos parámetros.

clave:

@Value("${token.header}")
private String header;

@Value("${token.secret}")
private String secret;

@Value("${token.expireTime}")
private int expireTime;

protected static final long MILLIS_SECOND = 1000;

protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

4.4.1 Generar código central del token

clave:

    private String generateToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

4.4.2 Lógica clave para generar tokens

LOGIN_TOKEN_KEY ordinario: login_tokens:

1. Primero genere un UUID aleatorio como valor del token y configúrelo en el objeto LoginUser.

2. Establezca la hora de inicio de sesión y la hora de vencimiento de LoginUser (hora actual + hora de vencimiento).

3. Almacene el objeto LoginUser en Redis, la clave es LOGIN_TOKEN_KEY+token y el tiempo de vencimiento predeterminado es 30 minutos configurado en yml.

4. Finalmente, llame al método generateToken para generar un token JWT y páselo a la colección de mapas hash de reclamos.

clave:

    public String createToken(LoginUser loginUser)
    {
        String token = UUID.randomUUID().toString();
        loginUser.setToken(token);
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        String userKey = CacheConstants.LOGIN_TOKEN_KEY + token;
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        return generateToken(claims);
    }

4.4.3 Analizar el código central del token

clave:

 private Claims parseToken(String token)
    {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

4.4.4 Obtener el token incluido en el encabezado de la solicitud

Constante TOKEN_PREFIX: "Portador"

1. Obtenga la información de autorización del nombre especificado (el encabezado configurado en el archivo yml) del encabezado de la solicitud.

2. Determine si el token obtenido no está vacío y comienza con el prefijo especificado (Constants.TOKEN_PREFIX).

3. En caso afirmativo, elimine el prefijo para obtener el token JWT final.

clave:

    private String getToken(HttpServletRequest request)
    {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
        {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

4.4.5 Obtener la clave token almacenada en Redis

clave:

    private String getTokenKey(String uuid)
    {
        return CacheConstants.LOGIN_TOKEN_KEY + uuid;
    }

4.4.6 Período de validez del token de actualización

1. El parámetro loginUser es la información del usuario de inicio de sesión actual.

2. Establezca la nueva hora de inicio de sesión de loginUser en la hora actual.

3. Vuelva a calcular el tiempo de vencimiento como el tiempo actual + tiempo de vencimiento (expireTime configurado en el archivo yml).

4. De acuerdo con el token del usuario que inició sesión como clave, almacene el usuario de inicio de sesión actualizado en Redis y establezca el tiempo de vencimiento.

5. Esto equivale a actualizar el tiempo de vencimiento del token.

clave:

    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

4.4.7 Período de validez del token de verificación

El período de validez del token de verificación es inferior a 20 minutos y el caché se actualiza automáticamente.

clave:

    public void verifyToken(LoginUser loginUser)
    {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
        {
            refreshToken(loginUser);
        }
    }

4.4.8 Obtener información de identidad del usuario

1. Primero obtenga el token JWT de la solicitud.

2. Si el Token no está vacío, analícelo para obtener reclamaciones.

3. Saque el uuid correspondiente de los reclamos.

4. Obtenga el objeto LoginUser de Redis de acuerdo con el uuid como clave.

5. Si la adquisición es exitosa, se devuelve el objeto LoginUser.

6. Si falla el análisis del token o la obtención del usuario, devuelva nulo.

clave:

    public LoginUser getLoginUser(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token))
        {
            try
            {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser user = redisCache.getCacheObject(userKey);
                return user;
            }
            catch (Exception e)
            {
            }
        }
        return null;
    }

4.4.9 Eliminar información de identidad del usuario

Obtenga el identificador único del usuario en redis a través del token para borrar los datos de inicio de sesión del usuario, y la próxima vez que pase por el filtro, el acceso estará restringido porque no hay información del usuario.

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token)
    {
        if (StringUtils.isNotEmpty(token))
        {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
    }

4.5, clase de procesamiento de fallas de autenticación de configuración AuthenticationEntryPointImpl

Constante NO AUTORIZADO: 401

AuthenticationEntryPoint es una interfaz utilizada en Spring Security para manejar fallas de autenticación. Se usa en el caso de que no se inicie sesión o caduque el inicio de sesión, y se activará el método de inicio.

1. Dentro del método, primero establezca el código de estado de respuesta en 401 No autorizado.

2. Luego use StringUtils para generar una cadena de mensaje de error, incluida la ruta de la interfaz del acceso solicitado y el mensaje de falla de autenticación. 3. Finalmente, use AjaxResult para encapsular el código de estado y la información de error en un resultado y escríbalo en la respuesta en formato JSON a través de ServletUtils.

4. AjaxResult es una clase que encapsula los resultados de la solicitud AJAX, lo que puede generar fácilmente errores o resultados de respuesta exitosos.

5. ServletUtils es una clase de herramienta que puede representar fácilmente datos de cadenas en HttpServletResponse.

Por lo tanto, la función de esta clase es devolver un resultado que incluye el código de error y el mensaje al front-end en formato JSON cuando falla la autenticación. El front-end puede mostrar las indicaciones correspondientes o realizar el procesamiento de acuerdo con el resultado.

clave:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint
{
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        int code = HttpStatus.UNAUTHORIZED;
        String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI());
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
    }
}

4.6, filtro de autenticación JwtAuthenticationTokenFilter

OncePerRequestFilter es una clase base de filtro proporcionada por SpringSecurity. Se utiliza principalmente para garantizar que el filtro solo se ejecute una vez en una solicitud. JwtAuthenticationTokenFilter necesita heredar esta clase base y reescribir el método doFilterInternal.

1. Extraiga el token JWT del encabezado de la solicitud a través de tokenService y analícelo para obtener el objeto LoginUser.

2. Llame al método verificarToken de tokenService para verificar la validez del token JWT.

3. Utilice el objeto LoginUser para crear un UsernamePasswordAuthenticationToken

4. Configure los detalles de AuthenticationToken, como el origen de la solicitud, etc.

5. Establezca el objeto UsernamePasswordAuthenticationToken construido en el contexto de SecurityContextHolder.

6. De esta manera, el objeto de autenticación del usuario que inició sesión se guarda en el contexto de seguridad.

7. Finalmente, la cadena de filtros continúa ejecutando el método doFilter al revés.

De esta manera, el análisis y la verificación del token se realizan en el filtro y el objeto de autenticación se establece en el contexto de seguridad, de modo que los filtros
posteriores puedan juzgar la información de autenticación del usuario en función de él.

clave:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{

    @Value("${token.header}")
    private String header;

    @Value("${token.secret}")
    private String tokenKey;

    @Resource
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {

        LoginUser loginUser = tokenService.getLoginUser(request);
        if (loginUser != null)
        {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

 Nota: Si desea obtener la información del usuario actualmente conectado, puede obtenerla a través de las siguientes 2 líneas de código.

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser) authentication.getPrincipal();

4.7, serialización FastJson

package com.example.security.config;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

4.8, serialización personalizada de Redis 

Nota: La explicación detallada se encuentra en este artículo: SpringBoot integra RedisTemplate para operar la base de datos de Redis en detalle (se proporciona el código fuente de Gitee) 

package com.example.security.config;

import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        String[] acceptNames = {"org.springframework.security.core.authority.SimpleGrantedAuthority"};
        GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer(acceptNames);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

}

4.9, clase de herramienta Redis

package com.example.redis.utils;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
 
import java.util.*;
import java.util.concurrent.TimeUnit;
 
/**
 * spring redis 工具类
 **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;
 
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }
 
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
 
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }
 
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }
 
    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }
 
    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }
 
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
 
    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }
 
    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }
 
    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }
 
    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
 
    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }
 
    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }
 
    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }
 
    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }
 
    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }
 
    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }
 
    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }
 
    /**
     * 删除Hash中的某条数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }
 
    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
 
    /**
     * 存储有序集合
     * @param key 键
     * @param value 值
     * @param score 排序
     */
    public void zSet(Object key, Object value, double score){
        redisTemplate.opsForZSet().add(key, value, score);
    }
 
    /**
     * 存储值
     * @param key 键
     * @param set 集合
     */
    public void zSet(Object key, Set set){
        redisTemplate.opsForZSet().add(key, set);
    }
 
    /**
     * 获取key指定范围的值
     * @param key 键
     * @param start 开始位置
     * @param end 结束位置
     * @return 返回set
     */
    public Set zGet(Object key, long start, long end){
        Set set = redisTemplate.opsForZSet().range(key, start, end);
        return set;
    }
 
    /**
     * 获取key对应的所有值
     * @param key 键
     * @return 返回set
     */
    public Set zGet(Object key){
        Set set = redisTemplate.opsForZSet().range(key, 0, -1);
        return set;
    }
 
    /**
     * 获取对用数据的大小
     * @param key 键
     * @return 键值大小
     */
    public long zGetSize(Object key){
        Long size = redisTemplate.opsForZSet().size(key);
        return size;
    }
}

4.10, clase de configuración principal SecurityConfig

Este es el código central y todos los comentarios están en el código. Puede verlo más de cerca usted mismo, por lo que no daré más detalles aquí.

Código completo:

package com.example.security.config;

import com.example.security.filter.JwtAuthenticationTokenFilter;
import com.example.security.service.serviceImpl.AuthenticationEntryPointImpl;
import com.example.security.service.serviceImpl.LogoutSuccessHandlerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;

    /**
     * token认证过滤器
     */
    @Resource
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 认证失败处理类
     */
    @Resource
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Resource
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                //允许登录接口匿名访问
                .antMatchers("/login").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }


    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

4.11, almacenamiento local del subproceso AuthenticationContextHolder

Proporciona principalmente los siguientes métodos estáticos:

1. getContext() obtiene el objeto de autenticación del hilo actual.

2. setContext (contexto de autenticación) establece el objeto de autenticación del hilo actual.

3. clearContext() borra el objeto de autenticación del hilo actual.

Utiliza ThreadLocal para mantener el aislamiento de los subprocesos, de modo que cada subproceso tenga su propia información de autenticación sin interferir entre sí.

En Spring Security, la información de autenticación se puede pasar en diferentes capas a través de esta clase.

clave:

public class AuthenticationContextHolder
{
    private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

    public static Authentication getContext()
    {
        return contextHolder.get();
    }

    public static void setContext(Authentication context)
    {
        contextHolder.set(context);
    }

    public static void clearContext()
    {
        contextHolder.remove();
    }
}

4.12 Interfaz de usuario de consulta UserServiceImpl

Aquí soy vago y no me conecté a la base de datos, esta contraseña se obtiene mediante cifrado con el siguiente código.

clave:

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encode = passwordEncoder.encode("123456");
System.out.println(encode);

Es principalmente para demostración, por lo que la información del usuario y la información de la función se escriben directamente aquí, y el desarrollo real aún necesita conectarse a la base de datos.

clave:

@Service
public class UserServiceImpl {

    public User selectUserByUsername(String username){
        User user = new User();
        user.setId(UUID.randomUUID().toString());
        user.setUsername(username);
        user.setPassword("$2a$10$ErrO7WgkEBAWVQwuJtbBve7R2.pSKUrfs7zt8XkASqJKqcetMvAUC");
        Set<Role> roles = new HashSet<>();
        Role role1 = new Role(1L, "ROLE_ADMIN");
        Role role2 = new Role(2L, "ROLE_USER");
        roles.add(role1);
        roles.add(role2);
        user.setRoles(roles);
        return user;
    }
}

Aquí los usuarios deben usar ROLE_ como prefijo. Para un análisis específico, consulte el análisis de anotaciones @PreAuthorize en 3.15 .

4.13, clase de servicio de verificación de contraseña PasswordServiceImpl 

1. El método validar() se utiliza para verificar la contraseña del usuario.

2. Primero obtiene el nombre de usuario y la contraseña actualmente autenticados del AuthenticationContextHolder.

3. Luego llame al métodomatches() para verificar la contraseña.

4. El métodomatches() utiliza BCryptPasswordEncoder para realizar una verificación coincidente en la contraseña de texto cifrado almacenada.

5. Si la coincidencia es exitosa, la verificación es exitosa y si falla, la verificación falla.

De esta forma, la información de la principal de autenticación actual se puede obtener a través del AuthenticationContextHolder de Spring Security.

Combinada con la verificación de coincidencia de cifrado de contraseña, la verificación de contraseña se puede implementar convenientemente en el servicio.

clave:

@Service
public class PasswordServiceImpl {

    public void validate(User user)
    {
        Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
        String AuthUsername = usernamePasswordAuthenticationToken.getName();
        String AuthPassword = usernamePasswordAuthenticationToken.getCredentials().toString();

        if (matches(user, AuthPassword)) {
            System.out.println("验证成功!");
        } else {
            System.out.println("验证失败!");
        }
    }

    public boolean matches(User user, String rawPassword)
    {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, user.getPassword());
    }
}

4.14, clase de servicio de usuario de autenticación UserDetailsServiceImpl

1. Implemente la interfaz UserDetailsService de Spring Security.

UserDetailsService es la interfaz principal utilizada por Spring Security para cargar información del usuario. Las implementaciones personalizadas pueden controlar de manera flexible el proceso de carga de información del usuario.

2. Cargue información del usuario según el nombre de usuario.Consulte la base de datos a través del servicio de usuario para obtener objetos de usuario, incluida información del usuario como nombre de usuario, contraseña, función, etc.

3. Verificar la contraseña del usuario Utilice el servicio de contraseña para verificar la contraseña y comprobar si la contraseña de inicio de sesión es correcta.

4. Construya información de autoridad del usuario y convierta la información de la función del usuario en una recopilación de información de autorización de GrantedAuthority.

5. Encapsule la devolución del objeto de usuario. Encapsule la información del usuario y la información de permisos en el objeto LoginUser y devuélvala como UserDetails.

6. Proporcione los detalles del usuario durante la verificación de inicio de sesión. Spring Security llamará a este servicio para obtener los detalles del usuario durante la verificación de inicio de sesión para autenticación y autorización.

clave:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private PasswordServiceImpl passwordService;

    @Resource
    private UserServiceImpl userService;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userService.selectUserByUsername(username);
        passwordService.validate(user);
        //取出角色和权限信息
        Set<Role> roles = user.getRoles();

        Set<GrantedAuthority> authorities = new HashSet<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return new LoginUser(user,authorities);
    }

}

4.15, interfaz de inicio de sesión de LoginController

Todos los inicios de sesión deben pasar por esta interfaz. Después de iniciar sesión correctamente, se devolverá un token al usuario para acceder a los recursos protegidos del sistema.

clave: 

@RestController
public class LoginController {

    @Resource
    private LoginServiceImpl loginService;

    @PostMapping("/login")
    public AjaxResult login(@RequestBody LoginBody loginBody)
    {
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword());
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }


}

4.16, lógica central de la interfaz de inicio de sesión LoginServiceImpl

1. Cree UsernamePasswordAuthenticationToken, que contiene el nombre de usuario y la contraseña.

2. Establezca el token de autenticación en AuthenticationContextHolder, que es un soporte para almacenar la autenticación proporcionada por Spring Security.

3. Llame al método de autenticación de AuthenticationManager para la autenticación. Este método llamará a UserDetailsService relacionado según la configuración para la autenticación. ( A continuación se seguirá el método loadUserByUsername en la clase de servicio de información de usuario de autenticación ), y se devolverá un objeto de autenticación después de que la verificación sea exitosa.

4. Borre AuthenticationContextHolder.

5. Obtenga la información del usuario de inicio de sesión LoginUser del objeto de autenticación.

6. Utilice TokenService para generar el token JWT.

7. Devuelva el token JWT.

clave:

@Service
public class LoginServiceImpl {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private TokenService tokenService;

    public String login(String username, String password)
    {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        AuthenticationContextHolder.setContext(authenticationToken);
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        AuthenticationContextHolder.clearContext();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        return tokenService.createToken(loginUser);
    }

}

4.17, lógica central de cierre de sesión de LogoutSuccessHandlerImpl 

LogoutSuccessHandlerImpl implementa la interfaz LogoutSuccessHandler de Spring Security, que se utiliza para manejar la lógica después de que el usuario cierra sesión correctamente.

1. Obtenga la información del usuario actualmente conectado LoginUser a través de TokenService.

2. Si LoginUser no está vacío, llame al método delLoginUser de TokenService para eliminar la información de caché del usuario. Esto implica un TokenService personalizado para manejar la autenticación de usuarios basada en tokens. Al iniciar sesión, se genera un token correspondiente a la información del usuario y la relación de caché debe eliminarse al cerrar sesión.

3. Utilice ServletUtils para escribir un mensaje de salida exitoso en la respuesta, de modo que la solicitud ajax pueda obtener la información del mensaje.

clave:

/**
 * 退出登录
 */
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Autowired
    private TokenService tokenService;

    /**
     * 退出处理
     * @return
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser))
        {
            // 删除用户缓存记录
            tokenService.delLoginUser(loginUser.getToken());
        }
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("退出成功")));
    }
}

4.18 Anotación @PreAuthorize

Esta anotación se coloca en la interfaz para marcar que solo los usuarios específicos tienen acceso a la interfaz.

1. Cuando uso la anotación @PreAuthorize("hasRole('USER')") en la interfaz , puedo analizar el proceso de ejecución del código fuente interno por usted.

Primero obtenga el parámetro de interfaz configurado arriba como USUARIO .

Ingrese el método hasAnyAuthorityName para obtener todos los permisos del usuario actual ( colección roleSet ) y luego use el bucle for para recorrer la matriz de parámetros en la anotación. Este ciclo es solo una vez, porque solo hay un USUARIO en la anotación , por lo que la longitud de la matriz es 1. Si la información de permiso de la interfaz actual está incluida en todos los conjuntos de permisos del usuario actual, se permitirá; de lo contrario, no se publicará. 

3. El método getRoleWithDefaultPrefix se utiliza para detectar si el rol pasado actualmente tiene el prefijo ROLE_ predeterminado ; de ser así, se devolverá directamente; de ​​lo contrario, se devolverá con el prefijo predeterminado ROLE_ .

Este es el proceso de verificación de esta anotación, que también explica por qué los permisos de usuario deben tener el prefijo ROLE_ en 3.9.

4.19, interfaz de prueba HelloController

Escriba dos interfaces de prueba respectivamente, una es una interfaz a la que cualquiera puede acceder después de iniciar sesión, una es una interfaz a la que solo pueden acceder los usuarios que necesitan permiso de USUARIO después de iniciar sesión, y la otra es una interfaz a la que solo se puede acceder después de iniciar sesión que requiere Permiso COMÚN.

clave:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "Hello World!";
    }

    @GetMapping("/user")
    @PreAuthorize("hasRole('USER')")
    public String user(){
        return "Hello USER!";
    }

    @GetMapping("/common")
    @PreAuthorize("hasRole('COMMON')")
    public String common(){
        return "Hello COMMON!";
    }
}

Nota: Debe modificarse con un método público; de lo contrario, la anotación @PreAuthorize no será válida.

4.20 Resumen

Finalmente, resumimos el proceso de autenticación de Spring Security a través de proyectos reales:

1. En el método de inicio de sesión de LoginService, cree un UsernamePasswordAuthenticationToken, que contiene el nombre de usuario y la contraseña, que es el punto de entrada para la autenticación.

2. LoginService llama al método de autenticación de AuthenticationManager para iniciar el proceso de autenticación.

3. AuthenticationManager encontrará un AuthenticationProvider coincidente para la autenticación.

4. AuthenticationProvider llamará al método loadUserByUsername de UserDetailsService para cargar la información del usuario. Aquí consultamos a los usuarios a través de UserDetailsServiceImpl.

5. En UserDetailsServiceImpl, consulte la información del usuario según el nombre de usuario y luego llame a PasswordService para verificar la contraseña.

6. PasswordService obtiene el nombre de usuario y la contraseña de inicio de sesión a través de AuthenticationContextHolder. Luego haga coincidir con la contraseña de usuario (codificada) almacenada en la base de datos y, si coincide, la verificación es exitosa.

7. Una vez que la autenticación de PasswordService sea exitosa, UserDetailsServiceImpl construirá un objeto UserDetails (aquí está LoginUser) de acuerdo con la información del usuario, incluido el nombre de usuario, la contraseña, la información de permisos, etc.

8. UserDetailsServiceImpl devuelve UserDetails a AuthenticationProvider.

9. Después de que AuthenticationProvider recibe los detalles del usuario, completa la verificación y genera un objeto de autenticación autenticado.

10. AuthenticationProvider devuelve la autenticación a AuthenticationManager.

11. AuthenticationManager establece la autenticación en SecurityContextHolder para el control de acceso posterior.

12. LoginService obtiene la autenticación autenticada, extrae los detalles del usuario, genera JWTtoken y lo devuelve.

En resumen, combinado con la lógica del proyecto, el proceso de autenticación de Spring Security se puede dividir aproximadamente en: obtener información del usuario -> verificación del usuario -> crear detalles de usuario -> generar autenticación. Implementamos la lógica de autenticación de usuarios personalizando UserDetailsService y PasswordService. 

5. Ejecute el proyecto 

5.1. Inicio de sesión exitoso

Envíe datos en formato json mediante solicitud posterior de inicio de sesión.

¡El inicio de sesión se realizó correctamente y se devuelve el token Token!

5.2 Acceso a interfaces no autorizadas

Configure el token que acaba de obtener en el encabezado de la solicitud para acceder a la interfaz de prueba.

5.3 Acceso a interfaces que requieren autorización de USUARIO

Cambie hola a usuario.

Debido a que este usuario está configurado con dos permisos de ADMIN y USUARIO de forma predeterminada, ¡el acceso puede ser exitoso! 

5.4 Acceso a interfaces que requieren permisos COMUNES

Cambie de usuario a común.

Obviamente, se devolvió un mensaje de error 403. 

5.5 Cerrar sesión

Cambie común para cerrar sesión.

Cerrar sesión correctamente.

5.6 Fallo de acceso 

Cierre sesión arriba y vea si aún puede acceder a la interfaz de prueba llevando el token anterior.

Se puede ver claramente que el sistema devuelve un error 401. 

5.7. Error al iniciar sesión

Todos somos ejemplos de inicio de sesión exitoso arriba. Esta vez ingresamos deliberadamente la contraseña incorrecta para ver si podemos iniciar sesión exitosamente.

Se puede ver claramente que el sistema devuelve un error 401. 

6. Dirección del código fuente de Gitee

Debido a que el código proporcionado en este blog no está completo, abro el proyecto completo en Code Cloud para su estudio y referencia.

Dirección del proyecto: SpringBoot integra el tutorial completo de SpringSecurity + JWT + Redis

7. Resumen

Lo anterior es mi comprensión personal de Spring Security y cómo desarrollar aplicaciones en proyectos reales. Si tiene alguna pregunta, ¡deje un mensaje en el área de comentarios!

Supongo que te gusta

Origin blog.csdn.net/HJW_233/article/details/131969622
Recomendado
Clasificación