若依微服务中验证码校验逻辑

        若依微服务的验证码流程简单来说:

        前端发起验证码请求 --> 后端响应返回验证码 -->前端渲染 --> 登录 --> 后端校验验证码

        后端生成验证码在gateway模块,验证码校验在auth模块(配置了filter过滤器,filter在gateway模块下)

目录

1、前端发起请求,后端返回验证码

1.1前端发起

1.2后端响应code请求

1.3后端生成验证码

1.4前端登录 

1.5后端验证码校验


1、前端发起请求,后端返回验证码

1.1前端发起

         若依微服务前端发起程序就是getCode()函数,在created()钩子函数触发,页面初始创建的时候,就发起get请求,地址是/code,代码如下,很简单:

    getCode() {
      getCodeImg().then(res => {
        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    },

1.2后端响应code请求

        假设我们此时在后端全局搜索@GetMapping("/code")或者搜索@GetMapping("code"),会发现搜不到。咦?那是怎么响应的?我们全局搜索"/code"会在 src/main/java/com/ruoyi/gateway/config/RouterFunctionConfiguration.java,找到一个,代码如下:

/**
 * 路由配置信息
 * 
 * @author ruoyi
 */
@Configuration
public class RouterFunctionConfiguration
{
    @Autowired
    private ValidateCodeHandler validateCodeHandler;

    @SuppressWarnings("rawtypes")
    @Bean
    public RouterFunction routerFunction()
    {
        return RouterFunctions.route(
                RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
                validateCodeHandler);
    }
}

       解释:这是后端用了spring webflux,而不用传统的spring mvc。因为webflux是异步非阻塞,可以提高性能和效率,而且gatewya作为网关,更要关注性能效率,我们可以看到若依微服务gateway层面大部分都是响应式编程。本篇就不过多涉及响应式webflux,上面代码还涉及了设计模式。

       通过RouterFunctions.route匹配到了"/code"路径,并且是get方法;匹配后需要执行一个HandlerFunction<ServerResponse> 方法,此时若依是实现了ValidateCodeHandler 类。代码如下:

/**
 * 验证码获取
 *
 * @author ruoyi
 */
@Component
public class ValidateCodeHandler implements HandlerFunction<ServerResponse>
{
    @Autowired
    private ValidateCodeService validateCodeService;

    @Override
    public Mono<ServerResponse> handle(ServerRequest serverRequest)
    {
        AjaxResult ajax;
        try
        {
            ajax = validateCodeService.createCaptcha();
        }
        catch (CaptchaException | IOException e)
        {
            return Mono.error(e);
        }
        return ServerResponse.status(HttpStatus.OK).body(BodyInserters.fromValue(ajax));
    }
}

上述代码,调用了handle方法。通过validateCodeService进一步获取验证码详情,上述代码只是将返回的验证码数据封装,返回。validateCodeService.createCaptcha()代码如下:

 /**
     * 生成验证码
     */
    @Override
    public AjaxResult createCaptcha() throws IOException, CaptchaException
    {
        AjaxResult ajax = AjaxResult.success();
        boolean captchaEnabled = captchaProperties.getEnabled();
        ajax.put("captchaEnabled", captchaEnabled);
        if (!captchaEnabled)
        {
            return ajax;
        }

        // 保存验证码信息
        String uuid = IdUtils.simpleUUID();
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;

        String capStr = null, code = null;
        BufferedImage image = null;

        String captchaType = captchaProperties.getType();
        // 生成验证码
        if ("math".equals(captchaType))
        {
            String capText = captchaProducerMath.createText();
            capStr = capText.substring(0, capText.lastIndexOf("@"));
            code = capText.substring(capText.lastIndexOf("@") + 1);
            image = captchaProducerMath.createImage(capStr);
        }
        else if ("char".equals(captchaType))
        {
            capStr = code = captchaProducer.createText();
            image = captchaProducer.createImage(capStr);
        }

        redisService.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
        // 转换流信息写出
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try
        {
            ImageIO.write(image, "jpg", os);
        }
        catch (IOException e)
        {
            return AjaxResult.error(e.getMessage());
        }

        ajax.put("uuid", uuid);
        ajax.put("img", Base64.encode(os.toByteArray()));
        return ajax;
    }

1.3后端生成验证码

主要代码就是createCaptcha()中,过程:

生成uuid(后面验证码校验用) -- >  生成验证码 --> 存一份redis --> 转为输出流 --> 转为Base64

返回的数据如下图所示:

       

1.4前端登录 

        前端登录除了携带username登录名,password密码外,还携带了uuid和code。

        uuid:就是生成验证码的唯一标识

        code:就是验证码的值

    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid).then(res => {
          let data = res.data
          setToken(data.access_token)
          commit('SET_TOKEN', data.access_token)
          setExpiresIn(data.expires_in)
          commit('SET_EXPIRES_IN', data.expires_in)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

1.5后端验证码校验

        后端验证码校验是在前端请求login接口时,通过过滤器ValidateCodeFilter来校验的。

        因为login请求的完整路径是:/auth/login,位于auth模块,我们不妨去nacos下对应gateway.yml看看auth配置:

        # 认证中心
        - id: ruoyi-auth
          uri: lb://ruoyi-auth
          predicates:
            - Path=/auth/**
          filters:
            # 验证码处理
            - CacheRequestFilter
            - ValidateCodeFilter
            - StripPrefix=1

可以看到若依已经贴心的写了注释:验证码处理:ValidateCodeFilter

现在去看ValidateCodeFilter代码:

/**
 * 验证码过滤器
 *
 * @author ruoyi
 */
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object>
{
    private final static String[] VALIDATE_URL = new String[] { "/auth/login", "/auth/register" };

    @Autowired
    private ValidateCodeService validateCodeService;

    @Autowired
    private CaptchaProperties captchaProperties;

    private static final String CODE = "code";

    private static final String UUID = "uuid";

    @Override
    public GatewayFilter apply(Object config)
    {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // 非登录/注册请求或验证码关闭,不处理
            if (!StringUtils.equalsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled())
            {
                return chain.filter(exchange);
            }

            try
            {
                String rspStr = resolveBodyFromRequest(request);
                JSONObject obj = JSON.parseObject(rspStr);
                validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
            }
            catch (Exception e)
            {
                return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
            }
            return chain.filter(exchange);
        };
    }

    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
    {
        // 获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        return bodyRef.get();
    }
}
ValidateCodeFilter继承了AbstractGatewayFilterFactory类,需要实现apply()方法,并返回一个GatewayFilter 对象。若依微服务中涉及了两个过滤器AbstractGatewayFilterFactory 和 GlobalFilter。一个是局部,一个是全局,这边也不过多涉及。

从前端request中获取到code和uuid,交给validateCodeService.checkCaptcha()方法校验。代码如下:

    /**
     * 校验验证码
     */
    @Override
    public void checkCaptcha(String code, String uuid) throws CaptchaException
    {
        if (StringUtils.isEmpty(code))
        {
            throw new CaptchaException("验证码不能为空");
        }
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
        String captcha = redisService.getCacheObject(verifyKey);
        if (captcha == null)
        {
            throw new CaptchaException("验证码已失效");
        }
        redisService.deleteObject(verifyKey);
        if (!code.equalsIgnoreCase(captcha))
        {
            throw new CaptchaException("验证码错误");
        }
    }
}

也就是从redis那数据,key是uuid,value是code,判断时间是否过期以及是否正确。

<think>好的,用户想了解Ruoyi微服务架构的实现、源码和配置。首先,我需要回忆一下关于Ruoyi框架的知识。Ruoyi是一个开源的企业级快速开发平台,微服务版本是基于Spring Cloud的。用户提到了引用中的组件视图和配置文件,比如ruoyi-cloud,还有各种yml文件。 首先,我应该介绍Ruoyi微服务架构的整体结构,可能包括核心组件如注册中心、配置中心、网关等。然后需要分模块说明,比如系统模块、认证模块、任务调度等,每个模块对应的子项目和使用的技术,比如Nacos、Spring Cloud Gateway、Sentinel等。 接下来是源码结构部分,用户可能需要知道各个子模块的作用,比如ruoyi-auth负责认证,ruoyi-system是系统核心。需要列出主要的子项目,并简要说明每个的功能。 然后是配置说明,用户提到了ruoyi-system-dev.yml等配置文件,应该解释这些配置文件的作用,以及如何配置数据库、Redis、Nacos等。同时,后台运行的命令也需要提及,比如nohup命令启动各个服务。 部署步骤也是用户关心的,需要给出从克隆代码到启动的步骤,确保用户能按照流程操作。可能还需要提到Maven构建和端口配置。 最后,相关问题部分要覆盖架构设计、模块功能、配置管理、扩展方法以及与其他框架的比较,这样用户如果有后续问题可以继续深入。 需要注意引用中的内容,比如引用[1]提到的组件视图和重要特征,引用[2]中的配置文件和后台运行命令,要在回答中适当引用这些内容,比如在架构设计和配置说明部分。同时要使用正确的LaTeX格式,比如变量用$符号包裹,确保格式正确。还要用中文回答,结构清晰,分点说明。</think>### Ruoyi 微服务架构解析 #### 一、架构设计 Ruoyi-cloud采用标准Spring Cloud微服务架构,包含以下核心组件: 1. **注册中心**:基于Nacos实现服务注册与发现,服务启动时向Nacos注册实例信息[^1] 2. **配置中心**:通过Nacos统一管理所有微服务的配置,支持动态刷新 3. **API网关**:使用Spring Cloud Gateway实现路由转发和权限过滤 4. **服务保护**:集成Sentinel实现流量控制和服务熔断 5. **监控系统**:通过Spring Boot Admin实现服务监控 典型架构分层: $$ \begin{cases} \text{网关层} & \text{(Gateway)} \\ \text{业务层} & \text{(System/Gen/Job)} \\ \text{支撑层} & \text{(Auth/Common)} \\ \text{基础设施} & \text{(Nacos/Redis/MySQL)} \end{cases} $$ #### 二、模块划分 | 模块名称 | 功能说明 | 核心技术 | |----------------|------------------------------|------------------------| | ruoyi-gateway | API网关 | Spring Cloud Gateway | | ruoyi-auth | 认证中心 | OAuth2.0 | | ruoyi-system | 系统管理核心业务 | MyBatis-Plus | | ruoyi-gen | 代码生成器 | Velocity模板引擎 | | ruoyi-job | 分布式任务调度 | XXL-JOB | | ruoyi-monitor | 系统监控 | Spring Boot Admin | #### 三、源码结构 主要子项目结构: ``` ruoyi-cloud ├── ruoyi-common # 公共模块 ├── ruoyi-gateway # 网关服务 ├── ruoyi-auth # 认证服务 ├── ruoyi-system # 系统模块 ├── ruoyi-gen # 代码生成 ├── ruoyi-job # 定时任务 └── ruoyi-visual # 可视化模块 ``` #### 四、配置说明 1. **环境配置**(以ruoyi-system-dev.yml为例): ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/ry-cloud?useSSL=false username: root password: 123456 redis: host: localhost port: 6379 ``` 2. **Nacos配置**: ```bash nacos: discovery: server-addr: 127.0.0.1:8848 config: file-extension: yml ``` 3. **服务启动命令**: ```bash nohup java -jar ruoyi-gateway.jar > gateway.log & nohup java -jar ruoyi-auth.jar > auth.log & ``` #### 五、部署流程 1. 克隆代码库 2. 初始化数据库(执行SQL脚本) 3. 启动Nacos服务 4. 按顺序启动核心服务: ``` Gateway → Auth → System → 其他模块 ``` 5. 访问管理后台:`http://localhost:8080`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值