Bladeren bron

account 模块和 myblog 项目对齐

reghao 2 jaren geleden
bovenliggende
commit
332b2d7912

+ 47 - 0
manager/src/main/java/cn/reghao/devops/manager/account/controller/AccountCodeController.java

@@ -0,0 +1,47 @@
+package cn.reghao.devops.manager.account.controller;
+
+import cn.reghao.devops.manager.account.model.dto.RsaPubkey;
+import cn.reghao.devops.manager.account.service.CodeService;
+import cn.reghao.devops.manager.account.service.PubkeyService;
+import cn.reghao.jutil.jdk.result.WebResult;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+/**
+ * @author reghao
+ * @date 2022-02-18 16:05:30
+ */
+@RestController
+@RequestMapping("/api/account/code")
+public class AccountCodeController {
+    private final CodeService codeService;
+    private final PubkeyService pubkeyService;
+
+    public AccountCodeController(CodeService codeService, PubkeyService pubkeyService) {
+        this.codeService = codeService;
+        this.pubkeyService = pubkeyService;
+    }
+
+    @GetMapping(value = "/pubkey", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getPubkey() throws NoSuchAlgorithmException {
+        RsaPubkey rsaPubkey = pubkeyService.getPubkey();
+        return WebResult.success(rsaPubkey);
+    }
+
+    @GetMapping(value = "/captcha", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String getCaptcha() throws IOException {
+        InputStream in = codeService.generateCaptcha();
+        byte[] base64Bytes = Base64.getEncoder().encode(in.readAllBytes());
+        String base64Str = new String(base64Bytes, StandardCharsets.UTF_8);
+        String imageStr = String.format("data:image/jpeg;base64,%s", base64Str);
+        return WebResult.success(imageStr);
+    }
+}

+ 5 - 3
manager/src/main/java/cn/reghao/devops/manager/account/model/dto/AccountLoginDto.java

@@ -3,6 +3,7 @@ package cn.reghao.devops.manager.account.model.dto;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.Setter;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.NotBlank;
 import java.io.Serializable;
@@ -13,16 +14,17 @@ import java.io.Serializable;
  * @author reghao
  * @date 2021-11-17 16:36:05
  */
-@AllArgsConstructor
 @Setter
 @Getter
 public class AccountLoginDto implements Serializable {
     private static final long serialVersionUID = 1L;
 
     @NotBlank
-    private String username;
+    private String principal;
     @NotBlank
-    private String password;
+    private String credential;
+    @Length(min = 5, max = 5, message = "图形验证码应是 5 个字符")
+    private String captchaCode;
     private Boolean rememberMe;
 
     public AccountLoginDto() {

+ 19 - 0
manager/src/main/java/cn/reghao/devops/manager/account/model/dto/RsaPubkey.java

@@ -0,0 +1,19 @@
+package cn.reghao.devops.manager.account.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * @author reghao
+ * @date 2024-01-29 13:49:33
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Setter
+@Getter
+public class RsaPubkey {
+    private String pubkey;
+    private String r;
+}

+ 2 - 2
manager/src/main/java/cn/reghao/devops/manager/account/security/WebSecurityConfig.java

@@ -76,8 +76,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
     protected void configure(HttpSecurity http) throws Exception {
         http.authorizeRequests()
                 .antMatchers("/login").permitAll()
-                // 放行所有以 /api 为前缀的请求
-                //.antMatchers("/api/**").permitAll()
+                // 放行匹配前缀的请求
+                .antMatchers("/api/account/code/**").permitAll()
                 .anyRequest().authenticated();
 
         // 配置 FilterChainProxy 过滤器链

+ 3 - 18
manager/src/main/java/cn/reghao/devops/manager/account/security/form/AccountAuthFilter.java

@@ -12,8 +12,6 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * 账号密码登录 filter
@@ -41,22 +39,9 @@ public class AccountAuthFilter extends AbstractAuthenticationProcessingFilter {
     @Override
     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
             throws AuthenticationException, IOException, ServletException {
-        // form-data 中的 username 和 password 参数
-        String body = ServletUtil.getBody();
-        Map<String, String> map = new HashMap<>();
-        String[] arr = body.split("&");
-        for (String ele : arr) {
-            String[] param = ele.split("=");
-            map.put(param[0], param[1]);
-        }
-
-        String username = map.get("username");
-        String password = map.get("password");
-        String rememberMeStr = map.get("rememberMe");
-        boolean rememberMe = false;
-
-        AccountLoginDto userLoginDto = new AccountLoginDto(username, password, rememberMe);
-        AccountAuthToken preAuthToken = accountAuthService.getPreAuthentication(userLoginDto);
+        // json payload 中的 username 和 password 参数
+        AccountLoginDto accountLoginDto = (AccountLoginDto) ServletUtil.getBody(request, AccountLoginDto.class);
+        AccountAuthToken preAuthToken = accountAuthService.getPreAuthentication(accountLoginDto);
         // 调用 UserAuthProvider.authenticate()
         return this.getAuthenticationManager().authenticate(preAuthToken);
     }

+ 42 - 0
manager/src/main/java/cn/reghao/devops/manager/account/service/CodeService.java

@@ -0,0 +1,42 @@
+package cn.reghao.devops.manager.account.service;
+
+import cn.reghao.devops.manager.util.CaptchaUtil;
+import cn.reghao.devops.manager.util.CacheKeys;
+import cn.reghao.jutil.jdk.security.RandomString;
+import com.github.benmanes.caffeine.cache.Cache;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author reghao
+ * @date 2022-04-26 20:24:58
+ */
+@Slf4j
+@Service
+public class CodeService {
+    private final long sessionTimeout;
+    private final Cache<String, Object> caffeineCache;
+
+    public CodeService(ServerProperties serverProperties, Cache<String, Object> caffeineCache) {
+        this.sessionTimeout = serverProperties.getServlet().getSession().getTimeout().getSeconds();
+        this.caffeineCache = caffeineCache;
+    }
+
+    public InputStream generateCaptcha() throws IOException {
+        String captchaCode = RandomString.getString(5);
+        caffeineCache.put(CacheKeys.getCaptchaKey(), captchaCode);
+
+        ByteArrayOutputStream baos = CaptchaUtil.captchaWithDisturb(captchaCode, 130, 48);
+        return new ByteArrayInputStream(baos.toByteArray());
+    }
+
+    public String getCaptcha() {
+        return (String) caffeineCache.getIfPresent(CacheKeys.getCaptchaKey());
+    }
+}

+ 58 - 0
manager/src/main/java/cn/reghao/devops/manager/account/service/PubkeyService.java

@@ -0,0 +1,58 @@
+package cn.reghao.devops.manager.account.service;
+
+import cn.reghao.devops.manager.account.model.dto.RsaPubkey;
+import cn.reghao.devops.manager.util.CacheKeys;
+import cn.reghao.jutil.jdk.security.RandomString;
+import cn.reghao.jutil.jdk.security.RsaCryptor;
+import com.github.benmanes.caffeine.cache.Cache;
+import org.springframework.stereotype.Service;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2023-02-20 11:09:03
+ */
+@Service
+public class PubkeyService {
+    private final Cache<String, Object> caffeineCache;
+
+    public PubkeyService(Cache<String, Object> caffeineCache) {
+        this.caffeineCache = caffeineCache;
+    }
+
+    public RsaPubkey getPubkey() throws NoSuchAlgorithmException {
+        String pubkey = (String) caffeineCache.getIfPresent(CacheKeys.getRsaPubkeyKey());
+        if (pubkey == null) {
+            Map<String, String> rsaKeyPair = RsaCryptor.genKeyPair();
+            String prikey = rsaKeyPair.get("prikey");
+            pubkey = rsaKeyPair.get("pubkey");
+
+            caffeineCache.put(CacheKeys.getRsaPubkeyKey(), pubkey);
+            caffeineCache.put(CacheKeys.getRsaPrikeyKey(), prikey);
+        }
+
+        String r = RandomString.getString(16);
+        long timeout = 300;
+        caffeineCache.put(CacheKeys.getPubkeyRKey(), r);
+        return new RsaPubkey(pubkey, r);
+    }
+
+    public String decrypt(String cipherText) throws Exception {
+        String prikey = (String) caffeineCache.getIfPresent(CacheKeys.getRsaPrikeyKey());
+        if (prikey == null) {
+            String msg = "私钥不存在";
+            throw new Exception(msg);
+        }
+
+        String r = (String) caffeineCache.getIfPresent(CacheKeys.getPubkeyRKey());
+        if (r == null) {
+            String msg = "当前会话已过期, 请刷新页面后重新登录. 或者检查浏览器是否禁用了 cookie";
+            throw new Exception(msg);
+        }
+
+        String plainText = RsaCryptor.decrypt(cipherText, prikey);
+        return plainText.replace(r, "");
+    }
+}

+ 41 - 23
manager/src/main/java/cn/reghao/devops/manager/account/service/impl/AccountAuthServiceImpl.java

@@ -6,15 +6,18 @@ import cn.reghao.devops.manager.account.model.po.User;
 import cn.reghao.devops.manager.account.security.exceptioin.AccountLoginException;
 import cn.reghao.devops.manager.account.security.form.AccountAuthToken;
 import cn.reghao.devops.manager.account.service.AccountAuthService;
+import cn.reghao.devops.manager.account.service.CodeService;
+import cn.reghao.devops.manager.account.service.PubkeyService;
+import cn.reghao.devops.manager.util.CacheKeys;
 import cn.reghao.jutil.web.ServletUtil;
 import com.github.benmanes.caffeine.cache.Cache;
-import org.springframework.security.authentication.DisabledException;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
 import org.springframework.stereotype.Service;
 
 import javax.servlet.http.Cookie;
@@ -29,54 +32,69 @@ public class AccountAuthServiceImpl implements AccountAuthService {
     private final String domain = "";
     private final Cache<String, Object> caffeineCache;
     private final UserRepository userRepository;
+    private final CodeService codeService;
+    private final PubkeyService pubkeyService;
     private final PasswordEncoder passwordEncoder;
 
     public AccountAuthServiceImpl(Cache<String, Object> caffeineCache, UserRepository userRepository,
-                                  PasswordEncoder passwordEncoder) {
+                                  CodeService codeService, PubkeyService pubkeyService, PasswordEncoder passwordEncoder) {
         this.caffeineCache = caffeineCache;
         this.userRepository = userRepository;
+        this.codeService = codeService;
+        this.pubkeyService = pubkeyService;
         this.passwordEncoder = passwordEncoder;
     }
 
     @Override
-    public AccountAuthToken getPreAuthentication(AccountLoginDto userLoginDto) {
-        String principal = userLoginDto.getUsername();
-        String credential = userLoginDto.getPassword();
-        Boolean rememberMe = userLoginDto.getRememberMe();
+    public AccountAuthToken getPreAuthentication(AccountLoginDto accountLoginDto) {
+        String principal = accountLoginDto.getPrincipal();
+        String credential = accountLoginDto.getCredential();
+        String captchaCode = accountLoginDto.getCaptchaCode();
+        Boolean rememberMe = accountLoginDto.getRememberMe();
+
+        String savedCaptchaCode = codeService.getCaptcha();
+        if (savedCaptchaCode == null) {
+            String errMsg = "当前会话已过期, 请刷新页面后重新登录. 或者检查浏览器是否禁用了 cookie";
+            throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
+        } else if (!savedCaptchaCode.equalsIgnoreCase(captchaCode)) {
+            String errMsg = "图形验证码不正确...";
+            throw new PreAuthenticatedCredentialsNotFoundException(errMsg);
+        }
+
+        String decryptCredential;
+        try {
+            decryptCredential = pubkeyService.decrypt(credential);
+        } catch (Exception e) {
+            throw new PreAuthenticatedCredentialsNotFoundException(e.getMessage());
+        }
 
         String loginId = ServletUtil.getSessionId();
-        return new AccountAuthToken(loginId, rememberMe, principal, credential);
+        return new AccountAuthToken(loginId, rememberMe, principal, decryptCredential);
     }
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         User user = userRepository.findByUsername(username);
-        if (user != null) {
-            return user;
-        } else {
-            // Spring Security 会将 UsernameNotFoundException 捕获并替换,使得前端无法看到信息
-            //throw new UsernameNotFoundException(email + " 未注册");
-            throw new DisabledException(username + " 未注册");
+        if (user == null) {
+            String errMsg = String.format("帐号 %s 不存在", username);
+            throw new UsernameNotFoundException(errMsg);
         }
+
+        return user;
     }
 
     @Override
     public AccountAuthToken authByPassword(AccountAuthToken authToken) {
         String username = (String) authToken.getPrincipal();
-        User userDetail = (User) loadUserByUsername(username);
-        if (userDetail == null) {
-            String errMsg = String.format("帐号 %s 未注册", username);
-            throw new UsernameNotFoundException(errMsg);
-        }
-
+        User user = (User) loadUserByUsername(username);
         String password = (String) authToken.getCredentials();
-        String encodedPassword = passwordEncoder.encode(password + userDetail.getSalt());
-        if (!userDetail.getPassword().equals(encodedPassword)) {
+        String encodedPassword = passwordEncoder.encode(password + user.getSalt());
+        if (!user.getPassword().equals(encodedPassword)) {
             String errMsg = "账号或密码不正确";
             throw new AccountLoginException(errMsg);
         }
 
-        return new AccountAuthToken(authToken, userDetail);
+        return new AccountAuthToken(authToken, user);
     }
 
     @Override
@@ -89,7 +107,7 @@ public class AccountAuthServiceImpl implements AccountAuthService {
         Cookie cookie3 = generateCookie(cookieName, userdata, timeout);
         ServletUtil.getResponse().addCookie(cookie3);
 
-        String loginSuccessKey = String.format("account:login:success:%s:%s:%s", userId, plat, loginId);
+        String loginSuccessKey = CacheKeys.getLoginSuccessKey(userId, plat, loginId);
         if (timeout == 0) {
             timeout = 3600*24*30;
         }

+ 52 - 0
manager/src/main/java/cn/reghao/devops/manager/util/CacheKeys.java

@@ -0,0 +1,52 @@
+package cn.reghao.devops.manager.util;
+
+import cn.reghao.jutil.web.ServletUtil;
+
+/**
+ * @author reghao
+ * @date 2023-02-03 15:57:06
+ */
+public class CacheKeys {
+    public static String getCaptchaKey() {
+        String sessionId = ServletUtil.getSessionId();
+        return String.format("account:code:%s:captcha", sessionId);
+    }
+
+    public static String getVerifyCodeKey(String receiver) {
+        String sessionId = ServletUtil.getSessionId();
+        return String.format("account:code:%s:mobile:%s", sessionId, receiver);
+    }
+
+    public static String getPubkeyRKey() {
+        String sessionId = ServletUtil.getSessionId();
+        return String.format("account:rsa:r:%s", sessionId);
+    }
+
+    public static String getRsaPubkeyKey() {
+        return String.format("account:rsa:pubkey");
+    }
+
+    public static String getRsaPrikeyKey() {
+        return String.format("account:rsa:prikey");
+    }
+
+    public static String getLoginSuccessKey(int userId, int plat, String loginId) {
+        return String.format("account:login:success:%s:%s:%s", userId, plat, loginId);
+    }
+
+    public static String getLoginSuccessKey(String sessId) {
+        return String.format("account:login:success:%s", sessId);
+    }
+
+    public static String getLoginSuccessKeyPattern(int userId) {
+        return String.format("account:login:success:%s:*", userId);
+    }
+
+    public static String getLoginFailedCountKey(String userId, int plat) {
+        return String.format("account:login:failed:count:%s:%s", userId, plat);
+    }
+
+    public static String getLoginFailedLockKey(String userId, int plat) {
+        return String.format("account:login:failed:lock:%s:%s", userId, plat);
+    }
+}

+ 157 - 0
manager/src/main/java/cn/reghao/devops/manager/util/CaptchaUtil.java

@@ -0,0 +1,157 @@
+package cn.reghao.devops.manager.util;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.geom.CubicCurve2D;
+import java.awt.geom.QuadCurve2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * @author reghao
+ * @date 2021-12-05 03:35:30
+ */
+public class CaptchaUtil {
+    // 常用颜色
+    private static final int[][] COLOR = {{0, 135, 255}, {51, 153, 51}, {255, 102, 102}, {255, 153, 0}, {153, 102, 0}, 
+            {153, 102, 153}, {51, 153, 153}, {102, 102, 255}, {0, 102, 204}, {204, 51, 51}, {0, 153, 204}, {0, 51, 102}};
+    private static final Random random = new SecureRandom();
+
+    public static ByteArrayOutputStream captchaWithDisturb(String randomStr, int width, int height) throws IOException {
+        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g2d = (Graphics2D) bi.getGraphics();
+        // 填充背景
+        g2d.setColor(Color.WHITE);
+        g2d.fillRect(0, 0, width, height);
+        // 抗锯齿
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        // 画干扰圆
+        drawOval(2, null, g2d, width, height);
+        // 设置线条特征
+        g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+        // 画干扰线
+        drawLine(1, null, g2d, width, height);
+        // 画贝塞尔曲线
+        drawBesselLine(1, null, g2d, width, height);
+
+        FontMetrics fontMetrics = g2d.getFontMetrics();
+        // 每一个字符所占的宽度
+        int fW = width / randomStr.length();
+        // 字符的左右边距
+        int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2;
+        for (int i = 0; i < randomStr.length(); i++) {
+            g2d.setColor(color());
+            // 文字的纵坐标
+            int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(randomStr.charAt(i)), g2d).getHeight()) >> 1);
+            g2d.drawString(String.valueOf(randomStr.charAt(i)), i * fW + fSp + 3, fY - 3);
+        }
+        g2d.dispose();
+
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        ImageIO.write((RenderedImage) bi, "jpg", byteArrayOutputStream);
+        return byteArrayOutputStream;
+    }
+
+    /**
+     * 随机画干扰圆
+     *
+     * @param num   数量
+     * @param color 颜色
+     * @param g     Graphics2D
+     */
+    private static void drawOval(int num, Color color, Graphics2D g, int width, int height) {
+        for (int i = 0; i < num; i++) {
+            g.setColor(color == null ? color() : color);
+            int w = 5 + num(10);
+            g.drawOval(num(width - 25), num(height - 15), w, w);
+        }
+    }
+
+    /**
+     * 产生两个数之间的随机数
+     *
+     * @param min 最小值
+     * @param max 最大值
+     * @return 随机数
+     */
+    private static int num(int min, int max) {
+        return min + random.nextInt(max - min);
+    }
+
+    /**
+     * 产生0-num的随机数,不包括num
+     *
+     * @param num 最大值
+     * @return 随机数
+     */
+    private static int num(int num) {
+        return random.nextInt(num);
+    }
+
+    /**
+     * 随机获取常用颜色
+     *
+     * @return 随机颜色
+     */
+    private static Color color() {
+        int[] color = COLOR[num(COLOR.length)];
+        int r = color[0];
+        int g = color[1];
+        int b = color[2];
+        return new Color(r, g, b);
+    }
+
+    /**
+     * 随机画干扰线
+     *
+     * @param num   数量
+     * @param color 颜色
+     * @param g     Graphics2D
+     */
+    private static void drawLine(int num, Color color, Graphics2D g, int width, int height) {
+        for (int i = 0; i < num; i++) {
+            g.setColor(color == null ? color() : color);
+            int x1 = num(-10, width - 10);
+            int y1 = num(5, height - 5);
+            int x2 = num(10, width + 10);
+            int y2 = num(2, height - 2);
+            g.drawLine(x1, y1, x2, y2);
+        }
+    }
+
+    /**
+     * 随机画贝塞尔曲线
+     *
+     * @param num   数量
+     * @param color 颜色
+     * @param g     Graphics2D
+     */
+    private static void drawBesselLine(int num, Color color, Graphics2D g, int width, int height) {
+        for (int i = 0; i < num; i++) {
+            g.setColor(color == null ? color() : color);
+            int x1 = 5, y1 = num(5, height / 2);
+            int x2 = width - 5, y2 = num(height / 2, height - 5);
+            int ctrlx = num(width / 4, width / 4 * 3), ctrly = num(5, height - 5);
+            if (num(2) == 0) {
+                int ty = y1;
+                y1 = y2;
+                y2 = ty;
+            }
+            // 二阶贝塞尔曲线
+            if (num(2) == 0) {
+                QuadCurve2D shape = new QuadCurve2D.Double();
+                shape.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
+                g.draw(shape);
+            } else {
+                // 三阶贝塞尔曲线
+                int ctrlx1 = num(width / 4, width / 4 * 3), ctrly1 = num(5, height - 5);
+                CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx, ctrly, ctrlx1, ctrly1, x2, y2);
+                g.draw(shape);
+            }
+        }
+    }
+}

File diff suppressed because it is too large
+ 1 - 0
manager/src/main/resources/static/js/jsencrypt_3.3.2.min.js


+ 36 - 1
manager/src/main/resources/static/js/login.js

@@ -1,4 +1,6 @@
 if(window.top!==window.self){window.top.location=window.location};
+document.write('<script type="text/javascript" src="/js/jsencrypt_3.3.2.min.js"></script>');
+
 layui.use(['element', 'layer'], function () {
     var layer = layui.layer;
     var $ = layui.jquery;
@@ -8,7 +10,40 @@ layui.use(['element', 'layer'], function () {
     });
     $(document).on('click', '.ajax-login', function (e) {
         e.preventDefault();
+        // 获取表单数据并将其转换为 JSON 对象
         var form = $(this).parents("form");
+        var serializeArray = form.serializeArray();
+
+        var jsonData = {}
+        $.each(serializeArray, function (index, item) {
+            jsonData[item.name] = item.value;
+        });
+        let encryptor = new JSEncrypt();
+        encryptor.setPublicKey(localStorage.getItem("pubkey"));
+        jsonData.credential = encryptor.encrypt(localStorage.getItem("pubkeyR") + jsonData.credential);
+        let jsonStr = JSON.stringify(jsonData);
+        // 手动提交表单
+        $.ajax({
+            type: 'POST',
+            url: '/login',
+            contentType: "application/json;charset=utf-8",
+            data: jsonStr,
+            success: function(resp) {
+                // 处理表单提交成功后的响应
+                if (resp.code === 0) {
+                    let redirectPath = resp.data
+                    window.location.replace(redirectPath)
+                } else {
+                    $.fn.Messager(resp)
+                }
+            },
+            error: function(xhr, status, error) {
+                // 处理表单提交失败后的错误
+                layer.msg(resp.responseText, {offset: '15px', time: 5000, icon: 2});
+            }
+        });
+
+        /*var form = $(this).parents("form");
         var url = form.attr("action");
         var serializeArray = form.serializeArray();
         $.post(url, serializeArray, function (result) {
@@ -21,7 +56,7 @@ layui.use(['element', 'layer'], function () {
             }
         }).error(function (result) {
             layer.msg(result.responseText, {offset: '15px', time: 5000, icon: 2});
-        });
+        });*/
     });
     $('.layui-layer-loading').hide();
 });

+ 33 - 5
manager/src/main/resources/templates/login.html

@@ -13,16 +13,16 @@
     <form class="layui-form" th:action="@{/login}" method="post">
         <div class="layui-form-item">
             <label class="layui-icon layui-icon-username" for="username"></label>
-            <input class="layui-input" type="text" name="username" id="username" placeholder="用户名">
+            <input class="layui-input" type="text" name="principal" id="username" placeholder="用户名">
         </div>
         <div class="layui-form-item">
             <label class="layui-icon layui-icon-password" for="password"></label>
-            <input class="layui-input" type="password" name="password" id="password" placeholder="密码">
+            <input class="layui-input" type="password" name="credential" id="password" placeholder="密码">
         </div>
-        <div th:if="${isCaptcha}" class="layui-form-item captcha-item">
+        <div class="layui-form-item captcha-item">
             <label class="layui-icon layui-icon-vercode"></label>
-            <input class="layui-input" type="text" name="captcha" autocomplete="off" placeholder="验证码">
-            <img class="captcha-img" th:src="@{/captcha}" />
+            <input class="layui-input" type="text" name="captchaCode" autocomplete="off" placeholder="验证码">
+            <img id="captchaImg" class="captcha-img" onclick="getCaptcha()" src="" alt="user image" />
         </div>
         <div class="layui-form-item">
             <input type="checkbox" name="rememberMe" title="记住我" lay-skin="primary">
@@ -35,7 +35,35 @@
         <div class="layui-layer-content"></div>
     </div>
 </div>
+
+<script type="text/javascript" th:src="@{/js/plugins/jquery-2.2.4.min.js}"></script>
 <script th:replace="/common/template :: script"></script>
 <script th:src="@{/js/login.js}" charset="utf-8"></script>
+<script type="text/javascript">
+    function getCaptcha(){
+        var captchaImg = document.getElementById("captchaImg");
+        $.get('/api/account/code/captcha', function (json) {
+            if (json.code === 0) {
+                captchaImg.setAttribute("src", json.data);
+            } else {
+                layer.alert(json.msg, function(){
+                });
+            }
+        })
+    }
+    function getPubkey(){
+        $.get('/api/account/code/pubkey', function (json) {
+            if (json.code === 0) {
+                localStorage.setItem("pubkey", json.data.pubkey)
+                localStorage.setItem("pubkeyR", json.data.r)
+                getCaptcha()
+            } else {
+                layer.alert(json.msg, function(){
+                });
+            }
+        })
+    }
+    getPubkey();
+</script>
 </body>
 </html>

Some files were not shown because too many files changed in this diff