Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class CaptchaConfig {
@Getter(onMethod_ = @NonNull)
private CaptchaType type = CaptchaType.ALPHANUMERIC;

private boolean ignoreCase = true;

private int captchaLength = 4;

private int arithmeticRange = 90;

public CaptchaConfig setType(CaptchaType type) {
this.type = (type == null ? CaptchaType.ALPHANUMERIC : type);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public RouterFunction<ServerResponse> endpoint() {
private Mono<ServerResponse> generateCaptcha(ServerRequest request) {
return settingConfigGetter.getSecurityConfig()
.map(SettingConfigGetter.SecurityConfig::getCaptcha)
.flatMap(captchaConfig -> captchaManager.generate(request.exchange(), captchaConfig.getType()))
.flatMap(captchaConfig -> captchaManager.generate(request.exchange(), captchaConfig))
.flatMap(captcha -> ServerResponse.ok().bodyValue(captcha.imageBase64()));
}

Expand Down
29 changes: 16 additions & 13 deletions src/main/java/run/halo/comment/widget/captcha/CaptchaGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ public class CaptchaGenerator {
customFont = loadArialFont();
}

public static Captcha generateMathCaptcha() {
return generateCaptchaImage(CaptchaGenerator::drawMathCaptchaText);
public static Captcha generateMathCaptcha(int arithmeticRange) {
return generateCaptchaImage((g2d) -> drawMathCaptchaText(g2d, arithmeticRange));
}

public static Captcha generateSimpleCaptcha() {
return generateCaptchaImage(CaptchaGenerator::drawSimpleText);
public static Captcha generateSimpleCaptcha(int captchaLength) {
return generateCaptchaImage((g2d) -> drawSimpleText(g2d, captchaLength));
}

private static Captcha generateCaptchaImage(Function<Graphics2D, String> drawCaptchaTextFunc) {
Expand Down Expand Up @@ -61,10 +61,10 @@ private static Captcha generateCaptchaImage(Function<Graphics2D, String> drawCap
return new Captcha(captchaText, bufferedImage);
}

private static String drawMathCaptchaText(Graphics2D g2d) {
private static String drawMathCaptchaText(Graphics2D g2d, int arithmeticRange) {
Random random = new Random();
int num1 = random.nextInt(90) + 1;
int num2 = random.nextInt(90) + 1;
int num1 = random.nextInt(arithmeticRange) + 1;
int num2 = random.nextInt(arithmeticRange) + 1;
char operator = getRandomOperator();

int result;
Expand Down Expand Up @@ -92,12 +92,15 @@ private static String drawMathCaptchaText(Graphics2D g2d) {
public record Captcha(String code, BufferedImage image) {
}

private static String drawSimpleText(Graphics2D g2d) {
var captchaText = generateRandomText();
private static String drawSimpleText(Graphics2D g2d, int captchaLength) {
var captchaText = generateRandomText(captchaLength);
Random random = new Random();
int charSpacing = WIDTH / (captchaLength + 1);
for (int i = 0; i < captchaText.length(); i++) {
g2d.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
g2d.drawString(String.valueOf(captchaText.charAt(i)), 20 + i * 24, 30);
// 动态计算每个字符的位置
int xPos = charSpacing + i * charSpacing;
g2d.drawString(String.valueOf(captchaText.charAt(i)), xPos, 30);
}
return captchaText;
}
Expand All @@ -121,10 +124,10 @@ private static char getRandomOperator() {
return operators[random.nextInt(operators.length)];
}

private static String generateRandomText() {
StringBuilder sb = new StringBuilder(CHAR_LENGTH);
private static String generateRandomText(int captchaLength) {
StringBuilder sb = new StringBuilder(captchaLength);
Random random = new Random();
for (int i = 0; i < CHAR_LENGTH; i++) {
for (int i = 0; i < captchaLength; i++) {
sb.append(CHAR_STRING.charAt(random.nextInt(CHAR_STRING.length())));
}
return sb.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import run.halo.comment.widget.SettingConfigGetter;

public interface CaptchaManager {
Mono<Boolean> verify(String id, String captchaCode);
Mono<Boolean> verify(String id, String captchaCode, boolean ignoreCase);

Mono<Void> invalidate(String id);

Mono<Captcha> generate(ServerWebExchange exchange, CaptchaType type);
Mono<Captcha> generate(ServerWebExchange exchange, SettingConfigGetter.CaptchaConfig captchaConfig);

record Captcha(String id, String code, String imageBase64) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import run.halo.comment.widget.SettingConfigGetter;

@Component
@RequiredArgsConstructor
Expand All @@ -25,9 +26,9 @@ public class CaptchaManagerImpl implements CaptchaManager {
private final CaptchaCookieResolver captchaCookieResolver;

@Override
public Mono<Boolean> verify(String key, String captchaCode) {
public Mono<Boolean> verify(String key, String captchaCode, boolean ignoreCase) {
return Mono.justOrEmpty(captchaCache.getIfPresent(key))
.filter(captcha -> captcha.code().equalsIgnoreCase(captchaCode))
.filter(captcha -> ignoreCase ? captcha.code().equalsIgnoreCase(captchaCode) : captcha.code().equals(captchaCode))
.hasElement();
}

Expand All @@ -38,16 +39,16 @@ public Mono<Void> invalidate(String id) {
}

@Override
public Mono<Captcha> generate(ServerWebExchange exchange, CaptchaType type) {
return doGenerate(type)
public Mono<Captcha> generate(ServerWebExchange exchange, SettingConfigGetter.CaptchaConfig captchaConfig) {
return doGenerate(captchaConfig)
.doOnNext(captcha -> captchaCookieResolver.setCookie(exchange, captcha.id()));
}

private Mono<Captcha> doGenerate(CaptchaType type) {
private Mono<Captcha> doGenerate(SettingConfigGetter.CaptchaConfig captchaConfig) {
return Mono.fromSupplier(() -> {
var captcha = switch (type) {
case ALPHANUMERIC -> CaptchaGenerator.generateSimpleCaptcha();
case ARITHMETIC -> CaptchaGenerator.generateMathCaptcha();
var captcha = switch (captchaConfig.getType()) {
case ALPHANUMERIC -> CaptchaGenerator.generateSimpleCaptcha(captchaConfig.getCaptchaLength());
case ARITHMETIC -> CaptchaGenerator.generateMathCaptcha(captchaConfig.getArithmeticRange());
};
var imageBase64 = encodeBufferedImageToDataUri(captcha.image());
var id = UUID.randomUUID().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilter
private Mono<Void> sendCaptchaRequiredResponse(ServerWebExchange exchange,
SettingConfigGetter.CaptchaConfig captchaConfig,
ResponseStatusException e) {
var type = captchaConfig.getType();
exchange.getResponse().getHeaders().addIfAbsent(CAPTCHA_REQUIRED_HEADER, "true");
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return captchaManager.generate(exchange, type)
return captchaManager.generate(exchange, captchaConfig)
.flatMap(captcha -> {
var problemDetail = toProblemDetail(e);
problemDetail.setProperty("captcha", captcha.imageBase64());
Expand All @@ -94,7 +93,7 @@ private Mono<Void> validateCaptcha(ServerWebExchange exchange, WebFilterChain ch
if (captchaCodeOpt.isEmpty() || cookie == null) {
return sendCaptchaRequiredResponse(exchange, captchaConfig, new CaptchaCodeMissingException());
}
return captchaManager.verify(cookie.getValue(), captchaCodeOpt.get())
return captchaManager.verify(cookie.getValue(), captchaCodeOpt.get(), captchaConfig.isIgnoreCase())
.flatMap(valid -> {
if (valid) {
captchaCookieResolver.expireCookie(exchange);
Expand Down
30 changes: 30 additions & 0 deletions src/main/resources/extensions/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,42 @@ spec:
label: 验证码类型
if: "$get(anonymousCommentCaptcha).value === true"
name: type
id: type
value: "ALPHANUMERIC"
options:
- label: 字母数字组合
value: "ALPHANUMERIC"
- label: 算术验证码
value: "ARITHMETIC"
- $formkit: number
if: "$get(type).value === ALPHANUMERIC"
name: captchaLength
key: captchaLength
label: 验证码长度
help: 字母数字组合验证码长度,不宜过长或过短,建议4-6位
max: 8
value: 4
validation: required
- $formkit: radio
if: "$get(type).value === ALPHANUMERIC"
name: ignoreCase
key: ignoreCase
label: 是否忽略验证码大小写验证
help: 忽略大小写验证码,建议开启
value: true
options:
- label: 是
value: true
- label: 否
value: false
- $formkit: number
if: "$get(type).value === ARITHMETIC"
name: arithmeticRange
key: arithmeticRange
label: 计算范围
help: 算术验证码计算范围
value: 90
validation: required
- group: avatar
label: 头像设置
formSchema:
Expand Down