提交 16ea2d1b 作者: 方治民

feat: 添加图形验证码生成接口、登录校验扩展图形验证码配置等

上级 ce3cf67b
......@@ -40,6 +40,13 @@ spring:
# ----------------------------------------------
# ----------------------------------------------
# Auth
auth:
# 是否启用图形验证码进行校验
captcha: true
# ----------------------------------------------
# ----------------------------------------------
# Spring Doc
springdoc:
default-consumes-media-type: "application/x-www-form-urlencoded"
......
......@@ -29,17 +29,17 @@ ext {
// SpringBootAdmin
// https://central.sonatype.com/artifact/de.codecentric/spring-boot-admin-starter-server
springBootAdminVersion = '3.3.3'
springBootAdminVersion = '3.3.4'
// Sentry
// https://central.sonatype.com/artifact/io.sentry/sentry-spring-boot-starter-jakarta
sentryVersion = '7.14.0'
sentryVersion = '7.15.0'
// Dependencies
// https://central.sonatype.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter
knife4jOpen3Version = '4.5.0'
// https://central.sonatype.com/artifact/io.swagger.core.v3/swagger-annotations
swaggerAnnotationsVersion = '2.2.23'
swaggerAnnotationsVersion = '2.2.25'
// https://central.sonatype.com/artifact/cn.dev33/sa-token-spring-boot3-starter
saTokenVersion = '1.39.0'
// https://central.sonatype.com/artifact/cn.hutool/hutool-core
......@@ -59,9 +59,9 @@ ext {
// https://mvnrepository.com/artifact/org.n52.jackson/jackson-datatype-jts
jacksonDatatypeJtsVersion = '1.2.10'
// https://central.sonatype.com/artifact/com.github.liaochong/myexcel
myexcelVersion = '4.5.2'
myexcelVersion = '4.5.3'
// https://central.sonatype.com/artifact/org.jetbrains/annotations
jetbrainsAnnotationsVersion = '24.1.0'
jetbrainsAnnotationsVersion = '26.0.0'
// https://central.sonatype.com/artifact/org.apache.pdfbox/pdfbox
pdfboxVersion = '3.0.3'
// https://central.sonatype.com/artifact/org.lionsoul/ip2region
......
......@@ -4,6 +4,7 @@ plugins {
dependencies {
implementation project(':modules:common:core')
implementation project(':modules:common:redis')
implementation project(':modules:common:i18n')
implementation project(':modules:common:util')
......@@ -25,6 +26,7 @@ dependencies {
// hutool-core
implementation "cn.hutool:hutool-core:${hutoolVersion}"
implementation "cn.hutool:hutool-http:${hutoolVersion}"
implementation "cn.hutool:hutool-captcha:${hutoolVersion}"
// https://github.com/vladmihalcea/hypersistence-utils
// hypersistence-utils-hibernate-63
......
/* (C) 2024 YiRing, Inc. */
package com.yiring.auth.config;
import java.io.Serial;
import java.io.Serializable;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author Jim
*/
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Configuration("custom.auth.config")
@ConfigurationProperties(prefix = "auth")
public class AuthConfig implements Serializable {
@Serial
private static final long serialVersionUID = 2896697722679227230L;
/**
* 是否启用验证码校验
*/
boolean captcha;
}
/* (C) 2024 YiRing, Inc. */
package com.yiring.auth.core;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.yiring.auth.vo.auth.CaptchaVo;
import com.yiring.common.core.Redis;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author Jim
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class Captcha {
final Redis redis;
static final String CAPTCHA_KEY_PREFIX = "CAPTCHA:";
/**
* 生成验证码
*
* @return CaptchaVo 验证码信息
*/
public CaptchaVo create() {
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 80);
String key = IdUtil.fastSimpleUUID();
CaptchaVo vo = CaptchaVo
.builder()
.key(key)
.data("data:image/jpg;base64," + lineCaptcha.getImageBase64())
.build();
redis.set(CAPTCHA_KEY_PREFIX + key, lineCaptcha.getCode(), 300);
return vo;
}
/**
* 校验验证码
*
* @param key 验证码标识
* @param code 验证码识别结果
* @return true: 验证通过 false: 验证失败
*/
public boolean verify(String key, String code) {
if (StrUtil.isBlank(key) || StrUtil.isBlank(code)) {
return false;
}
String cacheKey = CAPTCHA_KEY_PREFIX + key;
String value = redis.get(cacheKey, String.class);
if (value == null || !value.equals(code)) {
return false;
}
redis.del(cacheKey);
return true;
}
}
/* (C) 2021 YiRing, Inc. */
package com.yiring.auth.param.auth;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 验证码校验参数
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@Schema(name = "CaptchaParam")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class CaptchaParam implements Serializable {
@Serial
private static final long serialVersionUID = 2482112377591343046L;
@Schema(description = "验证码校验标识", example = "b6523c1198f64602aea006238a744de7")
String captchaKey;
@Schema(description = "验证码识别结果", example = "vLUF4")
String captchaCode;
}
/* (C) 2021 YiRing, Inc. */
package com.yiring.auth.vo.auth;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serial;
import java.io.Serializable;
import lombok.*;
import lombok.experimental.FieldDefaults;
/**
* 验证码输出信息
*
* @author ifzm
* @version 0.1
* 2019/5/28 22:11
*/
@Schema(name = "CaptchaVo")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class CaptchaVo implements Serializable {
@Serial
private static final long serialVersionUID = 5303746889633243114L;
@Schema(description = "验证码唯一标识", example = "b6523c1198f64602aea006238a744de7")
String key;
@Schema(description = "验证码图片 Base64", example = "data:image/jpg;base64,iV==")
String data;
}
......@@ -7,14 +7,18 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.*;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.yiring.auth.config.AuthConfig;
import com.yiring.auth.core.Captcha;
import com.yiring.auth.domain.log.LoginLog;
import com.yiring.auth.domain.log.LoginLogRepository;
import com.yiring.auth.domain.user.User;
import com.yiring.auth.domain.user.UserRepository;
import com.yiring.auth.param.auth.CaptchaParam;
import com.yiring.auth.param.auth.LoginParam;
import com.yiring.auth.param.auth.RegisterParam;
import com.yiring.auth.param.auth.SafeParam;
import com.yiring.auth.util.Auths;
import com.yiring.auth.vo.auth.CaptchaVo;
import com.yiring.auth.vo.auth.LoginVo;
import com.yiring.common.annotation.RateLimiter;
import com.yiring.common.core.I18n;
......@@ -56,6 +60,8 @@ public class AuthController {
final I18n i18n;
final Auths auths;
final Captcha captcha;
final AuthConfig authConfig;
final UserRepository userRepository;
final LoginLogRepository loginLogRepository;
......@@ -100,7 +106,11 @@ public class AuthController {
@RateLimiter(count = 3)
@Operation(summary = "登录")
@PostMapping("login")
public Result<LoginVo> login(@ParameterObject @Validated LoginParam param, HttpServletRequest request) {
public Result<LoginVo> login(
@ParameterObject @Validated LoginParam param,
@ParameterObject CaptchaParam captchaParam,
HttpServletRequest request
) {
// 获取登录 IP
// String ip = "222.244.92.58";
String ip = IpUtil.getClientIp(request);
......@@ -138,6 +148,13 @@ public class AuthController {
}
try {
// 校验图形验证码
if (authConfig.isCaptcha() || StrUtil.isNotBlank(captchaParam.getCaptchaKey())) {
if (!captcha.verify(captchaParam.getCaptchaKey(), captchaParam.getCaptchaCode())) {
throw BusinessException.i18n("Code.100006");
}
}
// 查询用户信息是否匹配
User user = userRepository.findByAccount(param.getAccount());
if (user == null) {
......@@ -220,4 +237,11 @@ public class AuthController {
return Result.no();
}
@Operation(summary = "生成图形验证码")
@GetMapping("captcha")
public Result<CaptchaVo> captcha() {
CaptchaVo vo = captcha.create();
return Result.ok(vo);
}
}
......@@ -11,3 +11,4 @@ Code.100002=\u90AE\u7BB1\u5DF2\u5B58\u5728
Code.100003=\u8D26\u53F7\u5BC6\u7801\u9519\u8BEF
Code.100004=\u7528\u6237\u88AB\u7981\u7528, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
Code.100005=\u7528\u6237\u88AB\u7981\u6B62\u767B\u5F55, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
Code.100006=\u9A8C\u8BC1\u7801\u6821\u9A8C\u5931\u8D25
......@@ -11,3 +11,4 @@ Code.100002=\u90AE\u7BB1\u5DF2\u5B58\u5728
Code.100003=\u8D26\u53F7\u5BC6\u7801\u9519\u8BEF
Code.100004=\u7528\u6237\u88AB\u7981\u7528, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
Code.100005=\u7528\u6237\u88AB\u7981\u6B62\u767B\u5F55, \u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
Code.100006=\u9A8C\u8BC1\u7801\u6821\u9A8C\u5931\u8D25
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论