SpringBoot中登錄驗證碼的4種實現方案
在當今互聯網安全形勢日益嚴峻的環(huán)境下,驗證碼已成為保護用戶賬戶安全、防止破解和自動化攻擊的重要手段。尤其在登錄系統中,合理使用驗證碼不僅能有效阻止機器人批量嘗試賬號密碼,還能降低賬戶被盜風險,提升系統安全性。
本文將詳細介紹在SpringBoot應用中實現四種登錄驗證碼的技術方案,包括圖形驗證碼、短信驗證碼、郵箱驗證碼和滑動拼圖驗證碼。
方案一:基于Kaptcha的圖形驗證碼
原理介紹
圖形驗證碼是最傳統且應用最廣泛的驗證碼類型,原理是在服務端生成隨機字符串并渲染成圖片,用戶需要識別圖片中的字符并輸入。圖形驗證碼實現簡單,對用戶體驗影響較小,是中小型應用的理想選擇。
實現步驟
1. 添加Kaptcha依賴
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>2. 配置Kaptcha生成器
@Configuration
public class KaptchaConfig {
@Bean
public Producer kaptchaProducer() {
Properties properties = new Properties();
// 圖片寬度
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "150");
// 圖片高度
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "50");
// 字體大小
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "32");
// 字體顏色
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 文本集合,驗證碼值從此集合中獲取
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
// 驗證碼長度
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 干擾線顏色
properties.setProperty(Constants.KAPTCHA_NOISE_COLOR, "blue");
// 去除背景漸變
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
return config.getProducerImpl();
}
}3. 創(chuàng)建驗證碼存儲服務
@Service
public class CaptchaService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 驗證碼有效期(秒)
private static final long CAPTCHA_EXPIRATION = 300;
// 存儲驗證碼
public void storeCaptcha(String sessionId, String captchaCode) {
redisTemplate.opsForValue().set(
"CAPTCHA:" + sessionId,
captchaCode,
CAPTCHA_EXPIRATION,
TimeUnit.SECONDS);
}
// 驗證驗證碼
public boolean validateCaptcha(String sessionId, String userInputCaptcha) {
String key = "CAPTCHA:" + sessionId;
String storedCaptcha = redisTemplate.opsForValue().get(key);
if (storedCaptcha != null && storedCaptcha.equalsIgnoreCase(userInputCaptcha)) {
// 驗證成功后立即刪除,防止重復使用
redisTemplate.delete(key);
return true;
}
return false;
}
}4. 實現驗證碼生成控制器
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
@Autowired
private Producer kaptchaProducer;
@Autowired
private CaptchaService captchaService;
@GetMapping("/image")
public void getImageCaptcha(HttpServletResponse response, HttpServletRequest request) throws IOException {
// 清除瀏覽器緩存
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
// 創(chuàng)建驗證碼文本
String capText = kaptchaProducer.createText();
// 獲取會話ID
String sessionId = request.getSession().getId();
// 存儲驗證碼
captchaService.storeCaptcha(sessionId, capText);
// 創(chuàng)建驗證碼圖片
BufferedImage image = kaptchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
// 輸出圖片
ImageIO.write(image, "jpg", out);
out.flush();
out.close();
}
}5. 登錄控制器集成驗證碼校驗
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private CaptchaService captchaService;
@Autowired
private UserService userService;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
// 獲取會話ID
String sessionId = request.getSession().getId();
// 驗證驗證碼
if (!captchaService.validateCaptcha(sessionId, loginRequest.getCaptcha())) {
return ResponseEntity.badRequest().body(new ApiResponse(false, "驗證碼錯誤或已過期"));
}
// 用戶名密碼驗證
boolean authenticated = userService.authenticate(
loginRequest.getUsername(),
loginRequest.getPassword());
if (authenticated) {
// 生成Token或設置會話
String token = jwtTokenProvider.generateToken(loginRequest.getUsername());
return ResponseEntity.ok(new JwtAuthResponse(token));
} else {
return ResponseEntity.badRequest().body(new ApiResponse(false, "用戶名或密碼錯誤"));
}
}
}6. 前端集成示例
<div class="login-form">
<form id="loginForm">
<div class="form-group">
<label for="username">用戶名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="form-group">
<label for="captcha">驗證碼</label>
<div class="captcha-container">
<input type="text" class="form-control" id="captcha" name="captcha" required>
<img id="captchaImg" src="/captcha/image" alt="驗證碼" onclick="refreshCaptcha()">
</div>
</div>
<button type="submit" class="btn btn-primary">登錄</button>
</form>
</div>
<script>
function refreshCaptcha() {
document.getElementById('captchaImg').src = '/captcha/image?t=' + new Date().getTime();
}
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
const data = {
username: document.getElementById('username').value,
password: document.getElementById('password').value,
captcha: document.getElementById('captcha').value
};
fetch('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = '/dashboard';
} else {
alert(data.message);
refreshCaptcha();
}
})
.catch(error => {
console.error('Error:', error);
refreshCaptcha();
});
});
</script>優(yōu)缺點分析
優(yōu)點
- 實現簡單,開發(fā)成本低
- 對服務器資源占用少
- 用戶體驗相對友好
- 無需第三方服務,降低依賴
缺點
- 安全性較低,容易被OCR識別破解
- 對視障用戶不友好
- 在移動設備上體驗不佳
- 隨著AI發(fā)展,圖形驗證碼的安全性逐漸降低
方案二:基于短信驗證碼
原理介紹
短信驗證碼通過向用戶手機發(fā)送一次性驗證碼實現身份驗證。用戶需要輸入收到的驗證碼完成登錄過程。這種方式不僅驗證了賬號密碼的正確性,還確認了用戶對手機號的控制權,大幅提高了安全性。
實現步驟
1. 添加阿里云SDK依賴
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.3</version>
</dependency>2. 配置短信服務參數
@Configuration
@ConfigurationProperties(prefix = "aliyun.sms")
@Data
public class SmsProperties {
private String accessKeyId;
private String accessKeySecret;
private String signName;
private String templateCode;
private String endpoint = "dysmsapi.aliyuncs.com";
}# application.properties aliyun.sms.access-key-id=YOUR_ACCESS_KEY_ID aliyun.sms.access-key-secret=YOUR_ACCESS_KEY_SECRET aliyun.sms.sign-name=YOUR_SMS_SIGN_NAME aliyun.sms.template-code=SMS_TEMPLATE_CODE
3. 實現短信服務
@Service
@Slf4j
public class SmsService {
@Autowired
private SmsProperties smsProperties;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final long SMS_EXPIRATION = 300; // 5分鐘過期
private static final String SMS_PREFIX = "SMS_CAPTCHA:";
/**
* 發(fā)送短信驗證碼
* @param phoneNumber 手機號
* @return 是否發(fā)送成功
*/
public boolean sendSmsCaptcha(String phoneNumber) {
try {
// 生成6位隨機驗證碼
String captcha = generateCaptcha(6);
// 存儲驗證碼
redisTemplate.opsForValue().set(
SMS_PREFIX + phoneNumber,
captcha,
SMS_EXPIRATION,
TimeUnit.SECONDS);
// 構建短信客戶端
DefaultProfile profile = DefaultProfile.getProfile(
"cn-hangzhou",
smsProperties.getAccessKeyId(),
smsProperties.getAccessKeySecret());
IAcsClient client = new DefaultAcsClient(profile);
// 構建短信請求
CommonRequest request = new CommonRequest();
request.setSysMethod(MethodType.POST);
request.setSysDomain(smsProperties.getEndpoint());
request.setSysVersion("2017-05-25");
request.setSysAction("SendSms");
request.putQueryParameter("RegionId", "cn-hangzhou");
request.putQueryParameter("PhoneNumbers", phoneNumber);
request.putQueryParameter("SignName", smsProperties.getSignName());
request.putQueryParameter("TemplateCode", smsProperties.getTemplateCode());
// 設置模板參數,將驗證碼作為參數傳入
Map<String, String> templateParam = Map.of("code", captcha);
request.putQueryParameter("TemplateParam", new ObjectMapper().writeValueAsString(templateParam));
// 發(fā)送短信
CommonResponse response = client.getCommonResponse(request);
log.info("短信發(fā)送結果: {}", response.getData());
// 解析響應
JsonNode responseJson = new ObjectMapper().readTree(response.getData());
return "OK".equals(responseJson.get("Code").asText());
} catch (Exception e) {
log.error("發(fā)送短信驗證碼失敗", e);
return false;
}
}
/**
* 驗證短信驗證碼
* @param phoneNumber 手機號
* @param captcha 用戶輸入的驗證碼
* @return 是否驗證成功
*/
public boolean validateSmsCaptcha(String phoneNumber, String captcha) {
String key = SMS_PREFIX + phoneNumber;
String storedCaptcha = redisTemplate.opsForValue().get(key);
if (storedCaptcha != null && storedCaptcha.equals(captcha)) {
// 驗證成功后刪除驗證碼,防止重復使用
redisTemplate.delete(key);
return true;
}
return false;
}
/**
* 生成指定長度的隨機數字驗證碼
*/
private String generateCaptcha(int length) {
StringBuilder captcha = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
captcha.append(random.nextInt(10));
}
return captcha.toString();
}
}4. 實現短信驗證碼控制器
@RestController
@RequestMapping("/captcha")
public class SmsCaptchaController {
@Autowired
private SmsService smsService;
@PostMapping("/sms/send")
public ResponseEntity<?> sendSmsCaptcha(@RequestBody PhoneNumberRequest request) {
String phoneNumber = request.getPhoneNumber();
// 驗證手機號格式
if (!isValidPhoneNumber(phoneNumber)) {
return ResponseEntity.badRequest().body(new ApiResponse(false, "手機號格式不正確"));
}
// 發(fā)送短信驗證碼
boolean sent = smsService.sendSmsCaptcha(phoneNumber);
if (sent) {
return ResponseEntity.ok(new ApiResponse(true, "驗證碼已發(fā)送,請注意查收"));
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiResponse(false, "驗證碼發(fā)送失敗,請稍后再試"));
}
}
// 驗證手機號格式(簡化版)
private boolean isValidPhoneNumber(String phoneNumber) {
return phoneNumber != null && phoneNumber.matches("^1[3-9]\d{9}$");
}
}5. 登錄控制器集成短信驗證碼
@RestController
@RequestMapping("/auth")
public class SmsLoginController {
@Autowired
private SmsService smsService;
@Autowired
private UserService userService;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@PostMapping("/sms-login")
public ResponseEntity<?> smsLogin(@RequestBody SmsLoginRequest request) {
String phoneNumber = request.getPhoneNumber();
String captcha = request.getCaptcha();
// 驗證短信驗證碼
if (!smsService.validateSmsCaptcha(phoneNumber, captcha)) {
return ResponseEntity.badRequest().body(new ApiResponse(false, "驗證碼錯誤或已過期"));
}
// 查找或創(chuàng)建用戶
User user = userService.findOrCreateByPhone(phoneNumber);
if (user != null) {
// 生成JWT令牌
String token = jwtTokenProvider.generateToken(user.getUsername());
return ResponseEntity.ok(new JwtAuthResponse(token));
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiResponse(false, "登錄失敗,請稍后再試"));
}
}
}6. 前端集成示例
<div class="sms-login-form">
<form id="smsLoginForm">
<div class="form-group">
<label for="phoneNumber">手機號</label>
<input type="text" class="form-control" id="phoneNumber" name="phoneNumber" required>
</div>
<div class="form-group">
<label for="smsCaptcha">驗證碼</label>
<div class="captcha-container">
<input type="text" class="form-control" id="smsCaptcha" name="smsCaptcha" required>
<button type="button" class="btn btn-outline-primary" id="sendSmsBtn">獲取驗證碼</button>
</div>
</div>
<button type="submit" class="btn btn-primary">登錄</button>
</form>
</div>
<script>
let countdown = 60;
document.getElementById('sendSmsBtn').addEventListener('click', function() {
const phoneNumber = document.getElementById('phoneNumber').value;
if (!phoneNumber || !/^1[3-9]\d{9}$/.test(phoneNumber)) {
alert('請輸入正確的手機號');
return;
}
// 發(fā)送短信驗證碼請求
fetch('/captcha/sms/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ phoneNumber: phoneNumber })
})
.then(response => response.json())
.then(data => {
alert(data.message);
if (data.success) {
startCountdown();
}
})
.catch(error => {
console.error('Error:', error);
alert('發(fā)送驗證碼失敗,請稍后再試');
});
});
function startCountdown() {
const btn = document.getElementById('sendSmsBtn');
btn.disabled = true;
let timer = setInterval(() => {
btn.textContent = `${countdown}秒后重新獲取`;
countdown--;
if (countdown < 0) {
clearInterval(timer);
btn.disabled = false;
btn.textContent = '獲取驗證碼';
countdown = 60;
}
}, 1000);
}
document.getElementById('smsLoginForm').addEventListener('submit', function(e) {
e.preventDefault();
const data = {
phoneNumber: document.getElementById('phoneNumber').value,
captcha: document.getElementById('smsCaptcha').value
};
fetch('/auth/sms-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token);
window.location.href = '/dashboard';
} else {
alert(data.message);
}
})
.catch(error => {
console.error('Error:', error);
alert('登錄失敗,請稍后再試');
});
});
</script>優(yōu)缺點分析
優(yōu)點
- 安全性高,驗證了用戶對手機的控制權
- 可以作為無密碼登錄的方式,簡化用戶操作
- 適合移動應用和需要高安全性的場景
- 可與其他驗證方式結合,實現多因素認證
缺點
- 依賴第三方短信服務,存在成本
- 可能受網絡和運營商影響,導致延遲
- 對國際用戶不友好
- 用戶頻繁登錄時,體驗不佳
方案三:基于Spring Mail的郵箱驗證碼
原理介紹
郵箱驗證碼通過向用戶注冊的電子郵箱發(fā)送一次性驗證碼實現身份驗證。這種方式與短信驗證碼類似,但使用電子郵件作為傳遞媒介,適合對成本敏感或不需要實時驗證的場景。
實現步驟
1. 添加Spring Mail依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>2. 配置郵件服務
# application.properties spring.mail.host=smtp.gmail.com spring.mail.port=587 spring.mail.username=your-email@gmail.com spring.mail.password=your-app-password spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true # 自定義配置 app.email.from=noreply@yourapp.com app.email.personal=Your App Name
3. 創(chuàng)建郵件驗證碼服務
@Service
@Slf4j
public class EmailCaptchaService {
@Autowired
private JavaMailSender mailSender;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private TemplateEngine templateEngine;
@Value("${app.email.from}")
private String fromEmail;
@Value("${app.email.personal}")
private String emailPersonal;
private static final long EMAIL_CAPTCHA_EXPIRATION = 600; // 10分鐘過期
private static final String EMAIL_CAPTCHA_PREFIX = "EMAIL_CAPTCHA:";
/**
* 發(fā)送郵箱驗證碼
* @param email 電子郵箱
* @return 是否發(fā)送成功
*/
public boolean sendEmailCaptcha(String email) {
try {
// 生成6位隨機驗證碼
String captcha = generateCaptcha(6);
// 存儲驗證碼
redisTemplate.opsForValue().set(
EMAIL_CAPTCHA_PREFIX + email,
captcha,
EMAIL_CAPTCHA_EXPIRATION,
TimeUnit.SECONDS);
// 準備郵件內容
Context context = new Context();
context.setVariable("captcha", captcha);
context.setVariable("expirationMinutes", EMAIL_CAPTCHA_EXPIRATION / 60);
String emailContent = templateEngine.process("email/captcha-template", context);
// 創(chuàng)建MIME郵件
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
// 設置發(fā)件人、收件人、主題和內容
helper.setFrom(new InternetAddress(fromEmail, emailPersonal));
helper.setTo(email);
helper.setSubject("登錄驗證碼");
helper.setText(emailContent, true);
// 發(fā)送郵件
mailSender.send(message);
log.info("郵箱驗證碼已發(fā)送至: {}", email);
return true;
} catch (Exception e) {
log.error("發(fā)送郵箱驗證碼失敗", e);
return false;
}
}
/**
* 驗證郵箱驗證碼
* @param email 電子郵箱
* @param captcha 用戶輸入的驗證碼
* @return 是否驗證成功
*/
public boolean validateEmailCaptcha(String email, String captcha) {
String key = EMAIL_CAPTCHA_PREFIX + email;
String storedCaptcha = redisTemplate.opsForValue().get(key);
if (storedCaptcha != null && storedCaptcha.equals(captcha)) {
// 驗證成功后刪除驗證碼,防止重復使用
redisTemplate.delete(key);
return true;
}
return false;
}
/**
* 生成指定長度的隨機數字驗證碼
*/
private String generateCaptcha(int length) {
StringBuilder captcha = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
captcha.append(random.nextInt(10));
}
return captcha.toString();
}
}4. 創(chuàng)建郵件模板
<!-- src/main/resources/templates/email/captcha-template.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登錄驗證碼</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.header {
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.content {
padding: 20px 0;
}
.code {
font-size: 24px;
font-weight: bold;
text-align: center;
padding: 10px;
margin: 20px 0;
background-color: #f5f5f5;
border-radius: 5px;
letter-spacing: 5px;
}
.footer {
font-size: 12px;
color: #777;
text-align: center;
padding-top: 10px;
border-top: 1px solid #eee;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>登錄驗證碼</h2>
</div>
<div class="content">
<p>您好,</p>
<p>您正在進行登錄操作,請使用以下驗證碼完成驗證:</p>
<div class="code" th:text="${captcha}">123456</div>
<p>驗證碼有效期為 <span th:text="${expirationMinutes}">10</span> 分鐘,請及時使用。</p>
<p>如果這不是您的操作,請忽略此郵件。</p>
</div>
<div class="footer">
<p>此郵件由系統自動發(fā)送,請勿回復。</p>
</div>
</div>
</body>
</html>5. 實現郵箱驗證碼控制器
@RestController
@RequestMapping("/captcha")
public class EmailCaptchaController {
@Autowired
private EmailCaptchaService emailCaptchaService;
@PostMapping("/email/send")
public ResponseEntity<?> sendEmailCaptcha(@RequestBody EmailRequest request) {
String email = request.getEmail();
// 驗證郵箱格式
if (!isValidEmail(email)) {
return ResponseEntity.badRequest().body(new ApiResponse(false, "郵箱格式不正確"));
}
// 發(fā)送郵箱驗證碼
boolean sent = emailCaptchaService.sendEmailCaptcha(email);
if (sent) {
return ResponseEntity.ok(new ApiResponse(true, "驗證碼已發(fā)送,請查收郵件"));
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiResponse(false, "驗證碼發(fā)送失敗,請稍后再試"));
}
}
// 驗證郵箱格式
private boolean isValidEmail(String email) {
String regex = "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$";
return email != null && email.matches(regex);
}
}6. 登錄控制器集成郵箱驗證碼
@RestController
@RequestMapping("/auth")
public class EmailLoginController {
@Autowired
private EmailCaptchaService emailCaptchaService;
@Autowired
private UserService userService;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@PostMapping("/email-login")
public ResponseEntity<?> emailLogin(@RequestBody EmailLoginRequest request) {
String email = request.getEmail();
String captcha = request.getCaptcha();
// 驗證郵箱驗證碼
if (!emailCaptchaService.validateEmailCaptcha(email, captcha)) {
return ResponseEntity.badRequest().body(new ApiResponse(false, "驗證碼錯誤或已過期"));
}
// 查找或創(chuàng)建用戶
User user = userService.findOrCreateByEmail(email);
if (user != null) {
// 生成JWT令牌
String token = jwtTokenProvider.generateToken(user.getUsername());
return ResponseEntity.ok(new JwtAuthResponse(token));
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiResponse(false, "登錄失敗,請稍后再試"));
}
}
}優(yōu)缺點分析
優(yōu)點
- 成本低廉,適合預算有限的項目
- 無需依賴第三方服務,可自行部署
- 可發(fā)送富文本內容,支持品牌定制
- 適合注冊和找回密碼等非實時場景
缺點
- 郵件可能被歸類為垃圾郵件或延遲送達
- 用戶體驗不如短信驗證碼直接
- 郵件服務器配置相對復雜
- 不適合對實時性要求高的場景
方案四:基于AJ-Captcha的滑動拼圖驗證碼
原理介紹
滑動拼圖驗證碼是一種更現代的驗證方式,通過讓用戶拖動滑塊完成拼圖來驗證人機交互。這種驗證碼在用戶體驗和安全性之間取得了很好的平衡,既能有效防止自動化攻擊,又不會像傳統圖形驗證碼那樣影響用戶體驗。
實現步驟
1. 添加AJ-Captcha依賴
<dependency> <groupId>com.anji-plus</groupId> <artifactId>captcha-spring-boot-starter</artifactId> <version>1.4.0</version> </dependency>
2. 配置滑動驗證碼參數
aj:
captcha:
cache-type: local
expires-in: 300
req-frequency-limit-count: 50
cache-number: 1000
jigsaw: classpath:images/jigsaw
pic-click: classpath:images/pic-click3. 自定義滑動驗證碼控制器
@RestController
@RequestMapping("/captcha")
public class JigsawCaptchaController {
@Autowired
private CaptchaService captchaService;
@RequestMapping(value = "/get", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseModel get(@RequestParam(value = "type", defaultValue = "slide") String captchaType) {
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setCaptchaType(captchaType);
return captchaService.get(captchaVO);
}
@PostMapping("/check")
public ResponseModel check(@RequestBody CaptchaVO captchaVO) {
return captchaService.check(captchaVO);
}
}4. 登錄控制器集成滑動驗證碼
@RestController
@RequestMapping("/auth")
public class JigsawLoginController {
@Autowired
private CaptchaService captchaService;
@PostMapping("/jigsaw-login")
public ResponseEntity<?> jigsawLogin(@RequestBody JigsawLoginRequest request) {
// 驗證滑動驗證碼
CaptchaVO captchaVO = new CaptchaVO();
captchaVO.setCaptchaVerification(request.getCaptchaVerification());
ResponseModel response = captchaService.verification(captchaVO);
if (!response.isSuccess()) {
return ResponseEntity.badRequest().body(new ApiResponse(false, "驗證碼校驗失敗"));
}
// TODO 模擬驗證用戶名密碼
boolean authenticated = request.getPassword().equals("admin");
if (authenticated) {
// TODO 模擬生成令牌
String token = IdUtil.simpleUUID();
return ResponseEntity.ok(new JwtAuthResponse(token));
} else {
return ResponseEntity.badRequest().body(new ApiResponse(false, "用戶名或密碼錯誤"));
}
}
}@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public CaptchaService captchaService(){
BlockPuzzleCaptchaServiceImpl clickWordCaptchaService = new BlockPuzzleCaptchaServiceImpl();
Properties properties = new Properties();
properties.setProperty(Const.CAPTCHA_FONT_TYPE,"WenQuanZhengHei.ttf");
clickWordCaptchaService.init(properties);
return clickWordCaptchaService;
}
}5. 前端集成示例
注:依賴的本地js文件可從 https://github.com/anji-plus/captcha/tree/master/view/html 獲取
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>滑動驗證碼登錄</title>
<link rel="stylesheet" >
<link rel="stylesheet" >
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<link rel="stylesheet" type="text/css" href="css/verify.css">
<style>
</style>
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">用戶登錄</div>
<div class="card-body">
<form id="loginForm">
<div class="form-group">
<label for="username">用戶名</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密碼</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<div class="form-group">
<div id="captcha"></div>
<input type="hidden" id="captchaVerification" name="captchaVerification">
</div>
<div id="slidePanel" ></div>
<button type="submit" class="btn btn-primary btn-block">登錄</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script>
(function () {
if (!window.Promise) {
document.writeln('<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.1.1/es6-promise.min.js"><' + '/' + 'script>');
}
})();
</script>
<script src="./js/crypto-js.js"></script>
<script src="./js/ase.js"></script>
<script src="./js/verify.js" ></script>
<script>
let captchaVerification;
// 初始化驗證碼 嵌入式
$('#slidePanel').slideVerify({
baseUrl:'http://localhost:8080', // 服務器請求地址;
mode:'fixed',
imgSize : { //圖片的大小對象
width: '400px',
height: '200px',
},
barSize:{
width: '400px',
height: '40px',
},
ready : function() { //加載完畢的回調
},
success : function(params) { //成功的回調
// 返回的二次驗證參數 合并到驗證通過之后的邏輯 參數中回傳服務器
captchaVerification = params.captchaVerification;
},
error : function() { //失敗的回調
}
});
// 表單提交處理
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
if (!captchaVerification) {
alert('請先完成滑動驗證');
return;
}
var data = {
username: document.getElementById('username').value,
password: document.getElementById('password').value,
captchaVerification: captchaVerification
};
fetch('/auth/jigsaw-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.token) {
localStorage.setItem('token', data.token);
alert('login success');
} else {
alert(data.message);
}
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
</body>
</html>
優(yōu)缺點分析
優(yōu)點
- 用戶體驗良好,交互更加自然
- 安全性較高,難以被自動化工具破解
- 支持移動端和桌面端
- 可定制化程度高,支持品牌化設計
缺點
- 實現相對復雜,需要前后端配合
- 依賴JavaScript,不支持純靜態(tài)環(huán)境
四種驗證碼方案對比
| 特性/方案 | 圖形驗證碼 | 短信驗證碼 | 郵箱驗證碼 | 滑動拼圖驗證碼 |
|---|---|---|---|---|
| 安全性 | 中 | 高 | 中高 | 高 |
| 實現復雜度 | 低 | 中 | 中 | 高 |
| 適用場景 | 簡單應用 | 高安全性需求 | 注冊、找回密碼 | 現代Web應用 |
| 防機器人效果 | 一般 | 優(yōu)秀 | 良好 | 優(yōu)秀 |
| 是否需要第三方 | 否 | 是 | 否 | 否 |
總結
在實際項目中,可以根據應用特點、用戶需求和安全要求選擇合適的驗證碼方案,甚至可以組合多種方案,在不同場景下使用不同的驗證方式,既保障系統安全,又提供良好的用戶體驗。
隨著AI技術的發(fā)展,驗證碼技術也在不斷演進。對于重要系統,建議定期評估和更新驗證碼方案,以應對新型的自動化攻擊手段。
到此這篇關于SpringBoot中登錄驗證碼的4種實現方案的文章就介紹到這了,更多相關springboot登錄驗證碼內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Netty進階之EventExecutorGroup源碼詳解
這篇文章主要介紹了Netty進階之EventExecutorGroup源碼詳解,EventExecutorGroup繼承了JDK的ScheduledExecutroService,那么它就擁有了執(zhí)行定時任務,執(zhí)行提交的普通任務,需要的朋友可以參考下2023-11-11
Java使用RandomAccessFile類對文件進行讀寫
本篇文章主要介紹了Java使用RandomAccessFile類對文件進行讀寫,詳細的介紹了RandomAccessFile類的使用技巧和實例應用,有興趣的可以了解一下2017-04-04

