【记录】Spring gateway 整合 security

919 阅读14分钟

1. 题记

考虑使用gateway项目结合security的方式去做个网关拦截服务,某度一搜一片,基本都是不可以用的,而且问题不少,为此我自己也是东拼西凑,目前勉强搞了一套可以临时用的,先记录一下,后续优化会持续更新。

2. 看此文章前需要具备的条件

  1. 了解security基本使用方式(不了解可以先去看# B站三更草堂,2小时就可以入门)
  2. 了解响应式/反应式编程,与常规HTTP编程的区别(B站找尚硅谷或者黑马都可以,2小时看完入门)
  3. 了解spring boot/cloud/gateway等组件基本使用(这个自己百度看看帖子即可)
  4. 所以此篇文章跨度会比较大

3. 版本参考

<properties>
    <java.version>11</java.version>
    <revision>1.0.0-SNAPSHOT</revision>
    <maven.compile.source>11</maven.compile.source>
    <maven.compile.target>11</maven.compile.target>
    <spring-cloud-version>2020.0.1</spring-cloud-version>
    <spring-alibaba-cloud-version>2021.1</spring-alibaba-cloud-version>
</properties>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.2</version>
</parent>

4. 代码讲解

4.1 引入依赖

<!-- 引入gateway -->
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- 鉴权security -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>

4.2 引入基于webflux版本的security的配置文件

package cn.com.dzod.pm.gateway.security.conf;

import cn.com.dzod.pm.auth.interfaces.auth.dto.SysUserDto;
import cn.com.dzod.pm.gateway.security.check.AuthenticationConverter;
import cn.com.dzod.pm.gateway.security.check.AuthenticationManager;
import cn.com.dzod.pm.gateway.security.check.AuthorizeConfigManager;
import cn.com.dzod.pm.gateway.security.check.MySqlReactiveUserDetailsServiceImpl;
import cn.com.dzod.pm.gateway.security.exception.AuthEntryPointException;
import cn.com.dzod.pm.gateway.security.handle.JsonServerAuthenticationFailureHandler;
import cn.com.dzod.pm.gateway.security.handle.JsonServerAuthenticationSuccessHandler;
import cn.com.dzod.pm.gateway.security.handle.JsonServerLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;

import java.util.LinkedList;


@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    @Autowired
    private AuthenticationConverter authenticationConverter;

    @Autowired
    private AuthorizeConfigManager authorizeConfigManager;

    @Autowired
    private AuthEntryPointException serverAuthenticationEntryPoint;

    @Autowired
    private JsonServerAuthenticationSuccessHandler jsonServerAuthenticationSuccessHandler;

    @Autowired
    private JsonServerAuthenticationFailureHandler jsonServerAuthenticationFailureHandler;

    @Autowired
    private JsonServerLogoutSuccessHandler jsonServerLogoutSuccessHandler;

    @Autowired
    private AuthenticationManager authenticationManager;

    private static final String[] AUTH_WHITELIST = new String[]{"/login", "/logout"};

//    @Autowired
//    private MySqlReactiveUserDetailsServiceImpl mySqlReactiveUserDetailsService;

//    @Autowired
//    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        SecurityWebFilterChain chain = http
                .formLogin()
                .loginPage("/login")
//                .securityContextRepository(new JwtSecurityContextRepository(mySqlReactiveUserDetailsService))
                // 登录成功handler
                .authenticationSuccessHandler(jsonServerAuthenticationSuccessHandler)
                // 登陆失败handler
                .authenticationFailureHandler(jsonServerAuthenticationFailureHandler)
                // 无访问权限handler
                .authenticationEntryPoint(serverAuthenticationEntryPoint)

                .and()
                .logout()
                // 登出成功handler
                .logoutSuccessHandler(jsonServerLogoutSuccessHandler)
                .and()
                .csrf().disable()
                .cors().disable()
                .httpBasic().disable()
                .authorizeExchange()
                // 白名单放行
                .pathMatchers(AUTH_WHITELIST).permitAll()
                // 访问权限控制
                .anyExchange().access(authorizeConfigManager)
                .and().build();

        // 设置自定义登录参数转换器
        chain.getWebFilters()
                .filter(webFilter -> webFilter instanceof AuthenticationWebFilter)
                .subscribe(webFilter -> {
                    AuthenticationWebFilter filter = (AuthenticationWebFilter) webFilter;
                    filter.setServerAuthenticationConverter(authenticationConverter);
                });
        return chain;
    }

    /**
     * 注册用户信息验证管理器,可按需求添加多个按顺序执行
     * @return
     */
    @Bean
    ReactiveAuthenticationManager reactiveAuthenticationManager() {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(authenticationManager);
        return new DelegatingReactiveAuthenticationManager(managers);
    }


    /**
     * BCrypt密码编码
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SysUserDto sysUserEntity(){
        return new SysUserDto();
    }

    @Bean
    MySqlReactiveUserDetailsServiceImpl reactiveUserDetailsService(){ //6
        return new MySqlReactiveUserDetailsServiceImpl();
    }

}

4.3 引入封装security用户的类

package cn.com.dzod.pm.gateway.security.check;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Collections;


@Component("authenticationConverter")
public class AuthenticationConverter extends ServerFormLoginAuthenticationConverter {

    private String usernameParameter = "username";

    private String passwordParameter = "password";

//    private MySqlReactiveUserDetailsServiceImpl userDetailsService;

    @Override
    public Mono<Authentication> convert(ServerWebExchange exchange) {
//        HttpHeaders headers = exchange.getRequest().getHeaders();

        return exchange.getFormData()
                .map(data -> {
//                    String username = data.getFirst(this.usernameParameter);
//                    String password = data.getFirst(this.passwordParameter);
//                    UserDetails userDetails = userDetailsService.findByUsername(data.getFirst(this.usernameParameter));
                    return new UsernamePasswordAuthenticationToken(data.getFirst(this.usernameParameter),data.getFirst(this.passwordParameter), Collections.EMPTY_LIST);

//                    return null;
                });
    }

}

4.4 紧接着引入验证用户的类

package cn.com.dzod.pm.gateway.security.check;

import cn.com.dzod.pm.gateway.security.dto.AuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;


@Component
public class AuthenticationManager extends AbstractUserDetailsReactiveAuthenticationManager {

    private Scheduler scheduler = Schedulers.boundedElastic();

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @Autowired
    private MySqlReactiveUserDetailsServiceImpl mySqlReactiveUserDetailsService;

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {

//        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
        final String username = authentication.getName();
        final String presentedPassword = (String) authentication.getCredentials();
        return retrieveUser(username)
                .publishOn(scheduler)
                .filter(u -> passwordEncoder.matches(presentedPassword, u.getPassword()))
                .switchIfEmpty(Mono.defer(() -> Mono.error(new BadCredentialsException("Invalid Credentials"))))
                .flatMap(u -> {
                    boolean upgradeEncoding = mySqlReactiveUserDetailsService != null
                            && passwordEncoder.upgradeEncoding(u.getPassword());
                    if (upgradeEncoding) {
                        String newPassword = passwordEncoder.encode(presentedPassword);
                        return mySqlReactiveUserDetailsService.updatePassword(u, newPassword);
                    }
                    return Mono.just(u);
                })
                .flatMap(userDetails -> {
                    // 省略业务代码
                    return Mono.just(userDetails);
                })
                .map(u -> new AuthenticationToken(u, u.getPassword(), u.getAuthorities()));
    }

    @Override
    protected Mono<UserDetails> retrieveUser(String username) {
        return mySqlReactiveUserDetailsService.findByUsername(username);
    }
}

4.5 用户验证过了以后就需要鉴权

package cn.com.dzod.pm.gateway.security.check;


import cn.com.dzod.pm.gateway.security.dto.MyUserDetailsImpl;
import cn.com.dzod.pm.gateway.util.JwtUtil;
import cn.com.dzod.pm.gateway.util.RedisCache;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class AuthorizeConfigManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisCache redisCache;

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authentication,
                                             AuthorizationContext authorizationContext) {
        return authentication.map(auth -> {
            ServerWebExchange exchange = authorizationContext.getExchange();
            ServerHttpRequest request = exchange.getRequest();
            // todo 增加一份token认证的拦截
            String token = request.getHeaders().getFirst("token");
            if (token == null){
                return new AuthorizationDecision(false);
            }
            String userid;
            try {
                Claims claims = JwtUtil.parseJWT(token);
                userid = claims.getSubject();
            }catch (Exception e){
                log.error("token 非法");
                return new AuthorizationDecision(false);
            }
            String redisKey = "login:" + userid;
            MyUserDetailsImpl loginUser = redisCache.getCacheObject(redisKey);

//            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();


            for (String url : loginUser.getSysUserDto().getUrls()) {

                String path = request.getURI().getPath();
                if (antPathMatcher.match(url, path)) {
                    log.info(String.format("用户请求API校验通过,GrantedAuthority:{%s}  Path:{%s} ", url, path));
                    return new AuthorizationDecision(true);
                }
            }
            return new AuthorizationDecision(false);
        }).defaultIfEmpty(new AuthorizationDecision(false));
    }

    @Override
    public Mono<Void> verify(Mono<Authentication> authentication, AuthorizationContext object) {
        return check(authentication, object)
                .filter(d -> d.isGranted())
                .switchIfEmpty(Mono.defer(() -> {
//                    AjaxResult<String> ajaxResult = AjaxResult.restResult("当前用户没有访问权限! ", ApiErrorCode.FAILED);
                    String body = JSONObject.toJSONString("当前用户没有访问权限");
                    return Mono.error(new AccessDeniedException(body));
                }))
                .flatMap(d -> Mono.empty());
    }
}

到此为止一套最简单的鉴权流程就已经走完了,这篇文章更多是为了开箱即用,后续就更多是对代码的复制粘贴了

4.6 补全所有pom

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>3.11</version>
</dependency>


<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>

<!-- nacos服务发现 -->
<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

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


<dependency>
   <groupId>org.apache.dubbo</groupId>
   <artifactId>dubbo-registry-nacos</artifactId>
   <version>2.7.15</version>
</dependency>

<dependency>
   <groupId>org.apache.dubbo</groupId>
   <artifactId>dubbo-spring-boot-starter</artifactId>
   <version>2.7.15</version>
</dependency>

<dependency>
   <groupId>io.jsonwebtoken</groupId>
   <artifactId>jjwt</artifactId>
   <version>0.9.0</version>
</dependency>

<!--fastjson依赖-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.33</version>
</dependency>
<dependency>
   <groupId>com.alibaba.spring</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>1.0.11</version>
</dependency>

<dependency>
   <groupId>cn.com.dzod.pm.api</groupId>
   <artifactId>pm-api</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <scope>compile</scope>
</dependency>

<dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.3.0</version>
</dependency>
<dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-impl</artifactId>
   <version>2.3.0</version>
</dependency>
<dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-core</artifactId>
   <version>2.3.0</version>
</dependency>
<dependency>
   <groupId>javax.activation</groupId>
   <artifactId>activation</artifactId>
   <version>1.1.1</version>
</dependency>

4.7 准备工具类

4.7.1 JWT

package cn.com.dzod.pm.gateway.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
 * JWT工具类
 */
public class JwtUtil {

    //有效期为
    public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
    //设置秘钥明文
    public static final String JWT_KEY = "sangeng";

    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @return
     */
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    /**
     * 生成jtw
     * @param subject token中要存放的数据(json格式)
     * @param ttlMillis token超时时间
     * @return
     */
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }

    /**
     * 创建token
     * @param id
     * @param subject
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    public static void main(String[] args) throws Exception {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
        Claims claims = parseJWT(token);
        System.out.println(claims);
    }

    /**
     * 生成加密后的秘钥 secretKey
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析
     *
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }


}

4.7.2 Redis工具类

package cn.com.dzod.pm.gateway.util;

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;

@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 缓存键值
     * @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 long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存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
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(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);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

4.7.3 redis 配置文件

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;

@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

        // 使用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.7.4 主键生成器


import com.github.wujun234.uid.impl.CachedUidGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class IdGenerator {

    @Autowired
    private CachedUidGenerator cachedUidGenerator;

    /**
     * 获取uid
     *
     * @return
     */
    public long nextId() {
        return cachedUidGenerator.getUID();
    }

    /**
     * 格式化传入的uid,方便查看其实际含义
     *
     * @param uid
     * @return
     */
    public String parse(long uid) {
        return cachedUidGenerator.parseUID(uid);
    }
}

4.8 用户信息封装

package cn.com.dzod.pm.gateway.security.dto;

import cn.com.dzod.pm.auth.interfaces.auth.dto.SysUserDto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author 许权
 * @Date 2021/12/6
 */
@NoArgsConstructor
@Data
//@AllArgsConstructor
public class MyUserDetailsImpl implements UserDetails {

    private SysUserDto sysUserDto;

    private List<String> permissions;

    public MyUserDetailsImpl(SysUserDto sysUserDto, List<String> permissions){
        this.sysUserDto = sysUserDto;
        this.permissions = permissions;
    }
//    private boolean enabled;
//存储SpringSecurity所需要的权限信息的集合
    @JSONField(serialize = false)
    private List<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
        authorities = permissions.stream().
                map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

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

    @Override
    public String getUsername() {
        return sysUserDto.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;
    }

//    @Override
//    public boolean isEnabled() {
//        return enabled;
//    }
}
package cn.com.dzod.pm.gateway.security.check;


import cn.com.dzod.pm.auth.interfaces.auth.dto.SysUserDto;
import cn.com.dzod.pm.auth.interfaces.auth.service.LoginService;
import cn.com.dzod.pm.gateway.security.dto.MyUserDetailsImpl;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import reactor.core.publisher.Mono;

import java.util.List;


@Slf4j
//@Component("mySqlReactiveUserDetailsService")
@Data
public class MySqlReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {


//    @Resource
//    private SysUserMapper sysUserMapper;

    @DubboReference(check = false)
    private LoginService loginService;


    /**
     * 获取指定用户信息与登录信息进行验证
     * @param username
     * @return
     */
    @Override
    public Mono<UserDetails> findByUsername(String username) {

        SysUserDto sysUserDto = loginService.getUserDto(username);

        if (null == sysUserDto) {
            throw new UsernameNotFoundException("用户不存在");
        }

        List<String> permissionList = loginService.getPermissionList(username);

        log.info("登录用户权限列表:{}", JSON.toJSONString(permissionList));
        return Mono.just(new MyUserDetailsImpl(sysUserDto,permissionList));
    }



    @Override
    public Mono<UserDetails> updatePassword(UserDetails userDetails, String s) {
        System.out.println("进入修改密码");
        return null;
    }
}

4.9 还是对用户信息封装的类,后面我肯定要替换掉的

package cn.com.dzod.pm.gateway.security.dto;

import lombok.Getter;
import lombok.Setter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;


@SuppressWarnings("serial")
@Getter
@Setter
public class AuthenticationToken extends UsernamePasswordAuthenticationToken {

    private String tenant;

    private String host;

    public AuthenticationToken(Object principal, Object credentials, String tenant, String host) {
        super(principal, credentials);
        this.tenant = tenant;
        this.host = host;

    }

    public AuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public AuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

4.10 未认证处理

package cn.com.dzod.pm.gateway.security.exception;

import com.alibaba.fastjson.JSONObject;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;


@Component
public class AuthEntryPointException implements ServerAuthenticationEntryPoint {

    @Override
    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
        return Mono.defer(() -> Mono.just(exchange.getResponse())).flatMap(response -> {
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            DataBufferFactory dataBufferFactory = response.bufferFactory();
            String result = JSONObject.toJSONString("ResultVoUtil.failed(UserStatusCodeEnum.USER_UNAUTHORIZED)");
            DataBuffer buffer = dataBufferFactory.wrap(result.getBytes(
                    Charset.defaultCharset()));
            return response.writeWith(Mono.just(buffer));
        });
    }
}

4.11 登录成功、失败、登出成功处理类就一起粘过来了

package cn.com.dzod.pm.gateway.security.handle;

import cn.com.dzod.pm.gateway.security.dto.MyUserDetailsImpl;
import cn.com.dzod.pm.gateway.util.JwtUtil;
import cn.com.dzod.pm.gateway.util.RedisCache;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

@Component("jsonServerAuthenticationSuccessHandler")
public class JsonServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {

    @Autowired
    private RedisCache redisCache;

    @Override
    public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
        return Mono.defer(() -> Mono.just(webFilterExchange.getExchange().getResponse()).flatMap(response -> {
            DataBufferFactory dataBufferFactory = response.bufferFactory();

            Object s = authentication.getPrincipal();
            MyUserDetailsImpl userDetails = (MyUserDetailsImpl) s;

            Map<String, Object> tokenMap = new HashMap<>(2);
            tokenMap.put("token", "testToken");

            String userId = userDetails.getSysUserDto().getId().toString();
            String jwt = JwtUtil.createJWT(userId);
            redisCache.setCacheObject("login:"+userId,userDetails);

            tokenMap.put("token",jwt);
            DataBuffer dataBuffer = dataBufferFactory.wrap(JSON.toJSONString(tokenMap).getBytes());
            return response.writeWith(Mono.just(dataBuffer));
        }));
    }

}
package cn.com.dzod.pm.gateway.security.handle;


import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

@Component
public class JsonServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {

    @Override
    public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
        return Mono.defer(() -> Mono.just(webFilterExchange.getExchange()
                .getResponse()).flatMap(response -> {
            DataBufferFactory dataBufferFactory = response.bufferFactory();
//            ResultVO<Map<String, Object>> resultVO = ResultVoUtil.error();
            // 账号不存在
            if (exception instanceof UsernameNotFoundException) {
//                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_NOT_EXIST);
                System.out.println(1);
                // 用户名或密码错误
            } else if (exception instanceof BadCredentialsException) {
//                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.LOGIN_PASSWORD_ERROR);
                System.out.println(1);
                // 账号已过期
            } else if (exception instanceof AccountExpiredException) {
//                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_EXPIRED);
                System.out.println(1);
                // 账号已被锁定
            } else if (exception instanceof LockedException) {
//                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_LOCKED);
                System.out.println(1);
                // 用户凭证已失效
            } else if (exception instanceof CredentialsExpiredException) {
//                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_CREDENTIAL_EXPIRED);
                System.out.println(1);
                // 账号已被禁用
            } else if (exception instanceof DisabledException) {
//                resultVO = ResultVoUtil.failed(UserStatusCodeEnum.ACCOUNT_DISABLE);
                System.out.println(1);
            }
            DataBuffer dataBuffer = dataBufferFactory.wrap("1234".getBytes());
            return response.writeWith(Mono.just(dataBuffer));
        }));
    }
}
package cn.com.dzod.pm.gateway.security.handle;

import com.alibaba.fastjson.JSONObject;
import io.netty.util.CharsetUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

/**
 * @Author: 许权
 * @Date: 2020/7/10 0010 15:05
 */
@Component
public class JsonServerLogoutSuccessHandler implements ServerLogoutSuccessHandler {
    @Override
    public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
        ServerHttpResponse response = exchange.getExchange().getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json; charset=UTF-8");
        String result = JSONObject.toJSONString(("注销成功"));
        DataBuffer buffer = response.bufferFactory().wrap(result.getBytes(CharsetUtil.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }
}

总结

到此为止全部代码都拿过来了,除了dubbo相关的模块,那个大家自己替换就好,我做的方法是在gateway当中去鉴权,用户信息通过远程调用去用户服务查询。留一份数据库仅供参考。

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80027 (8.0.27)
 Source Host           : localhost:3306
 Source Schema         : auth

 Target Server Type    : MySQL
 Target Server Version : 80027 (8.0.27)
 File Encoding         : 65001

 Date: 20/02/2023 16:02:14
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限编码(标识)',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限URL',
  `is_delete` int NULL DEFAULT NULL COMMENT '0 未删除  1 已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'home', '首页', '/home/**', NULL);
INSERT INTO `sys_permission` VALUES (2, 'user:add', '添加用户', '/user/add', NULL);
INSERT INTO `sys_permission` VALUES (3, 'user:delete', '删除用户', '/auth/szzb/sys/user/**', NULL);
INSERT INTO `sys_permission` VALUES (4, 'hello:sayHello', '打招呼', '/hello/sayHello', NULL);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色编码',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
  `is_delete` int NULL DEFAULT NULL COMMENT '0 未删除  1 已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'employee', '员工', NULL);
INSERT INTO `sys_role` VALUES (2, 'engineer', '工程师', NULL);
INSERT INTO `sys_role` VALUES (3, 'leader', '组长', NULL);

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_id` bigint NOT NULL COMMENT '角色ID',
  `permission_id` bigint NOT NULL COMMENT '权限ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2, 1);
INSERT INTO `sys_role_permission` VALUES (3, 2, 2);
INSERT INTO `sys_role_permission` VALUES (4, 3, 1);
INSERT INTO `sys_role_permission` VALUES (5, 3, 2);
INSERT INTO `sys_role_permission` VALUES (6, 3, 3);
INSERT INTO `sys_role_permission` VALUES (7, 3, 4);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
  `is_delete` int NULL DEFAULT NULL COMMENT '0 未删除  1 已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 366954640605185 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'zhangsan', '$2a$10$e4wFsFHQCNjPe5tTJMPkRuKGwmMGC45pfjMupY9nwbTuoKQ0bKc/u', 0);
INSERT INTO `sys_user` VALUES (366953206145025, '1', '1', 0);
INSERT INTO `sys_user` VALUES (366953206145026, '1', '12345555', 1);
INSERT INTO `sys_user` VALUES (366954640605184, '1', '1', 0);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `role_id` bigint NOT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 1, 2);
INSERT INTO `sys_user_role` VALUES (3, 1, 3);

-- ----------------------------
-- Table structure for WORKER_NODE
-- ----------------------------
DROP TABLE IF EXISTS `WORKER_NODE`;
CREATE TABLE `WORKER_NODE`  (
  `ID` bigint NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
  `HOST_NAME` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'host name',
  `PORT` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'port',
  `TYPE` int NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
  `LAUNCH_DATE` date NOT NULL COMMENT 'launch date',
  `MODIFIED` timestamp NOT NULL COMMENT 'modified time',
  `CREATED` timestamp NOT NULL COMMENT 'created time',
  PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'DB WorkerID Assigner for UID Generator' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of WORKER_NODE
-- ----------------------------
INSERT INTO `WORKER_NODE` VALUES (1, '192.168.16.6', '1676355147858-17014', 2, '2023-02-14', '2023-02-14 06:12:27', '2023-02-14 06:12:27');
INSERT INTO `WORKER_NODE` VALUES (2, '192.168.16.6', '1676355223677-69932', 2, '2023-02-14', '2023-02-14 06:13:43', '2023-02-14 06:13:43');
INSERT INTO `WORKER_NODE` VALUES (3, '192.168.16.6', '1676355297710-38644', 2, '2023-02-14', '2023-02-14 06:14:57', '2023-02-14 06:14:57');
INSERT INTO `WORKER_NODE` VALUES (4, '192.168.16.6', '1676355532460-82261', 2, '2023-02-14', '2023-02-14 06:18:52', '2023-02-14 06:18:52');
INSERT INTO `WORKER_NODE` VALUES (5, '192.168.16.6', '1676355597540-90161', 2, '2023-02-14', '2023-02-14 06:19:57', '2023-02-14 06:19:57');
INSERT INTO `WORKER_NODE` VALUES (6, '192.168.16.6', '1676355661820-29835', 2, '2023-02-14', '2023-02-14 06:21:01', '2023-02-14 06:21:01');
INSERT INTO `WORKER_NODE` VALUES (7, '192.168.16.6', '1676355912322-89571', 2, '2023-02-14', '2023-02-14 06:25:12', '2023-02-14 06:25:12');
INSERT INTO `WORKER_NODE` VALUES (8, '192.168.16.6', '1676356003676-41259', 2, '2023-02-14', '2023-02-14 06:26:43', '2023-02-14 06:26:43');
INSERT INTO `WORKER_NODE` VALUES (9, '192.168.16.6', '1676356075873-70924', 2, '2023-02-14', '2023-02-14 06:27:55', '2023-02-14 06:27:55');
INSERT INTO `WORKER_NODE` VALUES (10, '192.168.16.6', '1676356223541-13622', 2, '2023-02-14', '2023-02-14 06:30:23', '2023-02-14 06:30:23');
INSERT INTO `WORKER_NODE` VALUES (11, '192.168.16.6', '1676618518856-15854', 2, '2023-02-17', '2023-02-17 07:21:58', '2023-02-17 07:21:58');
INSERT INTO `WORKER_NODE` VALUES (12, '192.168.16.6', '1676618589912-84367', 2, '2023-02-17', '2023-02-17 07:23:09', '2023-02-17 07:23:09');
INSERT INTO `WORKER_NODE` VALUES (13, '192.168.16.6', '1676618655760-76203', 2, '2023-02-17', '2023-02-17 07:24:15', '2023-02-17 07:24:15');
INSERT INTO `WORKER_NODE` VALUES (14, '192.168.16.6', '1676622061020-67338', 2, '2023-02-17', '2023-02-17 08:21:01', '2023-02-17 08:21:01');
INSERT INTO `WORKER_NODE` VALUES (15, '192.168.1.42', '1676813354530-47481', 2, '2023-02-19', '2023-02-19 13:29:14', '2023-02-19 13:29:14');

SET FOREIGN_KEY_CHECKS = 1;