mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-20 05:50:53 +08:00
Add configurable captcha endpoints
This commit is contained in:
@@ -46,6 +46,12 @@ OpenIsle 基于 Spring Boot 构建,提供社区后台常见的注册、登录
|
||||
- `JWT_SECRET`:JWT 签名密钥
|
||||
- `JWT_EXPIRATION`:JWT 过期时间(毫秒)
|
||||
- `PASSWORD_STRENGTH`:密码强度(LOW、MEDIUM、HIGH)
|
||||
- `CAPTCHA_ENABLED`:是否启用验证码(true/false)
|
||||
- `RECAPTCHA_SECRET_KEY`:Google reCAPTCHA 密钥
|
||||
- `CAPTCHA_REGISTER_ENABLED`:注册是否需要验证码
|
||||
- `CAPTCHA_LOGIN_ENABLED`:登录是否需要验证码
|
||||
- `CAPTCHA_POST_ENABLED`:发帖是否需要验证码
|
||||
- `CAPTCHA_COMMENT_ENABLED`:评论是否需要验证码
|
||||
2. 启动项目:
|
||||
|
||||
```bash
|
||||
@@ -56,6 +62,7 @@ mvn spring-boot:run
|
||||
|
||||
- `POST /api/auth/register`:注册新用户
|
||||
- `POST /api/auth/login`:登录并获取 Token
|
||||
- `GET /api/config`:查看验证码开关配置
|
||||
- 需要认证的接口示例:`GET /api/hello`(需 `Authorization` 头)
|
||||
- 管理员接口示例:`GET /api/admin/hello`
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ import com.openisle.model.User;
|
||||
import com.openisle.service.EmailSender;
|
||||
import com.openisle.service.JwtService;
|
||||
import com.openisle.service.UserService;
|
||||
import com.openisle.service.CaptchaService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -18,9 +20,22 @@ public class AuthController {
|
||||
private final UserService userService;
|
||||
private final JwtService jwtService;
|
||||
private final EmailSender emailService;
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
@Value("${app.captcha.enabled:false}")
|
||||
private boolean captchaEnabled;
|
||||
|
||||
@Value("${app.captcha.register-enabled:false}")
|
||||
private boolean registerCaptchaEnabled;
|
||||
|
||||
@Value("${app.captcha.login-enabled:false}")
|
||||
private boolean loginCaptchaEnabled;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@RequestBody RegisterRequest req) {
|
||||
if (captchaEnabled && registerCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "Invalid captcha"));
|
||||
}
|
||||
User user = userService.register(req.getUsername(), req.getEmail(), req.getPassword());
|
||||
emailService.sendEmail(user.getEmail(), "Verification Code", "Your verification code is " + user.getVerificationCode());
|
||||
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
|
||||
@@ -37,6 +52,9 @@ public class AuthController {
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestBody LoginRequest req) {
|
||||
if (captchaEnabled && loginCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "Invalid captcha"));
|
||||
}
|
||||
Optional<User> user = userService.authenticate(req.getUsername(), req.getPassword());
|
||||
if (user.isPresent()) {
|
||||
return ResponseEntity.ok(Map.of("token", jwtService.generateToken(user.get().getUsername())));
|
||||
@@ -50,12 +68,14 @@ public class AuthController {
|
||||
private String username;
|
||||
private String email;
|
||||
private String password;
|
||||
private String captcha;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
private String captcha;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.openisle.controller;
|
||||
|
||||
import com.openisle.model.Comment;
|
||||
import com.openisle.service.CommentService;
|
||||
import com.openisle.service.CaptchaService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -17,11 +19,21 @@ import java.util.stream.Collectors;
|
||||
@RequiredArgsConstructor
|
||||
public class CommentController {
|
||||
private final CommentService commentService;
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
@Value("${app.captcha.enabled:false}")
|
||||
private boolean captchaEnabled;
|
||||
|
||||
@Value("${app.captcha.comment-enabled:false}")
|
||||
private boolean commentCaptchaEnabled;
|
||||
|
||||
@PostMapping("/posts/{postId}/comments")
|
||||
public ResponseEntity<CommentDto> createComment(@PathVariable Long postId,
|
||||
@RequestBody CommentRequest req,
|
||||
Authentication auth) {
|
||||
if (captchaEnabled && commentCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
Comment comment = commentService.addComment(auth.getName(), postId, req.getContent());
|
||||
return ResponseEntity.ok(toDto(comment));
|
||||
}
|
||||
@@ -30,6 +42,9 @@ public class CommentController {
|
||||
public ResponseEntity<CommentDto> replyComment(@PathVariable Long commentId,
|
||||
@RequestBody CommentRequest req,
|
||||
Authentication auth) {
|
||||
if (captchaEnabled && commentCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
Comment comment = commentService.addReply(auth.getName(), commentId, req.getContent());
|
||||
return ResponseEntity.ok(toDto(comment));
|
||||
}
|
||||
@@ -62,6 +77,7 @@ public class CommentController {
|
||||
@Data
|
||||
private static class CommentRequest {
|
||||
private String content;
|
||||
private String captcha;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
47
src/main/java/com/openisle/controller/ConfigController.java
Normal file
47
src/main/java/com/openisle/controller/ConfigController.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.openisle.controller;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class ConfigController {
|
||||
|
||||
@Value("${app.captcha.enabled:false}")
|
||||
private boolean captchaEnabled;
|
||||
|
||||
@Value("${app.captcha.register-enabled:false}")
|
||||
private boolean registerCaptchaEnabled;
|
||||
|
||||
@Value("${app.captcha.login-enabled:false}")
|
||||
private boolean loginCaptchaEnabled;
|
||||
|
||||
@Value("${app.captcha.post-enabled:false}")
|
||||
private boolean postCaptchaEnabled;
|
||||
|
||||
@Value("${app.captcha.comment-enabled:false}")
|
||||
private boolean commentCaptchaEnabled;
|
||||
|
||||
@GetMapping("/config")
|
||||
public ConfigResponse getConfig() {
|
||||
ConfigResponse resp = new ConfigResponse();
|
||||
resp.setCaptchaEnabled(captchaEnabled);
|
||||
resp.setRegisterCaptchaEnabled(registerCaptchaEnabled);
|
||||
resp.setLoginCaptchaEnabled(loginCaptchaEnabled);
|
||||
resp.setPostCaptchaEnabled(postCaptchaEnabled);
|
||||
resp.setCommentCaptchaEnabled(commentCaptchaEnabled);
|
||||
return resp;
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class ConfigResponse {
|
||||
private boolean captchaEnabled;
|
||||
private boolean registerCaptchaEnabled;
|
||||
private boolean loginCaptchaEnabled;
|
||||
private boolean postCaptchaEnabled;
|
||||
private boolean commentCaptchaEnabled;
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ import com.openisle.model.Reaction;
|
||||
import com.openisle.service.CommentService;
|
||||
import com.openisle.service.PostService;
|
||||
import com.openisle.service.ReactionService;
|
||||
import com.openisle.service.CaptchaService;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
@@ -23,9 +25,19 @@ public class PostController {
|
||||
private final PostService postService;
|
||||
private final CommentService commentService;
|
||||
private final ReactionService reactionService;
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
@Value("${app.captcha.enabled:false}")
|
||||
private boolean captchaEnabled;
|
||||
|
||||
@Value("${app.captcha.post-enabled:false}")
|
||||
private boolean postCaptchaEnabled;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<PostDto> createPost(@RequestBody PostRequest req, Authentication auth) {
|
||||
if (captchaEnabled && postCaptchaEnabled && !captchaService.verify(req.getCaptcha())) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
Post post = postService.createPost(auth.getName(), req.getCategoryId(), req.getTitle(), req.getContent());
|
||||
return ResponseEntity.ok(toDto(post));
|
||||
}
|
||||
@@ -110,6 +122,7 @@ public class PostController {
|
||||
private Long categoryId;
|
||||
private String title;
|
||||
private String content;
|
||||
private String captcha;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
14
src/main/java/com/openisle/service/CaptchaService.java
Normal file
14
src/main/java/com/openisle/service/CaptchaService.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.openisle.service;
|
||||
|
||||
/**
|
||||
* Abstract service for verifying CAPTCHA tokens.
|
||||
*/
|
||||
public abstract class CaptchaService {
|
||||
/**
|
||||
* Verify the CAPTCHA token sent from client.
|
||||
*
|
||||
* @param token CAPTCHA token
|
||||
* @return true if token is valid
|
||||
*/
|
||||
public abstract boolean verify(String token);
|
||||
}
|
||||
35
src/main/java/com/openisle/service/RecaptchaService.java
Normal file
35
src/main/java/com/openisle/service/RecaptchaService.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.openisle.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CaptchaService implementation using Google reCAPTCHA.
|
||||
*/
|
||||
@Service
|
||||
public class RecaptchaService extends CaptchaService {
|
||||
|
||||
@Value("${recaptcha.secret-key:}")
|
||||
private String secretKey;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Override
|
||||
public boolean verify(String token) {
|
||||
if (token == null || token.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
String url = "https://www.google.com/recaptcha/api/siteverify?secret={secret}&response={response}";
|
||||
try {
|
||||
ResponseEntity<Map> resp = restTemplate.postForEntity(url, null, Map.class, secretKey, token);
|
||||
Map body = resp.getBody();
|
||||
return body != null && Boolean.TRUE.equals(body.get("success"));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,14 @@ app.upload.max-size=${UPLOAD_MAX_SIZE:5242880}
|
||||
app.user.posts-limit=${USER_POSTS_LIMIT:10}
|
||||
app.user.replies-limit=${USER_REPLIES_LIMIT:50}
|
||||
|
||||
# Captcha configuration
|
||||
app.captcha.enabled=${CAPTCHA_ENABLED:false}
|
||||
recaptcha.secret-key=${RECAPTCHA_SECRET_KEY:}
|
||||
app.captcha.register-enabled=${CAPTCHA_REGISTER_ENABLED:false}
|
||||
app.captcha.login-enabled=${CAPTCHA_LOGIN_ENABLED:false}
|
||||
app.captcha.post-enabled=${CAPTCHA_POST_ENABLED:false}
|
||||
app.captcha.comment-enabled=${CAPTCHA_COMMENT_ENABLED:false}
|
||||
|
||||
# ========= Optional =========
|
||||
# for resend email send service, you can improve your service by yourself
|
||||
resend.api.key=${RESEND_API_KEY:}
|
||||
|
||||
Reference in New Issue
Block a user