Przeglądaj źródła

登录添加验证码,密码输错次数校验

wangmh 8 lat temu
rodzic
commit
e6e35f7f2b

+ 71 - 25
sso-server/src/main/java/com/uas/sso/controller/LoginController.java

@@ -8,11 +8,14 @@ import com.uas.sso.SSOToken;
 import com.uas.sso.common.util.HttpUtil;
 import com.uas.sso.core.Const;
 import com.uas.sso.entity.App;
+import com.uas.sso.entity.User;
 import com.uas.sso.entity.UserAccount;
+import com.uas.sso.entity.UserRecord;
 import com.uas.sso.service.AppService;
 import com.uas.sso.service.UserService;
 import com.uas.sso.service.impl.UserAccountService;
 import com.uas.sso.util.AccountTypeUtils;
+import com.uas.sso.util.CaptchaUtil;
 import com.uas.sso.web.waf.request.WafRequestWrapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.ui.ModelMap;
@@ -23,6 +26,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.servlet.ServletException;
+import java.io.IOException;
 import java.util.*;
 
 /**
@@ -35,6 +40,8 @@ import java.util.*;
 @RestController
 public class LoginController extends BaseController {
 
+    private static final int PWD_ERROR_FIVE_TIME = 5;
+    private static final int PWD_ERROR_THREE_TIME = 3;
     @Autowired
     private AppService appService;
 
@@ -53,6 +60,7 @@ public class LoginController extends BaseController {
         String spaceUU = wr.getParameter("spaceUU");
         String username = StringUtils.trimAllWhitespace(wr.getParameter("username"));
         String password = wr.getParameter("password");
+        String captcha = wr.getParameter("captcha");
         String returnUrl = wr.getParameter("returnUrl");
         String baseUrl = wr.getParameter("baseUrl");
 
@@ -76,6 +84,34 @@ public class LoginController extends BaseController {
             return error("应用id不存在");
         }
 
+        // 获取用户基本信息
+        User user = userService.findByUsername(username);
+        if (user == null) {
+            return error("用户名或密码错误");
+        }
+
+        // 校验账户密码输错次数
+        int pwdErrorCount = user.getUserRecord() == null ? 0 : user.getUserRecord().getPwdErrorCount();
+        Object randomString = request.getSession().getAttribute("randomString");
+        String checkCode = randomString == null ? "" : randomString.toString();
+        if (pwdErrorCount >= PWD_ERROR_FIVE_TIME) {
+            return error("403", "密码错误次数已达上限,今日无法登陆");
+        }
+        // 校验账号是否被锁定,5次输错密码
+        if (pwdErrorCount >= PWD_ERROR_THREE_TIME && StringUtils.isEmpty(captcha)) {
+            return error("404", "验证码不能为空");
+        }
+        if (pwdErrorCount >= PWD_ERROR_THREE_TIME && !captcha.equalsIgnoreCase(checkCode)) {
+            return error("验证码错误");
+        }
+
+        // 校验密码
+        String encryPwd = userService.getEncryPassword(Const.ENCRY_FORMAT, password, user.getSalt());
+        if (!encryPwd.equals(user.getPassword())) {
+            inputErrorPwd(user);
+            return error("您输入的账号或密码有误");
+        }
+
         // 登录
         App controlApp = StringUtils.isEmpty(app.getUserControl()) ? app : appService.findOne(app.getUserControl());
         boolean personalEnable = Const.YES == controlApp.getPersonalEnable();
@@ -83,26 +119,16 @@ public class LoginController extends BaseController {
         if (StringUtils.isEmpty(spaceUU)) {
             /*企业uu号为空,让用户选择企业*/
             // 找到用户账号信息
-            List<UserAccount> userAccounts = getUserAccountByUserName(controlApp.getUid(), username);
+            List<UserAccount> userAccounts = userAccountService.findByUserUU(appId, user.getUserUU());
 
-            // 应用不允许个人账号,并且账号未绑定企业
-            if (!personalEnable && CollectionUtils.isEmpty(userAccounts)) {
-                return error("用户名或密码错误");
+            // 没有记录
+            if (CollectionUtils.isEmpty(userAccounts)) {
+                return error("您的账号为绑定企业并且当前应用不支持个人账号");
             }
 
             // 应用允许个人账号,并且账号未绑定企业,或者只绑定了一个企业,直接登录
             if (userAccounts.size() == 1) {
-                return loginByUser(userAccounts.get(0), password, returnUrl);
-            }
-
-            // 由于老账户存在多个账号绑定一个邮箱的情况,把密码不符合的企业去除
-            Iterator<UserAccount> iterator = userAccounts.iterator();
-            while (iterator.hasNext()) {
-                UserAccount userAccount = iterator.next();
-                String encryPwd = userService.getEncryPassword(Const.ENCRY_FORMAT, password, userAccount.getSalt());
-                if (!encryPwd.equals(userAccount.getPassword())) {
-                    iterator.remove();
-                }
+                return loginByUser(userAccounts.get(0), returnUrl);
             }
 
             // 返回企业id和名称
@@ -110,14 +136,22 @@ public class LoginController extends BaseController {
         } else if (spaceUU.equals(Const.SPACEUU_PERSONAL)) {
             // 使用个人账号登录
             UserAccount userAccount = getUserAccountByUserName(controlApp.getUid(), username, null);
-            return loginByUser(userAccount, password, returnUrl);
+            return loginByUser(userAccount, returnUrl);
         } else {
             // 带企业登录
             UserAccount userAccount = getUserAccountByUserName(controlApp.getUid(), username, spaceUU);
-            return loginByUser(userAccount, password, returnUrl);
+            return loginByUser(userAccount, returnUrl);
         }
     }
 
+    private void inputErrorPwd(User user) {
+        UserRecord userRecord = user.getUserRecord();
+        int pwdErrorCount = userRecord.getPwdErrorCount();
+        userRecord.setPwdErrorCount(++pwdErrorCount);
+        userService.save(userRecord);
+    }
+
+
     private List<UserAccount> getUserAccountByUserName(String appId, String username) {
         String type = AccountTypeUtils.getAccountType(username);
         if (AccountTypeUtils.MOBILE.equals(type)) {
@@ -130,7 +164,7 @@ public class LoginController extends BaseController {
         }
         if (AccountTypeUtils.UU_NUMBER.equals(type)) {
             // uu号登录
-            return userAccountService.findOneByUserUU(appId, Long.valueOf(username));
+            return userAccountService.findByUserUU(appId, Long.valueOf(username));
         }
 
         // 其余情况
@@ -156,17 +190,18 @@ public class LoginController extends BaseController {
         return null;
     }
 
-    private ModelMap loginByUser(UserAccount userAccount, String password, String returnUrl) {
+    /**
+     * 用户信息没问题,直接登录
+     *
+     * @param userAccount 用户账号信息
+     * @param returnUrl 跳转url
+     * @return
+     */
+    private ModelMap loginByUser(UserAccount userAccount, String returnUrl) {
         if (StringUtils.isEmpty((userAccount.getPassword()))) {
             // 使用错误码100来判断
             return error("100", "未设置密码");
         } else {
-            // 校验密码
-            String encryPwd = userService.getEncryPassword(Const.ENCRY_FORMAT, password, userAccount.getSalt());
-            if (!encryPwd.equals(userAccount.getPassword())) {
-                return error("您输入的账号或密码有误");
-            }
-
             // 登录
             /*
              * 设置登录 Cookie 最后一个参数 true 时添加 cookie 同时销毁当前 JSESSIONID
@@ -247,4 +282,15 @@ public class LoginController extends BaseController {
         return new ModelMap("spaces", spaces);
     }
 
+    @RequestMapping(value = "/checkcode", method = RequestMethod.GET)
+    @ResponseBody
+    public void checkCode() {
+        try {
+            CaptchaUtil.outputCaptcha(request, response);
+        } catch (ServletException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
 }

+ 15 - 0
sso-server/src/main/java/com/uas/sso/dao/UserRecordDao.java

@@ -0,0 +1,15 @@
+package com.uas.sso.dao;
+
+import com.uas.sso.entity.UserRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+/**
+ * 用户登录记录Dao
+ *
+ * @author wangmh
+ * @date 2018/1/9.
+ */
+public interface UserRecordDao extends JpaRepository<UserRecord, Long>, JpaSpecificationExecutor<UserRecord> {
+
+}

+ 15 - 0
sso-server/src/main/java/com/uas/sso/entity/User.java

@@ -170,6 +170,13 @@ public class User implements Serializable {
     @Column(name = "_lock")
     private Integer lock;
 
+    @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
+    @JoinColumn(name = "record_id")
+    private UserRecord userRecord;
+
+    public User() {
+    }
+
     public Long getUserUU() {
         return userUU;
     }
@@ -369,4 +376,12 @@ public class User implements Serializable {
     public void setLock(Integer lock) {
         this.lock = lock;
     }
+
+    public UserRecord getUserRecord() {
+        return userRecord;
+    }
+
+    public void setUserRecord(UserRecord userRecord) {
+        this.userRecord = userRecord;
+    }
 }

+ 50 - 0
sso-server/src/main/java/com/uas/sso/entity/UserRecord.java

@@ -0,0 +1,50 @@
+package com.uas.sso.entity;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+/**
+ * 用户登录记录
+ *
+ * @author wangmh
+ * @date 2018/1/9.
+ */
+@Entity
+@Table(name = "sso$user$record")
+public class UserRecord implements Serializable {
+
+    @Id
+    @Column(name = "_id")
+    private Long id;
+
+    @Column(name = "last_login_time")
+    private Timestamp lastLoginTime;
+
+    @Column(name = "pwd_error_count", nullable = false)
+    private int pwdErrorCount;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Timestamp getLastLoginTime() {
+        return lastLoginTime;
+    }
+
+    public void setLastLoginTime(Timestamp lastLoginTime) {
+        this.lastLoginTime = lastLoginTime;
+    }
+
+    public int getPwdErrorCount() {
+        return pwdErrorCount;
+    }
+
+    public void setPwdErrorCount(int pwdErrorCount) {
+        this.pwdErrorCount = pwdErrorCount;
+    }
+}

+ 24 - 3
sso-server/src/main/java/com/uas/sso/service/UserService.java

@@ -1,9 +1,7 @@
 package com.uas.sso.service;
 
 import com.uas.sso.entity.User;
-import com.uas.sso.entity.UserAccount;
-
-import java.util.List;
+import com.uas.sso.entity.UserRecord;
 
 /**
  * 用户信息service
@@ -92,4 +90,27 @@ public interface UserService {
      * @param isEncry 密码是否加密
      */
     void checkPasswordByEmail(String email, String password, boolean isEncry);
+
+    /**
+     * 获得当前账号密码输错次数
+     *
+     * @param username 账号
+     * @return
+     */
+    int getPwdErrorCount(String username);
+
+    /**
+     * 根据用户名找到用户,如果用户名为邮箱,则为已认证邮箱
+     *
+     * @param username
+     * @return
+     */
+    User findByUsername(String username);
+
+    /**
+     * 保存用户登录记录
+     *
+     * @param userRecord
+     */
+    void save(UserRecord userRecord);
 }

+ 1 - 1
sso-server/src/main/java/com/uas/sso/service/impl/UserAccountService.java

@@ -67,5 +67,5 @@ public interface UserAccountService {
      * @param userUU 用户uu号
      * @return
      */
-    List<UserAccount> findOneByUserUU(String appId, Long userUU);
+    List<UserAccount> findByUserUU(String appId, Long userUU);
 }

+ 1 - 1
sso-server/src/main/java/com/uas/sso/service/impl/UserAccountServiceImpl.java

@@ -45,7 +45,7 @@ public class UserAccountServiceImpl implements UserAccountService {
     }
 
     @Override
-    public List<UserAccount> findOneByUserUU(String appId, Long userUU) {
+    public List<UserAccount> findByUserUU(String appId, Long userUU) {
         return userAccountDao.findByAppIdAndUserUU(appId, userUU);
     }
 }

+ 43 - 0
sso-server/src/main/java/com/uas/sso/service/impl/UserServiceImpl.java

@@ -5,11 +5,14 @@ import com.uas.sso.core.Const;
 import com.uas.sso.core.Status;
 import com.uas.sso.core.Type;
 import com.uas.sso.dao.UserDao;
+import com.uas.sso.dao.UserRecordDao;
 import com.uas.sso.entity.User;
+import com.uas.sso.entity.UserRecord;
 import com.uas.sso.exception.VisibleError;
 import com.uas.sso.logging.LoggerManager;
 import com.uas.sso.logging.UserBufferedLogger;
 import com.uas.sso.service.UserService;
+import com.uas.sso.util.AccountTypeUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
@@ -32,6 +35,9 @@ public class UserServiceImpl implements UserService {
     @Autowired
     private UserDao userDao;
 
+    @Autowired
+    private UserRecordDao userRecordDao;
+
     private UserBufferedLogger userLog = LoggerManager.getLogger(UserBufferedLogger.class);
 
     @Override
@@ -141,6 +147,38 @@ public class UserServiceImpl implements UserService {
         }
     }
 
+    @Override
+    public int getPwdErrorCount(String username) {
+        String type = AccountTypeUtils.getAccountType(username);
+        User user = null;
+
+        if (user == null || user.getUserRecord() == null) {
+            return 0;
+        }
+        return user.getUserRecord().getPwdErrorCount();
+    }
+
+    @Override
+    public User findByUsername(String username) {
+        String type = AccountTypeUtils.getAccountType(username);
+        User user = null;
+        if (AccountTypeUtils.MOBILE.equals(type)) {
+            // 手机号
+            user = userDao.findByMobile(username);
+        } else if (AccountTypeUtils.EMAIL.equals(type)) {
+            // 邮箱
+            List<User> users = userDao.findByEmailAndEmailValidCode(username, (short) Status.AUTHENTICATED.getCode());
+            // 认证邮箱只有一条记录,直接选择第一个
+            if (!CollectionUtils.isEmpty(users)) {
+                user = users.get(0);
+            }
+        } else if (AccountTypeUtils.UU_NUMBER.equals(type)) {
+            // uu号
+            user = userDao.findByUserUU(Long.valueOf(type));
+        }
+        return user;
+    }
+
     /**
      * 校验用户密码
      *
@@ -160,4 +198,9 @@ public class UserServiceImpl implements UserService {
             throw new VisibleError("密码不一致");
         }
     }
+
+    @Override
+    public void save(UserRecord userRecord) {
+        userRecordDao.save(userRecord);
+    }
 }

+ 28 - 0
sso-server/src/main/java/com/uas/sso/support/SystemSession.java

@@ -0,0 +1,28 @@
+/**
+ * Company usoftchina
+ * Copyright (C) 2004-2017 All Rights Reserved.
+ */
+package com.uas.sso.support;
+
+import com.uas.sso.entity.UserAccount;
+
+/**
+ * @author wangmh
+ * @date 2018/1/9
+ */
+public class SystemSession {
+
+    private static ThreadLocal<UserAccount> local = new ThreadLocal<UserAccount>();
+
+    public static UserAccount getUserAccount() {
+        return local.get();
+    }
+
+    public static void setUserAccount(UserAccount userAccount) {
+        SystemSession.local.set(userAccount);
+    }
+
+    public static void clear() {
+        local.set(null);
+    }
+}

+ 101 - 0
sso-server/src/main/java/com/uas/sso/util/CaptchaUtil.java

@@ -0,0 +1,101 @@
+package com.uas.sso.util;
+
+import com.sun.image.codec.jpeg.JPEGCodec;
+import com.sun.image.codec.jpeg.JPEGImageEncoder;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * 图片验证码工具类
+ *
+ * @author wangmh
+ * @date 2017/11/2
+ */
+public class CaptchaUtil {
+
+    private CaptchaUtil(){}
+
+    /**
+     * 随机字符字典
+     */
+    private static final char[] CHARS = { '2', '3', '4', '5', '6', '7', '8',
+            '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
+            'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
+
+    /**
+     * 随机数
+     */
+    private static Random random = new Random();
+
+    /**
+     * 获取6位随机数
+     */
+    private static String getRandomString()
+    {
+        StringBuffer buffer = new StringBuffer();
+        for(int i = 0; i < 4; i++)
+        {
+            buffer.append(CHARS[random.nextInt(CHARS.length)]);
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * 获取随机数颜色
+     */
+    private static Color getRandomColor()
+    {
+        return new Color(255,255,255);
+    }
+
+    /**
+     * 返回某颜色的反色
+     */
+    private static Color getReverseColor(Color c)
+    {
+        return new Color(255 - c.getRed(), 255 - c.getGreen(),
+                255 - c.getBlue());
+    }
+
+    public static void outputCaptcha(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException, IOException
+    {
+
+        response.setContentType("image/jpeg");
+
+        String randomString = getRandomString();
+        request.getSession(true).setAttribute("randomString", randomString);
+
+        int width = 100;
+        int height = 30;
+
+        Color color = getRandomColor();
+        Color reverse = getReverseColor(color);
+
+        BufferedImage bi = new BufferedImage(width, height,
+                BufferedImage.TYPE_INT_RGB);
+        Graphics2D g = bi.createGraphics();
+        g.setFont(new Font(Font.SANS_SERIF, Font.ROMAN_BASELINE, 18));
+        g.setColor(color);
+        g.fillRect(0, 0, width, height);
+        g.setColor(reverse);
+        g.drawString(randomString, 18, 20);
+//        for (int i = 0, n = random.nextInt(100); i < n; i++)
+//        {
+//            g.drawRect(random.nextInt(width), random.nextInt(height), 1, 1);
+//        }
+
+        // 转成JPEG格式
+        ServletOutputStream out = response.getOutputStream();
+        JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
+        encoder.encode(bi);
+        out.flush();
+    }
+}

+ 93 - 0
sso-server/src/main/java/com/uas/sso/util/FastjsonUtils.java

@@ -0,0 +1,93 @@
+package com.uas.sso.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+
+import java.util.List;
+
+/**
+ * @author yingp
+ * @see JSON
+ *
+ */
+public class FastjsonUtils {
+
+	public static Feature DEFAULT_PARSER_FEATURE = Feature.DisableCircularReferenceDetect;
+	public static SerializerFeature DEFAULT_SERIAL_FEATURE = SerializerFeature.DisableCircularReferenceDetect;
+
+	/**
+	 * 把JSON文本parse为JSONObject或者JSONArray
+	 * 
+	 * @param text
+	 * @return
+	 */
+	public static Object parse(String text) {
+		return JSON.parse(text, DEFAULT_PARSER_FEATURE);
+	}
+
+	/**
+	 * 把JSON文本parse成JSONObject
+	 * 
+	 * @param text
+	 * @return
+	 */
+	public static final JSONObject parseObject(String text) {
+		return JSON.parseObject(text, DEFAULT_PARSER_FEATURE);
+	}
+
+	/**
+	 * 把JSON文本parse为JavaBean
+	 * 
+	 * @param text
+	 * @param clazz
+	 * @return
+	 */
+	public static final <T> T fromJson(String text, Class<T> clazz) {
+		return JSON.parseObject(text, clazz, DEFAULT_PARSER_FEATURE);
+	}
+
+	/**
+	 * 把JSON文本parse成JSONArray
+	 * 
+	 * @param text
+	 * @return
+	 */
+	public static final JSONArray fromJsonArray(String text) {
+		return JSON.parseArray(text);
+	}
+
+	/**
+	 * 把JSON文本parse成JavaBean集合
+	 * 
+	 * @param text
+	 * @param clazz
+	 * @return
+	 */
+	public static final <T> List<T> fromJsonArray(String text, Class<T> clazz) {
+		return JSON.parseArray(text, clazz);
+	}
+
+	/**
+	 * 将JavaBean序列化为JSON文本
+	 * 
+	 * @param object
+	 * @return
+	 */
+	public static final String toJson(Object object) {
+		return JSON.toJSONString(object, DEFAULT_SERIAL_FEATURE);
+	}
+
+	/**
+	 * 将JavaBean转换为JSONObject或者JSONArray。
+	 * 
+	 * @param javaObject
+	 * @return
+	 */
+	public static final Object toJSON(Object javaObject) {
+		return JSON.toJSON(javaObject);
+	}
+
+}