欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot中登錄驗(yàn)證碼的4種實(shí)現(xiàn)方案

 更新時(shí)間:2025年04月29日 08:27:15   作者:風(fēng)象南  
這篇文章主要介紹了在SpringBoot應(yīng)用中實(shí)現(xiàn)四種登錄驗(yàn)證碼的技術(shù)方案,包括圖形驗(yàn)證碼、短信驗(yàn)證碼、郵箱驗(yàn)證碼和滑動(dòng)拼圖驗(yàn)證碼,需要的可以參考下

在當(dāng)今互聯(lián)網(wǎng)安全形勢日益嚴(yán)峻的環(huán)境下,驗(yàn)證碼已成為保護(hù)用戶賬戶安全、防止破解和自動(dòng)化攻擊的重要手段。尤其在登錄系統(tǒng)中,合理使用驗(yàn)證碼不僅能有效阻止機(jī)器人批量嘗試賬號(hào)密碼,還能降低賬戶被盜風(fēng)險(xiǎn),提升系統(tǒng)安全性。

本文將詳細(xì)介紹在SpringBoot應(yīng)用中實(shí)現(xiàn)四種登錄驗(yàn)證碼的技術(shù)方案,包括圖形驗(yàn)證碼、短信驗(yàn)證碼、郵箱驗(yàn)證碼和滑動(dòng)拼圖驗(yàn)證碼。

方案一:基于Kaptcha的圖形驗(yàn)證碼

原理介紹

圖形驗(yàn)證碼是最傳統(tǒng)且應(yīng)用最廣泛的驗(yàn)證碼類型,原理是在服務(wù)端生成隨機(jī)字符串并渲染成圖片,用戶需要識(shí)別圖片中的字符并輸入。圖形驗(yàn)證碼實(shí)現(xiàn)簡單,對用戶體驗(yàn)影響較小,是中小型應(yīng)用的理想選擇。

實(shí)現(xiàn)步驟

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");
        // 文本集合,驗(yàn)證碼值從此集合中獲取
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
        // 驗(yàn)證碼長度
        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)建驗(yàn)證碼存儲(chǔ)服務(wù)

@Service
public class CaptchaService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 驗(yàn)證碼有效期(秒)
    private static final long CAPTCHA_EXPIRATION = 300;
    
    // 存儲(chǔ)驗(yàn)證碼
    public void storeCaptcha(String sessionId, String captchaCode) {
        redisTemplate.opsForValue().set(
                "CAPTCHA:" + sessionId, 
                captchaCode, 
                CAPTCHA_EXPIRATION, 
                TimeUnit.SECONDS);
    }
    
    // 驗(yàn)證驗(yàn)證碼
    public boolean validateCaptcha(String sessionId, String userInputCaptcha) {
        String key = "CAPTCHA:" + sessionId;
        String storedCaptcha = redisTemplate.opsForValue().get(key);
        
        if (storedCaptcha != null && storedCaptcha.equalsIgnoreCase(userInputCaptcha)) {
            // 驗(yàn)證成功后立即刪除,防止重復(fù)使用
            redisTemplate.delete(key);
            return true;
        }
        
        return false;
    }
}

4. 實(shí)現(xiàn)驗(yàn)證碼生成控制器

@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)建驗(yàn)證碼文本
        String capText = kaptchaProducer.createText();
        
        // 獲取會(huì)話ID
        String sessionId = request.getSession().getId();
        
        // 存儲(chǔ)驗(yàn)證碼
        captchaService.storeCaptcha(sessionId, capText);
        
        // 創(chuàng)建驗(yàn)證碼圖片
        BufferedImage image = kaptchaProducer.createImage(capText);
        ServletOutputStream out = response.getOutputStream();
        
        // 輸出圖片
        ImageIO.write(image, "jpg", out);
        out.flush();
        out.close();
    }
}

5. 登錄控制器集成驗(yàn)證碼校驗(yàn)

@RestController
@RequestMapping("/auth")
public class AuthController {
    
    @Autowired
    private CaptchaService captchaService;
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
        // 獲取會(huì)話ID
        String sessionId = request.getSession().getId();
        
        // 驗(yàn)證驗(yàn)證碼
        if (!captchaService.validateCaptcha(sessionId, loginRequest.getCaptcha())) {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "驗(yàn)證碼錯(cuò)誤或已過期"));
        }
        
        // 用戶名密碼驗(yàn)證
        boolean authenticated = userService.authenticate(
                loginRequest.getUsername(), 
                loginRequest.getPassword());
        
        if (authenticated) {
            // 生成Token或設(shè)置會(huì)話
            String token = jwtTokenProvider.generateToken(loginRequest.getUsername());
            return ResponseEntity.ok(new JwtAuthResponse(token));
        } else {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "用戶名或密碼錯(cuò)誤"));
        }
    }
}

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">驗(yàn)證碼</label>
            <div class="captcha-container">
                <input type="text" class="form-control" id="captcha" name="captcha" required>
                <img id="captchaImg" src="/captcha/image" alt="驗(yàn)證碼" 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)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)簡單,開發(fā)成本低
  • 對服務(wù)器資源占用少
  • 用戶體驗(yàn)相對友好
  • 無需第三方服務(wù),降低依賴

缺點(diǎn)

  • 安全性較低,容易被OCR識(shí)別破解
  • 對視障用戶不友好
  • 在移動(dòng)設(shè)備上體驗(yàn)不佳
  • 隨著AI發(fā)展,圖形驗(yàn)證碼的安全性逐漸降低

方案二:基于短信驗(yàn)證碼

原理介紹

短信驗(yàn)證碼通過向用戶手機(jī)發(fā)送一次性驗(yàn)證碼實(shí)現(xiàn)身份驗(yàn)證。用戶需要輸入收到的驗(yàn)證碼完成登錄過程。這種方式不僅驗(yàn)證了賬號(hào)密碼的正確性,還確認(rèn)了用戶對手機(jī)號(hào)的控制權(quán),大幅提高了安全性。

實(shí)現(xiàn)步驟

1. 添加阿里云SDK依賴

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.5.3</version>
</dependency>

2. 配置短信服務(wù)參數(shù)

@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. 實(shí)現(xiàn)短信服務(wù)

@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ā)送短信驗(yàn)證碼
     * @param phoneNumber 手機(jī)號(hào)
     * @return 是否發(fā)送成功
     */
    public boolean sendSmsCaptcha(String phoneNumber) {
        try {
            // 生成6位隨機(jī)驗(yàn)證碼
            String captcha = generateCaptcha(6);
            
            // 存儲(chǔ)驗(yàn)證碼
            redisTemplate.opsForValue().set(
                    SMS_PREFIX + phoneNumber, 
                    captcha, 
                    SMS_EXPIRATION, 
                    TimeUnit.SECONDS);
            
            // 構(gòu)建短信客戶端
            DefaultProfile profile = DefaultProfile.getProfile(
                    "cn-hangzhou", 
                    smsProperties.getAccessKeyId(), 
                    smsProperties.getAccessKeySecret());
            IAcsClient client = new DefaultAcsClient(profile);
            
            // 構(gòu)建短信請求
            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());
            
            // 設(shè)置模板參數(shù),將驗(yàn)證碼作為參數(shù)傳入
            Map<String, String> templateParam = Map.of("code", captcha);
            request.putQueryParameter("TemplateParam", new ObjectMapper().writeValueAsString(templateParam));
            
            // 發(fā)送短信
            CommonResponse response = client.getCommonResponse(request);
            log.info("短信發(fā)送結(jié)果: {}", response.getData());
            
            // 解析響應(yīng)
            JsonNode responseJson = new ObjectMapper().readTree(response.getData());
            return "OK".equals(responseJson.get("Code").asText());
            
        } catch (Exception e) {
            log.error("發(fā)送短信驗(yàn)證碼失敗", e);
            return false;
        }
    }
    
    /**
     * 驗(yàn)證短信驗(yàn)證碼
     * @param phoneNumber 手機(jī)號(hào)
     * @param captcha 用戶輸入的驗(yàn)證碼
     * @return 是否驗(yàn)證成功
     */
    public boolean validateSmsCaptcha(String phoneNumber, String captcha) {
        String key = SMS_PREFIX + phoneNumber;
        String storedCaptcha = redisTemplate.opsForValue().get(key);
        
        if (storedCaptcha != null && storedCaptcha.equals(captcha)) {
            // 驗(yàn)證成功后刪除驗(yàn)證碼,防止重復(fù)使用
            redisTemplate.delete(key);
            return true;
        }
        
        return false;
    }
    
    /**
     * 生成指定長度的隨機(jī)數(shù)字驗(yàn)證碼
     */
    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. 實(shí)現(xiàn)短信驗(yàn)證碼控制器

@RestController
@RequestMapping("/captcha")
public class SmsCaptchaController {
    
    @Autowired
    private SmsService smsService;
    
    @PostMapping("/sms/send")
    public ResponseEntity<?> sendSmsCaptcha(@RequestBody PhoneNumberRequest request) {
        String phoneNumber = request.getPhoneNumber();
        
        // 驗(yàn)證手機(jī)號(hào)格式
        if (!isValidPhoneNumber(phoneNumber)) {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "手機(jī)號(hào)格式不正確"));
        }
        
        // 發(fā)送短信驗(yàn)證碼
        boolean sent = smsService.sendSmsCaptcha(phoneNumber);
        
        if (sent) {
            return ResponseEntity.ok(new ApiResponse(true, "驗(yàn)證碼已發(fā)送,請注意查收"));
        } else {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(new ApiResponse(false, "驗(yàn)證碼發(fā)送失敗,請稍后再試"));
        }
    }
    
    // 驗(yàn)證手機(jī)號(hào)格式(簡化版)
    private boolean isValidPhoneNumber(String phoneNumber) {
        return phoneNumber != null && phoneNumber.matches("^1[3-9]\d{9}$");
    }
}

5. 登錄控制器集成短信驗(yàn)證碼

@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();
        
        // 驗(yàn)證短信驗(yàn)證碼
        if (!smsService.validateSmsCaptcha(phoneNumber, captcha)) {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "驗(yàn)證碼錯(cuò)誤或已過期"));
        }
        
        // 查找或創(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">手機(jī)號(hào)</label>
            <input type="text" class="form-control" id="phoneNumber" name="phoneNumber" required>
        </div>
        <div class="form-group">
            <label for="smsCaptcha">驗(yàn)證碼</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">獲取驗(yàn)證碼</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('請輸入正確的手機(jī)號(hào)');
        return;
    }
    
    // 發(fā)送短信驗(yàn)證碼請求
    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ā)送驗(yàn)證碼失敗,請稍后再試');
    });
});

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 = '獲取驗(yàn)證碼';
            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)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 安全性高,驗(yàn)證了用戶對手機(jī)的控制權(quán)
  • 可以作為無密碼登錄的方式,簡化用戶操作
  • 適合移動(dòng)應(yīng)用和需要高安全性的場景
  • 可與其他驗(yàn)證方式結(jié)合,實(shí)現(xiàn)多因素認(rèn)證

缺點(diǎn)

  • 依賴第三方短信服務(wù),存在成本
  • 可能受網(wǎng)絡(luò)和運(yùn)營商影響,導(dǎo)致延遲
  • 對國際用戶不友好
  • 用戶頻繁登錄時(shí),體驗(yàn)不佳

方案三:基于Spring Mail的郵箱驗(yàn)證碼

原理介紹

郵箱驗(yàn)證碼通過向用戶注冊的電子郵箱發(fā)送一次性驗(yàn)證碼實(shí)現(xiàn)身份驗(yàn)證。這種方式與短信驗(yàn)證碼類似,但使用電子郵件作為傳遞媒介,適合對成本敏感或不需要實(shí)時(shí)驗(yàn)證的場景。

實(shí)現(xiàn)步驟

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. 配置郵件服務(wù)

# 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)建郵件驗(yàn)證碼服務(wù)

@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ā)送郵箱驗(yàn)證碼
     * @param email 電子郵箱
     * @return 是否發(fā)送成功
     */
    public boolean sendEmailCaptcha(String email) {
        try {
            // 生成6位隨機(jī)驗(yàn)證碼
            String captcha = generateCaptcha(6);
            
            // 存儲(chǔ)驗(yàn)證碼
            redisTemplate.opsForValue().set(
                    EMAIL_CAPTCHA_PREFIX + email, 
                    captcha, 
                    EMAIL_CAPTCHA_EXPIRATION, 
                    TimeUnit.SECONDS);
            
            // 準(zhǔn)備郵件內(nèi)容
            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");
            
            // 設(shè)置發(fā)件人、收件人、主題和內(nèi)容
            helper.setFrom(new InternetAddress(fromEmail, emailPersonal));
            helper.setTo(email);
            helper.setSubject("登錄驗(yàn)證碼");
            helper.setText(emailContent, true);
            
            // 發(fā)送郵件
            mailSender.send(message);
            log.info("郵箱驗(yàn)證碼已發(fā)送至: {}", email);
            
            return true;
        } catch (Exception e) {
            log.error("發(fā)送郵箱驗(yàn)證碼失敗", e);
            return false;
        }
    }
    
    /**
     * 驗(yàn)證郵箱驗(yàn)證碼
     * @param email 電子郵箱
     * @param captcha 用戶輸入的驗(yàn)證碼
     * @return 是否驗(yàn)證成功
     */
    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)) {
            // 驗(yàn)證成功后刪除驗(yàn)證碼,防止重復(fù)使用
            redisTemplate.delete(key);
            return true;
        }
        
        return false;
    }
    
    /**
     * 生成指定長度的隨機(jī)數(shù)字驗(yàn)證碼
     */
    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>登錄驗(yàn)證碼</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>登錄驗(yàn)證碼</h2>
        </div>
        <div class="content">
            <p>您好,</p>
            <p>您正在進(jìn)行登錄操作,請使用以下驗(yàn)證碼完成驗(yàn)證:</p>
            <div class="code" th:text="${captcha}">123456</div>
            <p>驗(yàn)證碼有效期為 <span th:text="${expirationMinutes}">10</span> 分鐘,請及時(shí)使用。</p>
            <p>如果這不是您的操作,請忽略此郵件。</p>
        </div>
        <div class="footer">
            <p>此郵件由系統(tǒng)自動(dòng)發(fā)送,請勿回復(fù)。</p>
        </div>
    </div>
</body>
</html>

5. 實(shí)現(xiàn)郵箱驗(yàn)證碼控制器

@RestController
@RequestMapping("/captcha")
public class EmailCaptchaController {
    
    @Autowired
    private EmailCaptchaService emailCaptchaService;
    
    @PostMapping("/email/send")
    public ResponseEntity<?> sendEmailCaptcha(@RequestBody EmailRequest request) {
        String email = request.getEmail();
        
        // 驗(yàn)證郵箱格式
        if (!isValidEmail(email)) {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "郵箱格式不正確"));
        }
        
        // 發(fā)送郵箱驗(yàn)證碼
        boolean sent = emailCaptchaService.sendEmailCaptcha(email);
        
        if (sent) {
            return ResponseEntity.ok(new ApiResponse(true, "驗(yàn)證碼已發(fā)送,請查收郵件"));
        } else {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(new ApiResponse(false, "驗(yàn)證碼發(fā)送失敗,請稍后再試"));
        }
    }
    
    // 驗(yàn)證郵箱格式
    private boolean isValidEmail(String email) {
        String regex = "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$";
        return email != null && email.matches(regex);
    }
}

6. 登錄控制器集成郵箱驗(yàn)證碼

@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();
        
        // 驗(yàn)證郵箱驗(yàn)證碼
        if (!emailCaptchaService.validateEmailCaptcha(email, captcha)) {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "驗(yàn)證碼錯(cuò)誤或已過期"));
        }
        
        // 查找或創(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)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 成本低廉,適合預(yù)算有限的項(xiàng)目
  • 無需依賴第三方服務(wù),可自行部署
  • 可發(fā)送富文本內(nèi)容,支持品牌定制
  • 適合注冊和找回密碼等非實(shí)時(shí)場景

缺點(diǎn)

  • 郵件可能被歸類為垃圾郵件或延遲送達(dá)
  • 用戶體驗(yàn)不如短信驗(yàn)證碼直接
  • 郵件服務(wù)器配置相對復(fù)雜
  • 不適合對實(shí)時(shí)性要求高的場景

方案四:基于AJ-Captcha的滑動(dòng)拼圖驗(yàn)證碼

原理介紹

滑動(dòng)拼圖驗(yàn)證碼是一種更現(xiàn)代的驗(yàn)證方式,通過讓用戶拖動(dòng)滑塊完成拼圖來驗(yàn)證人機(jī)交互。這種驗(yàn)證碼在用戶體驗(yàn)和安全性之間取得了很好的平衡,既能有效防止自動(dòng)化攻擊,又不會(huì)像傳統(tǒng)圖形驗(yàn)證碼那樣影響用戶體驗(yàn)。

實(shí)現(xiàn)步驟

1. 添加AJ-Captcha依賴

<dependency>
   <groupId>com.anji-plus</groupId>
   <artifactId>captcha-spring-boot-starter</artifactId>
   <version>1.4.0</version>
</dependency>

2. 配置滑動(dòng)驗(yàn)證碼參數(shù)

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-click

3. 自定義滑動(dòng)驗(yàn)證碼控制器

@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. 登錄控制器集成滑動(dòng)驗(yàn)證碼

@RestController
@RequestMapping("/auth")
public class JigsawLoginController {
    
    @Autowired
    private CaptchaService captchaService;

    @PostMapping("/jigsaw-login")
    public ResponseEntity<?> jigsawLogin(@RequestBody JigsawLoginRequest request) {
        // 驗(yàn)證滑動(dòng)驗(yàn)證碼
        CaptchaVO captchaVO = new CaptchaVO();
        captchaVO.setCaptchaVerification(request.getCaptchaVerification());
        
        ResponseModel response = captchaService.verification(captchaVO);
        
        if (!response.isSuccess()) {
            return ResponseEntity.badRequest().body(new ApiResponse(false, "驗(yàn)證碼校驗(yàn)失敗"));
        }
        
        // TODO 模擬驗(yàn)證用戶名密碼
        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, "用戶名或密碼錯(cuò)誤"));
        }
    }
}
@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>滑動(dòng)驗(yàn)證碼登錄</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;
    // 初始化驗(yàn)證碼  嵌入式
    $('#slidePanel').slideVerify({
        baseUrl:'http://localhost:8080',  // 服務(wù)器請求地址;
        mode:'fixed',
        imgSize : {       //圖片的大小對象
            width: '400px',
            height: '200px',
        },
        barSize:{
            width: '400px',
            height: '40px',
        },
        ready : function() {  //加載完畢的回調(diào)
        },
        success : function(params) { //成功的回調(diào)
            // 返回的二次驗(yàn)證參數(shù) 合并到驗(yàn)證通過之后的邏輯 參數(shù)中回傳服務(wù)器
            captchaVerification = params.captchaVerification;
        },
        error : function() {        //失敗的回調(diào)
        }
    });

    // 表單提交處理
    document.getElementById('loginForm').addEventListener('submit', function(e) {
        e.preventDefault();

        if (!captchaVerification) {
            alert('請先完成滑動(dòng)驗(yàn)證');
            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)缺點(diǎn)分析

優(yōu)點(diǎn)

  • 用戶體驗(yàn)良好,交互更加自然
  • 安全性較高,難以被自動(dòng)化工具破解
  • 支持移動(dòng)端和桌面端
  • 可定制化程度高,支持品牌化設(shè)計(jì)

缺點(diǎn)

  • 實(shí)現(xiàn)相對復(fù)雜,需要前后端配合
  • 依賴JavaScript,不支持純靜態(tài)環(huán)境

四種驗(yàn)證碼方案對比

特性/方案圖形驗(yàn)證碼短信驗(yàn)證碼郵箱驗(yàn)證碼滑動(dòng)拼圖驗(yàn)證碼
安全性中高
實(shí)現(xiàn)復(fù)雜度
適用場景簡單應(yīng)用高安全性需求注冊、找回密碼現(xiàn)代Web應(yīng)用
防機(jī)器人效果一般優(yōu)秀良好優(yōu)秀
是否需要第三方

總結(jié)

在實(shí)際項(xiàng)目中,可以根據(jù)應(yīng)用特點(diǎn)、用戶需求和安全要求選擇合適的驗(yàn)證碼方案,甚至可以組合多種方案,在不同場景下使用不同的驗(yàn)證方式,既保障系統(tǒng)安全,又提供良好的用戶體驗(yàn)。

隨著AI技術(shù)的發(fā)展,驗(yàn)證碼技術(shù)也在不斷演進(jìn)。對于重要系統(tǒng),建議定期評估和更新驗(yàn)證碼方案,以應(yīng)對新型的自動(dòng)化攻擊手段。

到此這篇關(guān)于SpringBoot中登錄驗(yàn)證碼的4種實(shí)現(xiàn)方案的文章就介紹到這了,更多相關(guān)springboot登錄驗(yàn)證碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Netty進(jìn)階之EventExecutorGroup源碼詳解

    Netty進(jìn)階之EventExecutorGroup源碼詳解

    這篇文章主要介紹了Netty進(jìn)階之EventExecutorGroup源碼詳解,EventExecutorGroup繼承了JDK的ScheduledExecutroService,那么它就擁有了執(zhí)行定時(shí)任務(wù),執(zhí)行提交的普通任務(wù),需要的朋友可以參考下
    2023-11-11
  • Java使用Socket簡單通訊詳解

    Java使用Socket簡單通訊詳解

    這篇文章主要介紹了Java使用Socket簡單通訊詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • java中File與MultipartFile互轉(zhuǎn)代碼示例

    java中File與MultipartFile互轉(zhuǎn)代碼示例

    在Java開發(fā)中,當(dāng)需要將本地File對象轉(zhuǎn)換為MultipartFile對象以處理文件上傳時(shí),可以通過實(shí)現(xiàn)MultipartFile接口或使用CommonsMultipartFile類來實(shí)現(xiàn),本文提供了詳細(xì)的轉(zhuǎn)換方法和代碼示例,需要的朋友可以參考下
    2024-10-10
  • Java打印星號(hào)圖案和數(shù)字圖案的示例代碼

    Java打印星號(hào)圖案和數(shù)字圖案的示例代碼

    在 Java 中打印圖案是一項(xiàng)常見的編程任務(wù),尤其在初學(xué)階段,通過以特定方式排列符號(hào)或數(shù)字,可以形成各種設(shè)計(jì)或形狀,這些圖案不僅有助于解決問題,還能培養(yǎng)算法思維能力,本文將討論如何在 Java 中打印圖案,并探索一些最常見的圖案類型,需要的朋友可以參考下
    2024-11-11
  • shiro無狀態(tài)web集成的示例代碼

    shiro無狀態(tài)web集成的示例代碼

    本篇文章主要介紹了shiro無狀態(tài)web集成的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • java中out.print和out.write的方法

    java中out.print和out.write的方法

    本文用一個(gè)小例子說明java out.print和out.write的方法,大家參考使用吧
    2013-11-11
  • 基于SpringMVC入門案例及講解

    基于SpringMVC入門案例及講解

    這篇文章主要介紹了基于SpringMVC入門案例及講解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法示例

    java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法示例

    這篇文章主要介紹了java統(tǒng)計(jì)漢字字?jǐn)?shù)的方法,結(jié)合實(shí)例形式分析了java正則判定、字符串遍歷及統(tǒng)計(jì)相關(guān)操作技巧,需要的朋友可以參考下
    2017-05-05
  • SpringBoot應(yīng)用剛啟動(dòng)時(shí)服務(wù)報(bào)大量超時(shí)的問題及解決

    SpringBoot應(yīng)用剛啟動(dòng)時(shí)服務(wù)報(bào)大量超時(shí)的問題及解決

    在Java項(xiàng)目上線過程中,經(jīng)常遇到的超時(shí)問題主要是由于JVM的JIT編譯導(dǎo)致,JIT(Just-In-Time)編譯是Java虛擬機(jī)的一項(xiàng)技術(shù),用于提高Java應(yīng)用的性能,它通過將熱點(diǎn)代碼(頻繁執(zhí)行的部分)轉(zhuǎn)換成本地機(jī)器碼來優(yōu)化執(zhí)行效率
    2024-11-11
  • Java使用RandomAccessFile類對文件進(jìn)行讀寫

    Java使用RandomAccessFile類對文件進(jìn)行讀寫

    本篇文章主要介紹了Java使用RandomAccessFile類對文件進(jìn)行讀寫,詳細(xì)的介紹了RandomAccessFile類的使用技巧和實(shí)例應(yīng)用,有興趣的可以了解一下
    2017-04-04

最新評論