【若依SpringCloud】学习笔记一(ruoyiCloud验证码生成及校验)

ruoyiCloud架构图

一、验证码生成

1、前端代码实现

(1) ruoyi-ui/src/views/login.vue (html部分,点击图片触发获取验证码动作)
   <div class="login-code">
      <img :src="codeUrl" @click="getCode" class="login-code-img"/>
   </div>
(2) ruoyi-ui/src/views/login.vue (js部分导入api目录下的login.js文件中的getCodeImg方法)
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
(3) ruoyi-ui/src/views/login.vue文件中的getCode方法 (使用导入的getCodeImg方法发送请求,并根据响应信息拼接codeUrl)
  methods: {
    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;
        }
      });
    },
(4) ruoyi-ui/src/api/login.js文件中的getCodeImg方法
// 获取验证码
export function getCodeImg() {
  return request({
    url: '/code',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  })

2、后端代码实现

(1) com/ruoyi/gateway/config/RouterFunctionConfiguration.java (Spring WebFlux中定义路由的方法,用于创建路由规则。定义了一个GET请求路径为"/code"且接受的媒体类型为TEXT_PLAIN的请求断言,当接收到GET请求且路径为"/code"且接受的媒体类型为TEXT_PLAIN时,将请求交给validateCodeHandler处理)
@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);
    }
}
(2) com/ruoyi/gateway/handler/ValidateCodeHandler.java( 实现了接口HandlerFunction HandlerFunction 是 Spring WebFlux 框架的一个接口,用于处理 HTTP 请求并生成响应。HandlerFunction 接口只包含一个 handle () 方法, 该方法接受一个ServerRequest 对象作为输入,返回一个 Mono 类型的响应对象)
@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));
    }
}
(3) com/ruoyi/gateway/service/impl/ValidateCodeServiceImpl.java (实现了接口ValidateCodeService 后端生成验证码逻辑代码、保存在redis缓存中并以流的方式写到响应的中)
    @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、前端代码实现

(1) ruoyi-ui/src/views/login.vue (html部分。点击登录按钮,触发登录处理事件)
     <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin">
          <span v-if="!loading">登 录</span>
          <span v-else>登 录 中...</span>
     </el-button>
(2) ruoyi-ui/src/views/login.vue (js部分。登录处理事件调用 Vuex 中的 Login action,传入 this.loginForm 作为参数)
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store.dispatch("Login", this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaEnabled) {
              this.getCode();
            }
          });
        }
      });
    }
(3) ruoyi-ui/src/store/modules/user.js (js部分。登录处理事件 Login action,调用login.js中的login方法)
	// 登录
    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)
        })
      })
    },
(4) ruoyi-ui/src/api/login.js (请求后端url:/auth/login)
// 登录方法
export function login(username, password, code, uuid) {
  return request({
    url: '/auth/login',
    headers: {
      isToken: false
    },
    method: 'post',
    data: { username, password, code, uuid }
  })
}

2、Nacos配置(ruoyi-gateway-dev.yml。 匹配请求路径为/auth/**的请求,设置过滤器并指定通过过滤器后的转发地址)

      routes:
        # 认证中心
        # 指定该路由规则的ID为ruoyi-auth
        - id: ruoyi-auth
          #指定请求转发的目标服务为ruoyi-auth,采用负载均衡的方式进行转发。lb代表从服务注册发现组件(如Eureka、Consul、Nacos等)中获取服务列表
          uri: lb://ruoyi-auth
          #匹配请求路径为/auth/**的请求
          predicates:
            - Path=/auth/**
          filters:
            # 验证码处理
            # 缓存请求的过滤器
            - CacheRequestFilter
            # 验证码处理的过滤器
            - ValidateCodeFilter
            # 去掉请求路径中的前缀,这里设置为1表示去掉第一个路径元素
            - StripPrefix=1

3、后端代码实现

(1) CacheRequestFilter(缓存过滤器,基于Spring Web的缓存机制。过滤非GET/DELETE请求则调用ServerWebExchangeUtils.cacheRequestBodyAndRequest方法,该方法会缓存请求体并返回一个新的ServerHttpRequest。如果缓存后的ServerHttpRequest与原始请求相同,直接调用chain.filter(exchange)。如果缓存后的ServerHttpRequest与原始请求不同,通过构建一个新的ServerWebExchange对象,将新的ServerHttpRequest设置为请求,然后调用chain.filter方法处理这个新的ServerWebExchange对象。)
    public static class CacheRequestGatewayFilter implements GatewayFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // GET DELETE 不过滤
            HttpMethod method = exchange.getRequest().getMethod();
            if (method == null || method == HttpMethod.GET || method == HttpMethod.DELETE) {
                return chain.filter(exchange);
            }
            return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> {
                if (serverHttpRequest == exchange.getRequest()) {
                    return chain.filter(exchange);
                }
                return chain.filter(exchange.mutate().request(serverHttpRequest).build());
            });
        }
    }
(2) ValidateCodeFilter (验证码过滤器。获取登录请求上送的验证码UUID获取redis中存储的验证码码值CODE是否和登录请求上送的验证码码值CODE一致。)
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();

            // 非登录/注册请求或验证码关闭,不处理
            if (!StringUtils.containsAnyIgnoreCase(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);
        };
    }
  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值