首页 > 其他分享 >Ruoyi | 集成aj-captcha实现滑块验证码

Ruoyi | 集成aj-captcha实现滑块验证码

时间:2023-03-18 15:33:06浏览次数:92  
标签:username 滑块 aj Ruoyi ruoyi captcha import password com

由于最近在集成这玩意儿的时候实在是报错报麻了,所以写一下笔记记录一下,如果有人跟我一样,集成这个东西的时候各种报错,可以往下翻一翻找一下有没有你遇到的情况。

首先准备东西:插件集成 | Ruoyi

这里边的集成 aj-captcha 实现滑块验证码部分里第五步有一个百度网盘链接,先下载以后有用。

aj-captcha源码下载:AJ-Captcha: 行为验证码(滑动拼图、点选文字),前后端(java)交互,包含vue/h5/Android/IOS/flutter/uni-app/react/php/go/微信小程序的源码和实现

步骤参考:05-若依使用AJ-captcha验证_刀不封的博客-CSDN博客

但是我觉得这文章还缺点东西,所以我写这篇再给他封装一层。

一、后端整合步骤

1.添加依赖

找到 ruoyi-framework\pom.xm l文件,在里边添加上依赖

  <!-- anji滑块验证码 -->
  <dependency>
      <groupId>com.anji-plus</groupId>
      <artifactId>spring-boot-starter-captcha</artifactId>
      <version>1.3.0</version>
  </dependency>

原来那个验证码的依赖kaptcha可以删掉不要了

添加好依赖之后记得更新一下maven

2.修改application.yml,加入aj-captcha配置

# 滑块验证码
aj:
   captcha:
      # 缓存类型
      cache-type: redis
      # blockPuzzle 滑块 clickWord 文字点选  default默认两者都实例化
      type: blockPuzzle
      # 右下角显示字
      water-mark: ruoyi.vip
      # 校验滑动拼图允许误差偏移量(默认5像素)
      slip-offset: 5
      # aes加密坐标开启或者禁用(true|false)
      aes-status: true
      # 滑动干扰项(0/1/2)
      interference-options: 2

这上边都是最基本的设置,想要其他配置效果可以参考AJ-Captcha的文档:

AJ-Captcha: 行为验证码(滑动拼图、点选文字),前后端(java)交互,包含vue/h5/Android/IOS/flutter/uni-app/react/php/go/微信小程序的源码和实现

3.添加实现类指向设置

找到ruoyi-admin\src\main\resources\META-INF\services下创建com.anji.captcha.service.CaptchaCacheService文件(不需要后缀名)同时设置文件内容为:

com.ruoyi.framework.web.service.CaptchaRedisService

这里的地址要跟你下一步写CaptchaRedisService的路径一致,不然扫不到包。

4.设置aj-captcha匿名访问权限

在 ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java 文件中configure方法下的httpSecurity添加如下语句:

.antMatchers("/login", "/captcha/get", "/captcha/check").permitAll()

这步保证这仨方法能够不登陆就访问,不然你登录的时候要求你有权限才能登录,就尬住了。

“我要登录。”
"对不起先生,您没有权限,不能登录。"
“我怎么才能有权限?”
"您需要先登录才有权限。"
“我要登录。”
"对不起先生,您没有权限,不能登录。"
我们要的是20岁拥有30年工作经验的人才.java

5.修改代码

​ 若依官方文档让修改 ruoyi-admin\com\ruoyi\web\controller\system\SysLoginController.java,但是其实改不改都行,区别就是改之前四个参数,改之后三个参数,多的那个参数uuid在换成滑动验证码之后其实用不到,所以传不传都行。

但是得保证修改之后,SysLoginController里的调用和SysLoginService里的定义是一致的,不管三个还是四个参数,得对应起来,不然就会报错。

我参考那个文章里有一句提醒:

大家在修改代码时,切记不要按照文档官方文档直接覆盖,建议使用文本比较工具将代码进行比对后只更新相关内容,这也是开发人员的良好习惯!!!!

​ 这个其实很重要,但是翻了翻一些文档对比工具,好多都要钱,而且如果是在公司电脑上开发,有可能还不让你下载,所以我用的这个http://www.jsons.cn/txtdiff/

免费的,虽然界面可能low了点,但是挺好使。

并且记得边修改边思考,因为自身水平有限,所以我经常性进行递归式学习,看到这句代码,不知道干嘛的,点进去看一看,百度搜一搜,然后看的过程中又发现了不懂的,又往里进一层,继续点进去看继续搜。然后一层一层越进越深。但是学的东西也越来越多。

盗梦空间了属于是。

主要是这是个学习的过程,不是只为了使用而去使用。学习嘛,不寒碜。

(1)修改SysLoginService.java

修改 com/ruoyi/framework/web/service/SysLoginService.java 的内容,这个文件建议不要看北京时间 2022年10月9日21:00 之前的官方文档,那个版本官方文档里缺两句东西,用了会报错。

package com.ruoyi.framework.web.service;
 
import javax.annotation.Resource;
 
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.anji.captcha.model.common.ResponseModel;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.system.service.ISysUserService;
 
/**
 * 登录校验方法
 *
 * @author ruoyi
 */
@Component
public class SysLoginService
{
    @Autowired
    private TokenService tokenService;
 
    @Resource
    private AuthenticationManager authenticationManager;
 
    @Autowired
    private ISysUserService userService;
 
    @Autowired
    @Lazy
    private CaptchaService captchaService;
 
    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @return 结果
     */
    public String login(String username, String password, String code)
    {
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(code);
        ResponseModel response = captchaService.verification(captchaVO);
        if (!response.isSuccess())
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,
                    MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
        // 用户验证
        Authentication authentication = null;
        try
        {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            UsernamePasswordAuthenticationToken temp = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(temp);
            authentication = authenticationManager.authenticate(temp);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 生成token
        return tokenService.createToken(loginUser);
    }
 
    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }
}

就是说不要无脑复制,最好对比一下看一看思考一下跟以前哪里不一样。

这里跟官方文档有区别的地方是这里:

// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        UsernamePasswordAuthenticationToken temp = new UsernamePasswordAuthenticationToken(username, password);
        AuthenticationContextHolder.setContext(temp);
        authentication = authenticationManager.authenticate(temp);

官方旧版:

 // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));

​ 就差一句setContext。可能是某个接口单独起了线程,所以要加一句setContext进行Holder的绑定,不然取不出来东西。

有关这个SecurityContext的相关知识详见:https://www.jianshu.com/p/a951859333a4,供递归式学习使用。

关于空指针报错的排查:SecurityContextHolder.getContext().getAuthentication()报空指针异常,已解决。_qq_48697226的博客-CSDN博客

这个issue在我写这篇文章的11天前就被提出(正好国庆放假前一天),然后在写这篇文章的18小时之前官方文档修复了,而昨天一整天我都被这个莫名其妙的空指针报错折磨着。

issue地址:更换滑块验证码的BUG · Issue #I5TUBG · 若依/RuoYi - Gitee.com

修了就好,可喜可贺。

(2)新增 CaptchaRedisService.java

ruoyi-framework\com\ruoyi\framework\web\service\ 目录下新建 CaptchaRedisService.java

package com.ruoyi.framework.web.service;
 
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.anji.captcha.service.CaptchaCacheService;
 
/**
 * 自定义redis验证码缓存实现类
 *
 * @author ruoyi
 */
public class CaptchaRedisService implements CaptchaCacheService
{
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
 
    @Override
    public void set(String key, String value, long expiresInSeconds)
    {
        stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
    }
 
    @Override
    public boolean exists(String key)
    {
        return stringRedisTemplate.hasKey(key);
    }
 
    @Override
    public void delete(String key)
    {
        stringRedisTemplate.delete(key);
    }
 
    @Override
    public String get(String key)
    {
        return stringRedisTemplate.opsForValue().get(key);
    }
 
    @Override
    public Long increment(String key, long val)
    {
        return stringRedisTemplate.opsForValue().increment(key, val);
    }
 
    @Override
    public String type()
    {
        return "redis";
    }
}

(3)删除不要的类

ruoyi-admin\com\ruoyi\web\controller\common\CaptchaController.java

ruoyi-framework\com\ruoyi\framework\config\CaptchaConfig.java

ruoyi-framework\com\ruoyi\framework\config\KaptchaTextCreator.java

不删直接运行会报错,如果运行的时候报错org.springframework.beans.factory.BeanDefinitionStoreException

八成是CaptchaController没删,导的那个依赖里边也有一个同名文件,所以创建Bean的时候重名了显示已存在。

6、导入资源

将aj-captcha 官方提供的demo中的images包整体引入到resources包下。

文章的最上边提供了下载地址,可自行下载。

至此,后端全部修改完毕。

二、前端修改

​ 其实前端改的比较简单,不容易出什么错,就不分步骤了,简单一说:

​ 下载官方提供的前端修改代码,就我一开始说要下载的那个

链接: https://pan.baidu.com/s/13JVC9jm-Dp9PfHdDDylLCQ 提取码: y9jt

在ruoyi-ui目录下的文件不要全部替换到项目中,建议比较一下改动内容。

  • ruoyi-ui\package.json
    dependencies 增加

     "crypto-js": "^4.1.1",
    
  • ruoyi-ui\src\views\login.vue
    删除原来的验证码部分,增加滑块验证码相关代码

    <template>
      <div class="login">
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
          <h3 class="title">若依后台管理系统</h3>
          <el-form-item prop="username">
            <el-input
              v-model="loginForm.username"
              type="text"
              auto-complete="off"
              placeholder="账号"
            >
              <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
            </el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input
              v-model="loginForm.password"
              type="password"
              auto-complete="off"
              placeholder="密码"
              @keyup.enter.native="handleLogin"
            >
              <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
            </el-input>
          </el-form-item>
          <Verify
            @success="capctchaCheckSuccess"
            :mode="'pop'"
            :captchaType="'blockPuzzle'"
            :imgSize="{ width: '330px', height: '155px' }"
            ref="verify"
          ></Verify>
          <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
          <el-form-item style="width:100%;">
            <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>
            <div style="float: right;" v-if="register">
              <router-link class="link-type" :to="'/register'">立即注册</router-link>
            </div>
          </el-form-item>
        </el-form>
        <!--  底部  -->
        <div class="el-login-footer">
          <span>Copyright © 2018-2022 ruoyi.vip All Rights Reserved.</span>
        </div>
      </div>
    </template>
     
    <script>
    import Cookies from "js-cookie";
    import { encrypt, decrypt } from '@/utils/jsencrypt'
    import Verify from "@/components/Verifition/Verify";
     
    export default {
      components: { Verify },
      name: "Login",
      data() {
        return {
          loginForm: {
            username: "admin",
            password: "admin123",
            rememberMe: false,
            code: "",
          },
          loginRules: {
            username: [
              { required: true, trigger: "blur", message: "请输入您的账号" }
            ],
            password: [
              { required: true, trigger: "blur", message: "请输入您的密码" }
            ],
          },
          loading: false,
          // 注册开关
          register: false,
          redirect: undefined
        };
      },
      watch: {
        $route: {
          handler: function(route) {
            this.redirect = route.query && route.query.redirect;
          },
          immediate: true
        }
      },
      created() {
        this.getCookie();
      },
      methods: {
     
        getCookie() {
          const username = Cookies.get("username");
          const password = Cookies.get("password");
          const rememberMe = Cookies.get('rememberMe')
          this.loginForm = {
            username: username === undefined ? this.loginForm.username : username,
            password: password === undefined ? this.loginForm.password : decrypt(password),
            rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
          };
        },
        capctchaCheckSuccess(params) {
          this.loginForm.code = params.captchaVerification;
          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;
            });
        },
        handleLogin() {
          this.$refs.loginForm.validate((valid) => {
            if (valid) {
              this.$refs.verify.show();
            }
          });
        }
      }
    };
    </script>
     
    <style rel="stylesheet/scss" lang="scss">
    .login {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100%;
      background-image: url("../assets/images/login-background.jpg");
      background-size: cover;
    }
    .title {
      margin: 0px auto 30px auto;
      text-align: center;
      color: #707070;
    }
     
    .login-form {
      border-radius: 6px;
      background: #ffffff;
      width: 400px;
      padding: 25px 25px 5px 25px;
      .el-input {
        height: 38px;
        input {
          height: 38px;
        }
      }
      .input-icon {
        height: 39px;
        width: 14px;
        margin-left: 2px;
      }
    }
    .login-tip {
      font-size: 13px;
      text-align: center;
      color: #bfbfbf;
    }
    .login-code {
      width: 33%;
      height: 38px;
      float: right;
      img {
        cursor: pointer;
        vertical-align: middle;
      }
    }
    .el-login-footer {
      height: 40px;
      line-height: 40px;
      position: fixed;
      bottom: 0;
      width: 100%;
      text-align: center;
      color: #fff;
      font-family: Arial;
      font-size: 12px;
      letter-spacing: 1px;
    }
    .login-code-img {
      height: 38px;
    }
    </style>
    
    • 其他资源
      将 “集成aj-captcha实现滑块验证码\ruoyi-ui\src\“ 目录下assets、components两个目录直接复制到项目中。

前端更新crypto-js (npm install --save crypto-js)

​ api文件夹里的东西可以不改,看一眼就知道,api里边没新增东西,只删了一些原来的验证码用的,现在用不到了,不删也能运行。

​ 然后运行应该就能正常用了

标签:username,滑块,aj,Ruoyi,ruoyi,captcha,import,password,com
From: https://www.cnblogs.com/RioTian/p/17230872.html

相关文章

  • Ajax提交后Moedl And View不进行页面跳转的解决方案
    场景前端使用ajax请求后台,返回ModelAndView后不进行页面跳转。用ajax提交是没有页面跳转的。实现如果只是简单的页面跳转和传递单个简单参数可以使用:window.location.hre......
  • Dcat-Admin改写ajax实现请求过滤同名参数
    //方案一Admin::script( <<<JS(function($){//备份jquery的ajax方法var_ajax=$.ajax;//重写jquery的ajax方法......
  • ajax
    JSON1、初识JSONJSON是什么JSON是Ajax发送和接收数据的一种格式JSON全称是JavascriptObjectNotationJSON数据一般放在一个.json的文件中,这个文件数据格式要遵......
  • react警告:Warning: [antd: Menu] children will be removed in next major version. P
    这是由于antv4.20+优化了导航菜单Menu的使用方式,采用优化后的方式使用菜单组件就可以了。更新前:<MenuonClick={this.handleClick}style={{wi......
  • ruoyi-vue启动报错error:03000086:digital envelope routines::initialization error
    原因分析可能是因为node是最新版本导致ERR_OSSL_EVP_UNSUPPORTED错误SSL数字信封不支持解决措施(ps:网上找的)setNODE_OPTIONS=--openssl-legacy-provider亲测并......
  • Ajax
    1.URL地址的组成部分URL地址一般由三部分组成1.客户端与服务器之间的通信协议2.存有该资源的服务器名称3.资源在服务器上具体的存放位置2.get和post请求get请求通常......
  • Vue 代理服务器___Vue 跨域通过Axios 的ajax方式的get请求获取数据
    Vue代理服务器___Vue跨域通过Axios的ajax方式的get请求获取数据1、说明1.1:配置2台本地服务器说明:node_modules为vue脚手架        package.json为静态数据......
  • 用jquery进行ajax渲染
    html代码1<!DOCTYPEhtml>2<htmllang="en">3<head>4<metacharset="UTF-8">5<metahttp-equiv="X-UA-Compatible"content="IE=edge">6<m......
  • ajax 泛微
    <%@pageimport="weaver.soa.workflow.request.RequestInfo"%><%@pageimport="weaver.soa.workflow.request.RequestService"%><%@pageimport="weaver.general.Util"......
  • Ajax续
    一.前端拿取后端的传递结果Java:@RequestMapping("/a2")publicList<User>a2(){ArrayList<User>list=newArrayList<User>();list.add(n......