zhou-hao 4 лет назад
Родитель
Сommit
5222eca0ef

+ 11 - 0
jetlinks-manager/authentication-manager/pom.xml

@@ -19,6 +19,17 @@
             <artifactId>guava</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.whvcse</groupId>
+            <artifactId>easy-captcha</artifactId>
+            <version>1.6.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-redis</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.hswebframework.web</groupId>
             <artifactId>hsweb-system-authorization-default</artifactId>

+ 99 - 0
jetlinks-manager/authentication-manager/src/main/java/org/jetlinks/community/auth/captcha/CaptchaController.java

@@ -0,0 +1,99 @@
+package org.jetlinks.community.auth.captcha;
+
+import com.wf.captcha.SpecCaptcha;
+import com.wf.captcha.base.Captcha;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hswebframework.web.authorization.annotation.Authorize;
+import org.hswebframework.web.authorization.events.AuthorizationDecodeEvent;
+import org.hswebframework.web.exception.ValidationException;
+import org.springframework.context.event.EventListener;
+import org.springframework.data.redis.core.ReactiveRedisOperations;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+import java.util.UUID;
+
+@RestController
+@Authorize(ignore = true)
+@AllArgsConstructor
+@RequestMapping("/authorize/captcha")
+public class CaptchaController {
+
+    private final CaptchaProperties properties;
+
+    private final ReactiveRedisOperations<String, String> redis;
+
+    @GetMapping("/config")
+    public Mono<CaptchaConfig> createCaptcha() {
+        CaptchaConfig captchaConfig=new CaptchaConfig();
+        captchaConfig.setEnabled(properties.isEnabled());
+        captchaConfig.setType(properties.getType().name());
+        return Mono.just(captchaConfig);
+    }
+
+    @GetMapping("/image")
+    public Mono<CaptchaInfo> createCaptcha(@RequestParam(defaultValue = "130") int width,
+                                           @RequestParam(defaultValue = "40") int height) {
+        if (!properties.isEnabled()) {
+            return Mono.empty();
+        }
+        SpecCaptcha captcha = new SpecCaptcha(width, height, 5);
+        captcha.setCharType(Captcha.TYPE_DEFAULT);
+
+        String base64 = captcha.toBase64();
+        String key = UUID.randomUUID().toString();
+
+        return redis
+            .opsForValue()
+            .set("captcha:" + key, captcha.text(), properties.getTtl())
+            .thenReturn(new CaptchaInfo(key, base64));
+    }
+
+    @EventListener
+    public void handleAuthEvent(AuthorizationDecodeEvent event) {
+        if (!properties.isEnabled()) {
+            return;
+        }
+        String key = event.getParameter("verifyKey").map(String::valueOf).orElseThrow(() -> new ValidationException("验证码错误"));
+        String code = event.getParameter("verifyCode").map(String::valueOf).orElseThrow(() -> new ValidationException("验证码错误"));
+        String redisKey = "captcha:" + key;
+        event.async(
+            redis
+                .opsForValue()
+                .get(redisKey)
+                .map(code::equalsIgnoreCase)
+                .defaultIfEmpty(false)
+                .flatMap(checked -> redis
+                    .delete(redisKey)
+                    .then(checked ? Mono.empty() : Mono.error(new ValidationException("验证码错误"))))
+        );
+
+    }
+
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class CaptchaInfo {
+        private String key;
+
+        private String base64;
+    }
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class CaptchaConfig{
+        private boolean enabled;
+
+        private String type;
+    }
+}

+ 24 - 0
jetlinks-manager/authentication-manager/src/main/java/org/jetlinks/community/auth/captcha/CaptchaProperties.java

@@ -0,0 +1,24 @@
+package org.jetlinks.community.auth.captcha;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+
+@Component
+@ConfigurationProperties(prefix = "captcha")
+@Getter
+@Setter
+public class CaptchaProperties {
+    private boolean enabled = false;
+
+    private Duration ttl = Duration.ofMinutes(2);
+
+    private CaptchaType type = CaptchaType.image;
+
+    public enum CaptchaType {
+        image
+    }
+}

+ 3 - 0
jetlinks-standalone/src/main/resources/application.yml

@@ -58,6 +58,9 @@ device:
     writer:
       time-series:
         enabled: true #写出设备消息数据到elasticsearch
+captcha:
+  enabled: false # 开启验证码
+  ttl: 2m #验证码过期时间,2分钟
 hsweb:
   cors:
     enable: true