[Proyecto de combate real] Spring Cloud Gateway implementa el proceso de autenticación de inicio de sesión JWT / Token

Tabla de contenido

I. Introducción

1. Introducción a Spring Cloud Gateway

2. Introducción al token

2. Proceso de autenticación de inicio de sesión

3. Proyecto de combate real

1. servicio de autenticación de autenticación

2. servicio de puerta de enlace


I. Introducción

Este artículo demostrará cómo crear un token al iniciar sesión, luego verificar el token en la puerta de enlace, extraer la información del usuario en el encabezado de solicitud del encabezado y enviarlo al sistema empresarial posterior.

El proyecto utiliza Spring Cloud Gateway como puerta de enlace para el enrutamiento unificado de microservicios. El módulo de autenticación de autenticación es responsable de generar el token y el módulo de puerta de enlace de puerta de enlace es responsable de verificar el token y unificar la entrada .

La función de la puerta de enlace es la de una arquitectura API para proteger, mejorar y controlar el acceso a los servicios API. La puerta de enlace API es un sistema frente a una aplicación o servicio (que proporciona servicios de interfaz API REST), que se utiliza para administrar la autorización, el control de acceso, la restricción de tráfico, etc., de modo que el servicio de interfaz API REST está protegido por la puerta de enlace API y es transparente para todas las personas que llaman. . Por lo tanto, el sistema empresarial oculto detrás de la puerta de enlace API puede centrarse en crear y gestionar servicios en lugar de ocuparse de estas infraestructuras estratégicas.

1. Introducción a Spring Cloud Gateway

Spring Cloud Gateway es una pasarela Spring oficial desarrollada en base a tecnologías como Spring 5.0, Spring Boot 2.0 y Project Reactor . Spring Cloud Gateway tiene como objetivo proporcionar un método de administración de enrutamiento API unificado simple y efectivo para la arquitectura de microservicios. Como puerta de enlace en el ecosistema de Spring Cloud, Spring Cloud Gateway tiene como objetivo reemplazar ZUUL . No solo proporciona un método de enrutamiento unificado , sino que también proporciona funciones básicas de la puerta de enlace basadas en la cadena de filtro, como: seguridad, monitoreo / puntos enterrados y limitación de corriente Espere.

2. Introducción al token

Un jwt es en realidad una cadena, que consta de tres partes, el encabezado , la carga útil y la firma . Estas tres partes están todas en formato json.

Para obtener más información, consulte: https://blog.csdn.net/a1036645146/article/details/103726635

2.1 Encabezado

El encabezado se utiliza para describir la información más básica sobre el JWT, como su tipo y algoritmo de firma.

{
    "typ": "JWT",
    "alg": "HS256"
}

Aquí, explicamos que este es un JWT, y el algoritmo de firma que usamos es el algoritmo HS256 .

2.2 Carga útil

La carga útil se puede utilizar para poner información insensible.

{
    "iss": "John Wu JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.example.com",
    "sub": "[email protected]",
    "from_user": "B",
    "target_user": "A"
}

Los primeros cinco campos están definidos por el estándar JWT.

  • iss: Emisor del JWT
  • sub: El usuario objetivo de este JWT
  • aud: La parte que recibe el JWT
  • exp(expira): ¿Cuándo expira? Aquí hay una marca de tiempo de Unix
  • iat(emitido en): cuándo se emitió

Se obtienen dos cadenas después de la codificación Base64 del encabezado y la carga útil respectivamente, y luego las dos cadenas codificadas se .conectan con un punto (encabezado primero) para formar una nueva cadena:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

2.3 Firma

Finalmente, usamos el algoritmo HS256 para cifrar la cadena empalmada anterior. Al cifrar, también debemos proporcionar un secreto. El contenido cifrado también es una cadena, y la última cadena es la firma. Al empalmar esta firma detrás de la cadena justo ahora, puede obtener el jwt completo. Si la parte del encabezado y la parte de carga útil se manipulan, ya que el manipulador no sabe cuál es la clave y no puede generar una nueva parte de firma , el servidor no podrá pasar. En jwt, el cuerpo del mensaje es transparente y la firma puede garantizar que el mensaje no sea Ha sido manipulado.

Por supuesto, si el token de una persona es robado por otros, entonces no puedo evitarlo. También pensaré que el ladrón es un usuario legítimo, que en realidad es lo mismo que el ID de sesión de una persona que es robado por otros.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJyb2xlcyI6InB1cmNoYXNlciIsInNvdXJjZSI6IlBDIiwidXNlck5hbWUiOiIxNTYyMjEzMzIzMSIsImV4cCI6MTYwNDgwMDM5NSwidXNlcklkIjoiMTE5MCJ9
.zpM6dyakS1N6kySMIEHuOZfN8l4WlybRbq6VK1cDqGc

 

2. Proceso de autenticación de inicio de sesión

Una vez que el usuario inicia sesión correctamente, el servicio de autenticación (módulo de autenticación) genera un token y todas las solicitudes de front-end posteriores llevan este token en el encabezado de la solicitud. La puerta de enlace es responsable de verificar el token y colocar la información del usuario en el encabezado de la solicitud para que el sistema descendente pueda obtener fácilmente la información del usuario.

El proceso de autenticación básico basado en la autenticación Token es el siguiente:

  1. El usuario ingresa la información de inicio de sesión (o llama a la interfaz Token para pasar la información del usuario) y la envía al servicio de autenticación para su autenticación. Nota: El servicio de autenticación de identidad puede estar junto con el servidor o separado, según la división de los microservicios, aquí hay un único módulo de autenticación.
  2. El servicio de autenticación (auth) verifica si la información de inicio de sesión es correcta y devuelve el Token si es correcto (la interfaz general contendrá la información básica del usuario, el alcance de la autoridad, la hora válida y otra información).
  3. El cliente almacena el Token. Al solicitar otra interfaz nuevamente, el Token se puede colocar en el encabezado de la solicitud HTTP para iniciar llamadas API relacionadas.
  4. Para acceder a la interfaz de back-end, el cliente debe pasar a través de la puerta de enlace. La puerta de enlace configura el interceptor de manera uniforme, verifica la autoridad del token y luego lo reenvía a las otras interfaces de microservicio de back-end correspondientes.
  5. Una vez pasada la verificación, el servidor devuelve los recursos y datos relacionados.

El proyecto involucra módulos:

  • puerta de enlace - servicio de puerta de enlace
  • auth - servicio de autenticación
  • servicio-usuario
  • orden-servicio
  • (Otros microservicios) ...

Cada aplicación de módulo se construye en base a SpringBoot, utilizando Nacos como centro de configuración y centro de registro, Feign se usa para llamadas de interfaz entre servicios, se publica la interfaz Http (API REST), la puerta de enlace se usa como la entrada unificada a la API, las solicitudes de front-end no pueden llamar directamente a los microservicios descendentes Interfaz específica.

3. Proyecto de combate real

1. servicio de autenticación de autenticación

Cree un módulo de servicio de autenticación de autenticación a través de SpringBoot y use Nacos en el centro de registro y configuración.

Este módulo proporciona principalmente una interfaz de inicio de sesión y genera token.

1.1 pom.xml

Necesita importar el kit de herramientas jwt y usar la última versión estable.

<dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>${jwt.version}</version>
</dependency>

1.2 Clase de herramienta JWTUtils.java

/**
 * jwt工具类
 *
 * @author stwen_gan
 * @since 
 */
public class JWTUtils {

    // token 签名的秘钥,可设置到配置文件中
    private static final String SECRET_KEY = "secretKey:123456";
    // token过期时间
    public static final long TOKEN_EXPIRE_TIME = 7200 * 1000;

    /**
     * 生成jwt
     */
    public String createJwt(String userId){
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
        //设置头信息
        HashMap<String, Object> header = new HashMap<>(2);
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        // 生成 token:头部+载荷+签名
        return JWT.create().withHeader(header)
                .withClaim(RequestKeyConstants.USER_ID,userId)
                .withExpiresAt(new Date(System.currentTimeMillis()+TOKEN_EXPIRE_TIME)).sign(algorithm);
    }

    /**
     * 解析jwt
     */
    public Map<String, Claim> parseJwt(String token) {
        Map<String, Claim> claims = null;
        try {
            Algorithm algorithm = Algorithm.HMAC256(key);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            claims = jwt.getClaims();
            return claims;
        } catch (Exception e) {
            return null;
        }
    }
}

Nota: Se recomienda configurar la clave de firma y el tiempo de vencimiento de esta herramienta en el archivo del centro de configuración de manera uniforme y luego pasar los parámetros a través del método.

Composición del token:

  • cabeza
{
    "typ": "JWT",
    "alg": "HS256"
}
  • Carga útil: el usuario almacena cierta información de usuario no confidencial (como identificación de usuario, nombre de usuario, permisos, roles, etc.)
  • Firma: Firme las dos partes anteriores con una clave secreta determinada

1.3 Interfaz de inicio de sesión

LoginController

Nota: Se omite parte del código para facilitar la demostración.

@PostMapping("/auth/login")
public Result login(HttpServletRequest request, @Valid @RequestBody LoginDTO loginDto) {

    String ip = IpAddressUtils.getIpAddr(request);
    // 获取用户信息、比对密码
    Result<UserDTO> result = loginFeignApi.login(loginDto,ip);
    if(ResultCode.SUCCESS.getCode()!=result.getCode()){
        log.error(result.getMsg());
        return result;
    }
    UserDTO user = result.getData();
    String token = JWTUtils.createJwt(user.getId() + "");
    data.put("token",token);
    return Result.success(data);
}

Entre ellos, loginFeignApi.login (loginDto, ip) es  la interfaz para llamar al  módulo de servicio de usuario a través de fingir : consultar la información del usuario de acuerdo con el nombre de usuario y realizar el cifrado MD5 y la verificación de sal en la contraseña, de la siguiente manera:

User user = userService.getUserByName(loginDTO.getUserName());
// 密码md5 校验
if (!user.getPassword().equals(MD5Utils.toMD5Password(user.getUserSalt(), loginDTO.getPassword()))){
    userService.userLog(user,ip,UserLogTypeEnum.LOGIN_FAIL,loginDTO.getSource());
    return Result.error(ResultCode.PASSWORD_ERROR);
}

2. servicio de puerta de enlace

De la misma manera, se construye un módulo de servicio de puerta de enlace a través de SpringBoot, y el centro de registro y configuración usa Nacos de manera uniforme y configura las estrategias de enrutamiento de otras interfaces de servicio como una entrada unificada a otras API de servicio.

La puerta de enlace es responsable de verificar el token y de colocar información relacionada con el usuario en el encabezado de la solicitud para que el sistema descendente pueda obtener fácilmente la información del usuario.

2.1 pom.xml  

Del mismo modo, debe presentar el kit de herramientas jwt

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>${jwt.version}</version>
</dependency>

2.2 propiedades.aplicaciones

server.port=1234
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

# gateway 路由策略
spring.cloud.gateway.routes[0].id=user
spring.cloud.gateway.routes[0].uri=lb://user-service
spring.cloud.gateway.routes[0].predicates[0]=Path=/userService/**
spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
spring.cloud.gateway.routes[1].id=order
spring.cloud.gateway.routes[1].uri=lb://order-service
spring.cloud.gateway.routes[1].predicates[0]=Path=/orderService/**
spring.cloud.gateway.routes[1].filters[0]=StripPrefix=1

### 。。。。。。。

2.3 Verificar token

Podemos implementar la interfaz de filtro GatewayFilter GlobalFilter . Cuando la solicitud del front-end pase por la puerta de enlace, se filtrará para verificar la validez del token. Aquí implementamos la interfaz GlobalFilter.

TokenFilter.java

/**
 * @description: token过滤器
 * @author: xianhao_gan
 * @date:
 **/
@Slf4j
@Component
public class TokenFilter implements GlobalFilter, Ordered {

    @Autowired
    private JwtUtils jwtUtils;

    /**
     * 校验token
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst(RequestKeyConstants.TOKEN);
        //检查token是否为空
        if (StringUtils.isEmpty(token)) {
            return denyAccess(exchange, ResultCode.TOKEN_NULL);
        }

        Map claimMap1 = jwtUtils.parseJwt(token);
        //token有误
        if (claimMap.containsKey("exception")) {
            log.error() (claimMap1.get("exception").toString());
            return denyAccess(exchange, ResultCode.TOKEN_INVALID);
        }

        //token无误,将用户信息设置进header中,传递到下游服务
        Map<String, Claim> claimMap = claimMap1;
        String userId = claimMap.get(RequestKeyConstants.USER_ID).asString();
        Consumer<HttpHeaders> headers = httpHeaders -> {
            httpHeaders.add(RequestKeyConstants.USER_ID, userId);
        };
        request.mutate().headers(headers).build();
        
        // todo 权限校验

        return chain.filter(exchange);
    }

    /**
     * 拦截并返回自定义的json字符串
     */
    private Mono<Void> denyAccess(ServerWebExchange exchange, ResultCode resultCode) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        //这里在返回头添加编码,否则中文会乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] bytes = JSON.toJSONBytes(Result.error(resultCode), SerializerFeature.WriteMapNullValue);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return -1;
    }

}

Nota :

  • Por supuesto, el código en el proyecto real será más complicado que estos. Para facilitar la demostración y explicar la idea principal, se omite parte del código. El diseño y desarrollo real se puede ampliar de acuerdo con esta idea.

  • Para los tokens generados después del inicio de sesión, el backend establece un período de validez fijo. Durante el período de validez, los usuarios llevan el token para acceder. Cuando el token expira, dejará de ser válido. El front-end saltará a la página de inicio de sesión para permitir que el usuario vuelva a iniciar sesión, pero la experiencia del usuario no es amigable.

  • Mejora: los usuarios activos deben obtener automáticamente un nuevo token después de que caduque el token sin saberlo, y llevar este nuevo token para acceder, mientras que los usuarios que han estado inactivos durante mucho tiempo deben iniciar sesión nuevamente después de que caduque el JWT. Más tarde, habrá tiempo para agregar la estrategia de actualización del tiempo de espera del token.

Referencia: https://www.cnblogs.com/cjsblog/p/12425912.html

● La optimización de rendimiento de Tomcat8 más sólida de la historia

¿Por qué Alibaba puede resistir 10 mil millones en 90 segundos? - La evolución de la arquitectura distribuida de alta concurrencia del lado del servidor

Plataforma de comercio electrónico B2B: función de pago electrónico ChinaPay UnionPay

Aprenda el candado distribuido de Zookeeper, deje que los entrevistadores lo miren con admiración

Solución de bloqueo distribuido de Redisson con microservicio de pico de comercio electrónico de SpringCloud

Vea más artículos buenos, ingrese a la cuenta oficial, por favor, excelente en el pasado

Una cuenta pública profunda y conmovedora 0.0

 

Supongo que te gusta

Origin blog.csdn.net/a1036645146/article/details/109546416
Recomendado
Clasificación