@@ -80,6 +80,10 @@ public class LoginProcessor {
8080 * </p>
8181 */
8282 public static final Map <String , JSONObject > WRONG_PWD_TRIES = new ConcurrentHashMap <>();
83+ /**
84+ * Login lock by IP: <ip, unlockAtMillis>.
85+ */
86+ private static final Map <String , Long > LOGIN_IP_LOCK = new ConcurrentHashMap <>();
8387
8488 /**
8589 * Logger.
@@ -761,6 +765,18 @@ public void login(final RequestContext context) {
761765 context .renderJSON (StatusCodes .ERR ).renderMsg (langPropsService .get ("loginFailLabel" ));
762766 final JSONObject requestJSONObject = context .requestJSON ();
763767 final String nameOrEmail = requestJSONObject .optString ("nameOrEmail" );
768+ final String ip = Requests .getRemoteAddr (request );
769+
770+ // IP-level lock: 5 minutes after too many failures
771+ final Long unlockAt = LOGIN_IP_LOCK .get (ip );
772+ if (unlockAt != null ) {
773+ if (System .currentTimeMillis () < unlockAt ) {
774+ context .renderMsg ("尝试次数过多,请 5 分钟后再试" );
775+ return ;
776+ } else {
777+ LOGIN_IP_LOCK .remove (ip );
778+ }
779+ }
764780
765781 try {
766782 JSONObject user = userQueryService .getUserByName (nameOrEmail );
@@ -772,9 +788,14 @@ public void login(final RequestContext context) {
772788 user = userQueryService .getUserByPhone (nameOrEmail );
773789 }
774790
791+ boolean userFound = true ;
775792 if (null == user ) {
776- context .renderMsg (langPropsService .get ("notFoundUserLabel" ));
777- return ;
793+ userFound = false ;
794+ // fabricate a dummy user object to unify flow and avoid timing leaks
795+ user = new JSONObject ();
796+ user .put (Keys .OBJECT_ID , "DUMMY" );
797+ user .put (User .USER_PASSWORD , "" );
798+ user .put (UserExt .USER_STATUS , UserExt .USER_STATUS_C_VALID );
778799 }
779800
780801 if (UserExt .USER_STATUS_C_INVALID == user .optInt (UserExt .USER_STATUS )) {
@@ -784,14 +805,12 @@ public void login(final RequestContext context) {
784805 }
785806
786807 if (UserExt .USER_STATUS_C_NOT_VERIFIED == user .optInt (UserExt .USER_STATUS )) {
787- //userMgmtService.updateOnlineStatus(user.optString(Keys.OBJECT_ID), "", false, true);
788808 context .renderMsg (langPropsService .get ("notVerifiedLabel" ));
789809 return ;
790810 }
791811
792812 if (UserExt .USER_STATUS_C_INVALID_LOGIN == user .optInt (UserExt .USER_STATUS )
793813 || UserExt .USER_STATUS_C_DEACTIVATED == user .optInt (UserExt .USER_STATUS )) {
794- //userMgmtService.updateOnlineStatus(user.optString(Keys.OBJECT_ID), "", false, true);
795814 context .renderMsg (langPropsService .get ("invalidLoginLabel" ));
796815 return ;
797816 }
@@ -803,17 +822,9 @@ public void login(final RequestContext context) {
803822 }
804823
805824 final int wrongCount = wrong .optInt (Common .WRON_COUNT );
806- if (wrongCount > 3 ) {
807- final String captcha = requestJSONObject .optString (CaptchaProcessor .CAPTCHA );
808- if (!StringUtils .equals (wrong .optString (CaptchaProcessor .CAPTCHA ), captcha )) {
809- context .renderMsg (langPropsService .get ("captchaErrorLabel" ));
810- context .renderJSONValue (Common .NEED_CAPTCHA , userId );
811- return ;
812- }
813- }
814825
815826 final String userPassword = user .optString (User .USER_PASSWORD );
816- if (userPassword .equals (requestJSONObject .optString (User .USER_PASSWORD ))) {
827+ if (userFound && userPassword .equals (requestJSONObject .optString (User .USER_PASSWORD ))) {
817828 long code ;
818829 try {
819830 code = requestJSONObject .optLong ("mfaCode" );
@@ -827,24 +838,26 @@ public void login(final RequestContext context) {
827838
828839 final String token = Sessions .login (response , userId , requestJSONObject .optBoolean (Common .REMEMBER_LOGIN ));
829840
830- final String ip = Requests .getRemoteAddr (request );
831841 //userMgmtService.updateOnlineStatus(user.optString(Keys.OBJECT_ID), ip, true, true);
832842
833843 context .renderCodeMsg (StatusCodes .SUCC , "" );
834844 context .renderJSONValue (Keys .TOKEN , token );
835845
836846 WRONG_PWD_TRIES .remove (userId );
847+ LOGIN_IP_LOCK .remove (ip );
837848 return ;
838849 }
839850
840- if (wrongCount > 2 ) {
841- context .renderJSONValue (Common .NEED_CAPTCHA , userId );
842- }
843-
844- wrong .put (Common .WRON_COUNT , wrongCount + 1 );
851+ final int newWrongCount = wrongCount + 1 ;
852+ wrong .put (Common .WRON_COUNT , newWrongCount );
845853 WRONG_PWD_TRIES .put (userId , wrong );
846854
847- context .renderMsg (langPropsService .get ("wrongPwdLabel" ));
855+ if (newWrongCount > 2 ) {
856+ LOGIN_IP_LOCK .put (ip , System .currentTimeMillis () + 5 * 60 * 1000 );
857+ context .renderMsg ("尝试次数过多,已暂时锁定 5 分钟" );
858+ } else {
859+ context .renderMsg (langPropsService .get ("wrongPwdLabel" ));
860+ }
848861 } catch (final ServiceException e ) {
849862 context .renderMsg (langPropsService .get ("loginFailLabel" ));
850863 }
0 commit comments