Prechádzať zdrojové kódy

网易云信的短信验证

shimingxy 5 rokov pred
rodič
commit
710299b78f
21 zmenil súbory, kde vykonal 924 pridanie a 660 odobranie
  1. 27 11
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/AbstractOptAuthn.java
  2. 7 4
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/OneTimePassword.java
  3. 6 0
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/CapOtpAuthn.java
  4. 4 0
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/CounterBasedOtpAuthn.java
  5. 4 0
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/HotpOtpAuthn.java
  6. 4 0
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/MailOtpAuthn.java
  7. 6 0
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/MobileOtpAuthn.java
  8. 6 0
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/RsaOtpAuthn.java
  9. 0 1
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/TimeBasedOtpAuthn.java
  10. 0 1
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/package-info.java
  11. 161 59
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/sms/netease/SmsOtpAuthnYunxin.java
  12. 6 1
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/AbstractOptTokenStore.java
  13. 47 4
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/InMemoryOptTokenStore.java
  14. 8 5
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/JdbcOptTokenStore.java
  15. 42 1
      maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/RedisOptTokenStore.java
  16. 87 96
      maxkey-core/src/main/java/org/maxkey/util/JsonUtils.java
  17. 452 460
      maxkey-core/src/main/java/org/maxkey/util/StringUtils.java
  18. 25 0
      maxkey-web-maxkey/src/main/java/org/maxkey/web/endpoint/LoginEndpoint.java
  19. 8 11
      maxkey-web-maxkey/src/main/resources/config/applicationConfig.properties
  20. 5 1
      maxkey-web-maxkey/src/main/resources/spring/maxkey-security.xml
  21. 19 5
      maxkey-web-maxkey/src/main/resources/templates/views/login.ftl

+ 27 - 11
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/AbstractOptAuthn.java

@@ -1,5 +1,7 @@
 package org.maxkey.crypto.password.opt;
 
+import org.maxkey.crypto.password.opt.token.AbstractOptTokenStore;
+import org.maxkey.crypto.password.opt.token.InMemoryOptTokenStore;
 import org.maxkey.domain.UserInfo;
 import org.maxkey.util.StringGenerator;
 import org.slf4j.Logger;
@@ -13,29 +15,35 @@ import org.slf4j.LoggerFactory;
 public abstract class AbstractOptAuthn {
     private static final  Logger logger = LoggerFactory.getLogger(AbstractOptAuthn.class);
 
+    protected AbstractOptTokenStore optTokenStore = new InMemoryOptTokenStore();
+    
+    //验证码有效間隔
     protected int interval = 30;
-
+    
+    // 验证码长度,范围4~10,默认为6
     protected int digits = 6;
 
     protected String crypto = "HmacSHA1";
 
     StringGenerator stringGenerator;
+    
+    protected String optType = OptTypes.TIMEBASED_OPT;
 
     public static final class OptTypes {
         // 手机
-        public static int MOBILE = 2;
+        public static String  MOBILE = "MOBILE";
         // 短信
-        public static int SMS = 3;
+        public static String SMS = "SMS";
         // 邮箱
-        public static int EMAIL = 4;
-
-        public static int TIMEBASED_OPT = 5;
-
-        public static int COUNTERBASED_OPT = 6;
-
-        public static int HOTP_OPT = 7;
+        public static String EMAIL = "EMAIL";
+        //TIMEBASED_OPT
+        public static String TIMEBASED_OPT = "TOPT";
+        // HmacOTP
+        public static String HOTP_OPT = "HOTP";
 
-        public static int RSA_OPT = 8;
+        public static String RSA_OPT = "RSA";
+        
+        public static String CAP_OPT = "CAP";
 
     }
 
@@ -109,5 +117,13 @@ public abstract class AbstractOptAuthn {
         this.crypto = crypto;
     }
 
+    public String getOptType() {
+        return optType;
+    }
+
+    public void setOptType(String optType) {
+        this.optType = optType;
+    }
+
  
 }

+ 7 - 4
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/OneTimePassword.java

@@ -1,8 +1,11 @@
 package org.maxkey.crypto.password.opt;
 
-public class OneTimePassword {
+import java.io.Serializable;
+
+public class OneTimePassword  implements Serializable {
+    private static final long serialVersionUID = -1637133296702014021L;
     private String id;
-    private int type;
+    private String type;
     private String token;
     private String username;
     private String receiver;
@@ -16,11 +19,11 @@ public class OneTimePassword {
         this.id = id;
     }
 
-    public int getType() {
+    public String getType() {
         return type;
     }
 
-    public void setType(int type) {
+    public void setType(String type) {
         this.type = type;
     }
 

+ 6 - 0
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/CapOtpAuthn.java

@@ -15,6 +15,12 @@ import org.maxkey.domain.UserInfo;
  */
 public class CapOtpAuthn extends AbstractOptAuthn {
 
+    
+    
+    public CapOtpAuthn() {
+        optType = OptTypes.CAP_OPT;
+    }
+
     @Override
     public boolean produce(UserInfo userInfo) {
         // TODO Auto-generated method stub

+ 4 - 0
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/CounterBasedOtpAuthn.java

@@ -12,6 +12,10 @@ public class CounterBasedOtpAuthn extends AbstractOptAuthn {
     private static final Logger _logger = LoggerFactory.getLogger(CounterBasedOtpAuthn.class);
 
 
+    public CounterBasedOtpAuthn() {
+        optType = OptTypes.HOTP_OPT;
+    }
+
     @Override
     public boolean produce(UserInfo userInfo) {
         return true;

+ 4 - 0
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/HotpOtpAuthn.java

@@ -15,6 +15,10 @@ public class HotpOtpAuthn extends AbstractOptAuthn {
     boolean addChecksum;
     int truncation = -1;
 
+    public HotpOtpAuthn() {
+        optType = OptTypes.HOTP_OPT;
+    }
+
     @Override
     public boolean produce(UserInfo userInfo) {
         return true;

+ 4 - 0
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/MailOtpAuthn.java

@@ -13,6 +13,10 @@ public class MailOtpAuthn extends AbstractOptAuthn {
     private static final Logger _logger = LoggerFactory.getLogger(MailOtpAuthn.class);
     EmailConfig emailConfig;
 
+    public MailOtpAuthn() {
+        optType = OptTypes.EMAIL;
+    }
+
     @Override
     public boolean produce(UserInfo userInfo) {
         try {

+ 6 - 0
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/MobileOtpAuthn.java

@@ -5,6 +5,12 @@ import org.maxkey.domain.UserInfo;
 
 public class MobileOtpAuthn extends AbstractOptAuthn {
 
+    
+    
+    public MobileOtpAuthn() {
+        optType = OptTypes.SMS;
+    }
+
     @Override
     public boolean produce(UserInfo userInfo) {
         // TODO Auto-generated method stub

+ 6 - 0
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/RsaOtpAuthn.java

@@ -15,6 +15,12 @@ import org.maxkey.domain.UserInfo;
  */
 public class RsaOtpAuthn extends AbstractOptAuthn {
 
+    
+    
+    public RsaOtpAuthn() {
+        optType = OptTypes.RSA_OPT;
+    }
+
     @Override
     public boolean produce(UserInfo userInfo) {
         // TODO Auto-generated method stub

+ 0 - 1
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/TimeBasedOtpAuthn.java

@@ -22,7 +22,6 @@ public class TimeBasedOtpAuthn extends AbstractOptAuthn {
 
     @Override
     public boolean produce(UserInfo userInfo) {
-        // TODO Auto-generated method stub
         return true;
     }
 

+ 0 - 1
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/package-info.java

@@ -1 +0,0 @@
-package org.maxkey.crypto.password.opt.impl;

+ 161 - 59
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/impl/sms/netease/SmsOtpAuthnYunxin.java

@@ -12,90 +12,192 @@ import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.util.EntityUtils;
 import org.maxkey.crypto.password.opt.impl.SmsOtpAuthn;
+import org.maxkey.domain.UserInfo;
+import org.maxkey.util.JsonUtils;
+import org.maxkey.util.StringGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * 网易云信的短信验证.
  * @author shimingxy
  *
  */
-
 public class SmsOtpAuthnYunxin extends SmsOtpAuthn {
+    private static final  Logger logger = LoggerFactory.getLogger(SmsOtpAuthnYunxin.class);
+    
     public SmsOtpAuthnYunxin() {
-
+        optType = OptTypes.SMS;
     }
 
     //发送验证码的请求路径URL
     private static final String
             SERVER_URL = "https://api.netease.im/sms/sendcode.action";
     //网易云信分配的账号,请替换你在管理后台应用下申请的Appkey
-    private static final String
-            APP_KEY = "94395d754eb55693043f5d6a2b772ef3";
+    private   String
+            appKey = "94395d754eb55693043f5d6a2b772ef3";
     //网易云信分配的密钥,请替换你在管理后台应用下申请的appSecret
-    private static final String APP_SECRET = "05d5485357bc";
-    // 随机数
-    private static final String NONCE = "123456";
+    private  String appSecret = "05d5485357bc";
     // 短信模板ID
-    private static final String TEMPLATEID = "14850150";
-    // 手机号
-    private static final String MOBILE = "15618726256";
-    // 验证码长度,范围4~10,默认为4
-    private static final String CODELEN = "6";
+    private  String templateId = "14860099";
     
-    /**
-     * .
-     * @param args String
-     * @throws Exception e
-     */
-    public static void sendSms(String[] args) throws Exception {
+    @Override
+    public boolean produce(UserInfo userInfo) {
+        HttpPost httpPost = null;
+        // 手机号
+        String mobile = userInfo.getMobile();
+        if (mobile != null && !mobile.equals("")) {
+            try {
+                httpPost = new HttpPost(SERVER_URL);
+                String curTime = String.valueOf((new Date()).getTime() / 1000L);
+                /*
+                 * 参考计算CheckSum的java代码,在上述文档的参数列表中,有CheckSum的计算文档示例
+                 */
+                // 随机数
+                String nonce = new StringGenerator(
+                        StringGenerator.DEFAULT_CODE_NUMBER,
+                        6
+                    ).randomGenerate();
+                String checkSum = SmsOtpAuthnYunxinCheckSumBuilder
+                        .getCheckSum(appSecret, nonce, curTime);
+        
+                // 设置请求的header
+                httpPost.addHeader("AppKey", appKey);
+                httpPost.addHeader("Nonce", nonce);
+                httpPost.addHeader("CurTime", curTime);
+                httpPost.addHeader("CheckSum", checkSum);
+                httpPost.addHeader("Content-Type", 
+                        "application/x-www-form-urlencoded;charset=utf-8");
         
-        HttpPost httpPost = new HttpPost(SERVER_URL);
-        String curTime = String.valueOf((new Date()).getTime() / 1000L);
-        /*
-         * 参考计算CheckSum的java代码,在上述文档的参数列表中,有CheckSum的计算文档示例
-         */
-        String checkSum = SmsOtpAuthnYunxinCheckSumBuilder
-                .getCheckSum(APP_SECRET, NONCE, curTime);
+                // 设置请求的的参数,requestBody参数
+                List<NameValuePair> nvps = new ArrayList<NameValuePair>();
+                /*
+                 * 1.如果是模板短信,请注意参数mobile是有s的,详细参数配置请参考“发送模板短信文档”
+                 * 2.参数格式是jsonArray的格式,例如 "['13888888888','13666666666']"
+                 * 3.params是根据你模板里面有几个参数,那里面的参数也是jsonArray格式
+                 */
+                //https://api.netease.im/sms/sendcode.action
+                nvps.add(new BasicNameValuePair("templateid", templateId));
+                nvps.add(new BasicNameValuePair("mobile", userInfo.getMobile()));
+                nvps.add(new BasicNameValuePair("codeLen", digits + ""));
+                //authCode 用户自定义验证码
+                //nvps.add(new BasicNameValuePair("authCode", ""));
+                //https://api.netease.im/sms/verifycode.action
+                //nvps.add(new BasicNameValuePair("code", "123456"));
+                
+                httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));
+                HttpClient httpClient = HttpClientBuilder.create().build();
+                // 执行请求
+                HttpResponse response = httpClient.execute(httpPost);
+                /*
+                 * 1.打印执行结果,打印结果一般会200、315、403、404、413、414、500
+                 * 2.具体的code有问题的可以参考官网的Code状态表
+                 */
+                //{"code":200,"msg":"1","obj":"740673"}
+                String responseString = EntityUtils.toString(response.getEntity(), "utf-8");
+                //String responseString = "{\"code\":200,\"msg\":\"1\",\"obj\":\"740673\"}";
+                logger.debug("responseString " + responseString);
+                YunxinSms  yunxinSms = 
+                        JsonUtils.gson2Object(responseString,YunxinSms.class);
+                logger.debug("responseEntity code " + yunxinSms.getObj());
+                this.optTokenStore.store(
+                                        userInfo, 
+                                        yunxinSms.getObj(), 
+                                        userInfo.getMobile(), 
+                                        OptTypes.SMS);
+                return true;
+            } catch  (Exception e) {
+                logger.error(" produce code error ", e);
+            } finally {
+                if (httpPost != null) {
+                    httpPost.releaseConnection();
+                }
+            }
+        }
+        return false;
+    }
 
-        // 设置请求的header
-        httpPost.addHeader("AppKey", APP_KEY);
-        httpPost.addHeader("Nonce", NONCE);
-        httpPost.addHeader("CurTime", curTime);
-        httpPost.addHeader("CheckSum", checkSum);
-        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
+    @Override
+    public boolean validate(UserInfo userInfo, String token) {
+        return this.optTokenStore.validate(userInfo, token, OptTypes.SMS, interval);
+    }
 
-        // 设置请求的的参数,requestBody参数
-        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
-        /*
-         * 1.如果是模板短信,请注意参数mobile是有s的,详细参数配置请参考“发送模板短信文档”
-         * 2.参数格式是jsonArray的格式,例如 "['13888888888','13666666666']"
-         * 3.params是根据你模板里面有几个参数,那里面的参数也是jsonArray格式
-         */
-        //https://api.netease.im/sms/sendcode.action
-        nvps.add(new BasicNameValuePair("templateid", TEMPLATEID));
-        nvps.add(new BasicNameValuePair("mobile", MOBILE));
-        nvps.add(new BasicNameValuePair("codeLen", CODELEN));
-        //authCode 用户自定义验证码
-        //nvps.add(new BasicNameValuePair("authCode", ""));
-        //https://api.netease.im/sms/verifycode.action
-        //nvps.add(new BasicNameValuePair("code", "123456"));
-        
-        
+    
+    
+    public String getAppKey() {
+        return appKey;
+    }
 
-        httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));
-        HttpClient httpClient = HttpClientBuilder.create().build();
-        // 执行请求
-        HttpResponse response = httpClient.execute(httpPost);
-        /*
-         * 1.打印执行结果,打印结果一般会200、315、403、404、413、414、500
-         * 2.具体的code有问题的可以参考官网的Code状态表
-         */
-        System.out.println(EntityUtils.toString(response.getEntity(), "utf-8"));
-        //{"code":200,"msg":"1","obj":"740673"}
+    public void setAppKey(String appKey) {
+        this.appKey = appKey;
+    }
+
+    public String getAppSecret() {
+        return appSecret;
+    }
 
+    public void setAppSecret(String appSecret) {
+        this.appSecret = appSecret;
+    }
+
+    public String getTemplateId() {
+        return templateId;
+    }
+
+    public void setTemplateId(String templateId) {
+        this.templateId = templateId;
+    }
+
+
+
+    public class YunxinSms {
+        int code;
+        String msg;
+        String obj;
+        
+        public YunxinSms() {
+        }
+
+        public int getCode() {
+            return code;
+        }
+        
+        public void setCode(int code) {
+            this.code = code;
+        }
+        
+        public String getMsg() {
+            return msg;
+        }
+        
+        public void setMsg(String msg) {
+            this.msg = msg;
+        }
+        
+        public String getObj() {
+            return obj;
+        }
+        
+        public void setObj(String obj) {
+            this.obj = obj;
+        }
+        
     }
     
+    /**
+     * main.
+     * @param args String
+     * @throws Exception throws
+     */
     public static void main(String[] args) throws Exception {
-        sendSms(null);
+        String nonce = new StringGenerator(
+                StringGenerator.DEFAULT_CODE_NUMBER,
+                6
+            ).randomGenerate();
+        System.out.println(nonce);
+        String mapJson = "{\"code\":200,\"msg\":\"1\",\"obj\":\"740673\"}";
+        YunxinSms  yunxinSms = JsonUtils.gson2Object(mapJson,YunxinSms.class);  
+        System.out.println("code " + yunxinSms.getObj());
     }
     
 }

+ 6 - 1
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/AbstractOptTokenStore.java

@@ -1,5 +1,10 @@
 package org.maxkey.crypto.password.opt.token;
 
-public class AbstractOptTokenStore {
+import org.maxkey.domain.UserInfo;
 
+public abstract class AbstractOptTokenStore {
+    
+    public abstract void store(UserInfo userInfo, String token, String receiver, String type);
+    
+    public abstract boolean validate(UserInfo userInfo, String token, String type,int interval);
 }

+ 47 - 4
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/InMemoryOptTokenStore.java

@@ -1,19 +1,62 @@
 package org.maxkey.crypto.password.opt.token;
 
-import java.time.Duration;
 import org.ehcache.UserManagedCache;
 import org.ehcache.config.builders.ExpiryPolicyBuilder;
 import org.ehcache.config.builders.UserManagedCacheBuilder;
+import org.joda.time.DateTime;
+import org.joda.time.Duration;
+import org.joda.time.format.DateTimeFormat;
 import org.maxkey.constants.ConstantsTimeInterval;
 import org.maxkey.crypto.password.opt.OneTimePassword;
+import org.maxkey.domain.UserInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public class InMemoryOptTokenStore {
-    protected static final UserManagedCache<String, OneTimePassword> remeberMeStore = 
+public class InMemoryOptTokenStore  extends AbstractOptTokenStore {
+    private static final  Logger logger = LoggerFactory.getLogger(InMemoryOptTokenStore.class);
+    
+    protected static final UserManagedCache<String, OneTimePassword> optTokenStore = 
             UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, OneTimePassword.class)
                 .withExpiry(
                     ExpiryPolicyBuilder.timeToLiveExpiration(
-                        Duration.ofMinutes(ConstantsTimeInterval.TWO_WEEK)
+                        java.time.Duration.ofMinutes(ConstantsTimeInterval.ONE_MINUTE * 5)
                     )
                 )
                 .build(true);
+
+    @Override
+    public void store(UserInfo userInfo, String token, String receiver, String type) {
+        DateTime currentDateTime = new DateTime();
+        OneTimePassword otp = new OneTimePassword();
+        otp.setId(userInfo.getUsername() + "_" + type + "_" + token);
+        otp.setType(type);
+        otp.setUsername(userInfo.getUsername());
+        otp.setToken(token);
+        otp.setReceiver(receiver);
+        otp.setCreateTime(currentDateTime.toString("yyyy-MM-dd HH:mm:ss"));
+        optTokenStore.put(otp.getId(), otp);
+        
+    }
+
+    @Override
+    public boolean validate(UserInfo userInfo, String token, String type, int interval) {
+        OneTimePassword otp = optTokenStore.get(userInfo.getUsername() + "_" + type + "_" + token);
+        if (otp != null) {
+            DateTime currentdateTime = new DateTime();
+            DateTime oneCreateTime = DateTime.parse(otp.getCreateTime(),
+                    DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"));
+            Duration duration = new Duration(oneCreateTime, currentdateTime);
+            int intDuration = Integer.parseInt(duration.getStandardSeconds() + "");
+            logger.debug("validate duration " + intDuration);
+            logger.debug("validate result " + (intDuration <= interval));
+            if (intDuration <= interval) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public InMemoryOptTokenStore() {
+        
+    }    
 }

+ 8 - 5
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/JdbcOptTokenStore.java

@@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 
-public class JdbcOptTokenStore {
+public class JdbcOptTokenStore extends AbstractOptTokenStore {
     private static final  Logger logger = LoggerFactory.getLogger(JdbcOptTokenStore.class);
     
     private static final String DEFAULT_DEFAULT_INSERT_STATEMENT = 
@@ -38,7 +38,10 @@ public class JdbcOptTokenStore {
     }
     
  
-    protected void store(UserInfo userInfo, String token, String receiver, int type) {
+    /**
+     *store.
+     */
+    public void store(UserInfo userInfo, String token, String receiver, String type) {
         jdbcTemplate.update(DEFAULT_DEFAULT_INSERT_STATEMENT,
                 new Object[] { 
                         java.util.UUID.randomUUID(), 
@@ -48,7 +51,7 @@ public class JdbcOptTokenStore {
                         receiver, 
                         new Date() 
                 },
-                new int[] { Types.VARCHAR, Types.INTEGER, 
+                new int[] { Types.VARCHAR, Types.VARCHAR, 
                         Types.VARCHAR, Types.VARCHAR, 
                         Types.VARCHAR,Types.TIMESTAMP 
                 }
@@ -62,7 +65,7 @@ public class JdbcOptTokenStore {
      * @param type int
      * @return
      */
-    public boolean validate(UserInfo userInfo, String token, int type,int interval) {
+    public boolean validate(UserInfo userInfo, String token, String type,int interval) {
         OneTimePassword oneTimePassword = jdbcTemplate.queryForObject(
                 DEFAULT_DEFAULT_SELECT_STATEMENT,
                 new OneTimePasswordRowMapper(), userInfo.getUsername(), token, type);
@@ -97,7 +100,7 @@ public class JdbcOptTokenStore {
         public OneTimePassword mapRow(ResultSet rs, int rowNum) throws SQLException {
             OneTimePassword oneTimePassword = new OneTimePassword();
             oneTimePassword.setId(rs.getString("ID"));
-            oneTimePassword.setType(rs.getInt("OPTTYPE"));
+            oneTimePassword.setType(rs.getString("OPTTYPE"));
             oneTimePassword.setUsername(rs.getString("USERNAME"));
             oneTimePassword.setToken(rs.getString("TOKEN"));
             oneTimePassword.setUsername(rs.getString("USERNAME"));

+ 42 - 1
maxkey-core/src/main/java/org/maxkey/crypto/password/opt/token/RedisOptTokenStore.java

@@ -1,5 +1,46 @@
 package org.maxkey.crypto.password.opt.token;
 
-public class RedisOptTokenStore {
+import org.joda.time.DateTime;
+import org.maxkey.constants.ConstantsTimeInterval;
+import org.maxkey.crypto.password.opt.OneTimePassword;
+import org.maxkey.domain.UserInfo;
+import org.maxkey.persistence.redis.RedisConnection;
+import org.maxkey.persistence.redis.RedisConnectionFactory;
+
+public class RedisOptTokenStore  extends AbstractOptTokenStore {
+    
+    protected int validitySeconds = ConstantsTimeInterval.ONE_MINUTE * 5;
+    
+    RedisConnectionFactory connectionFactory;
+    
+    public static String PREFIX = "REDIS_OTP_SERVICE_";
+    
+    @Override
+    public void store(UserInfo userInfo, String token, String receiver, String type) {
+        DateTime currentDateTime = new DateTime();
+        OneTimePassword otp = new OneTimePassword();
+        otp.setId(userInfo.getUsername() + "_" + type + "_" + token);
+        otp.setType(type);
+        otp.setUsername(userInfo.getUsername());
+        otp.setToken(token);
+        otp.setReceiver(receiver);
+        otp.setCreateTime(currentDateTime.toString("yyyy-MM-dd HH:mm:ss"));
+        RedisConnection conn = connectionFactory.getConnection();
+        conn.setexObject(PREFIX + otp.getId(), validitySeconds, otp);
+        conn.close();
+    }
+
+    @Override
+    public boolean validate(UserInfo userInfo, String token, String type, int interval) {
+        RedisConnection conn = connectionFactory.getConnection();
+        OneTimePassword otp = (OneTimePassword)conn.getObject(
+                PREFIX + userInfo.getUsername() + "_" + type + "_" + token);
+        conn.delete(PREFIX + userInfo.getUsername() + "_" + type + "_" + token);
+        conn.close();
+        if (otp != null) { 
+            return true;
+        }
+        return false;
+    }
 
 }

+ 87 - 96
maxkey-core/src/main/java/org/maxkey/util/JsonUtils.java

@@ -1,110 +1,101 @@
 package org.maxkey.util;
 
-import java.io.IOException;
-
 import com.fasterxml.jackson.core.JsonGenerationException;
 import com.fasterxml.jackson.core.JsonParseException;
 import com.fasterxml.jackson.databind.JsonMappingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.Gson;
+import java.io.IOException;
 
 public class JsonUtils {
 
-	/**
-	 * Transform json string to java bean object
-	 * @param json
-	 * @param bean
-	 * @return Object
-	 */
-	public static Object json2Object(String json,Object bean){
-		try {
-			bean=(new ObjectMapper()).readValue(json, bean.getClass());
-		} catch (JsonParseException e) {
-			e.printStackTrace();
-		} catch (JsonMappingException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}finally{
-			
-		}
-		return bean;
-	}
-	
-	/**
-	 * Transform json string to java bean object
-	 * @param json
-	 * @param Class
-	 * @return Object
-	 */
-	public static <T>  T json2Object(String json,Class<T> cls){
-		T bean =null;
-		try {
-			bean=(new ObjectMapper()).readValue(json, cls);
-		} catch (JsonParseException e) {
-			e.printStackTrace();
-		} catch (JsonMappingException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}finally{
-			
-		}
-		return bean;
-	}
-	
-	/**
-	 * Transform  java bean object to json string
-	 * @param bean
-	 * @return string
-	 */
-	public static String object2Json(Object bean){
-		String json="";
-		try {
-			json= (new ObjectMapper()).writeValueAsString(bean);
-		} catch (JsonGenerationException e) {
-			e.printStackTrace();
-		} catch (JsonMappingException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}finally{
-			
-		}
-		return json;
-	}
-	
+    /**
+     * Transform json string to java bean object.
+     * 
+     * @param json String
+     * @param bean Object 
+     * @return Object 
+     */
+    public static Object json2Object(String json, Object bean) {
+        try {
+            bean = (new ObjectMapper()).readValue(json, bean.getClass());
+        } catch (JsonParseException e) {
+            e.printStackTrace();
+        } catch (JsonMappingException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } 
+        return bean;
+    }
+
+    /**
+     * Transform json string to java bean object.
+     * 
+     * @param json String
+     * @param cls Class
+     * @return Object
+     */
+    public static <T> T json2Object(String json, Class<T> cls) {
+        T bean = null;
+        try {
+            bean = (new ObjectMapper()).readValue(json, cls);
+        } catch (JsonParseException e) {
+            e.printStackTrace();
+        } catch (JsonMappingException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } 
+        return bean;
+    }
+
+    /**
+     * Transform java bean object to json string.
+     * 
+     * @param bean Object
+     * @return string
+     */
+    public static String object2Json(Object bean) {
+        String json = "";
+        try {
+            json = (new ObjectMapper()).writeValueAsString(bean);
+        } catch (JsonGenerationException e) {
+            e.printStackTrace();
+        } catch (JsonMappingException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } 
+        return json;
+    }
+
+    /**
+     * Transform json string to java bean object use Gson.
+     * 
+     * @param <T> Class
+     * @param json String
+     * @return Object
+     */
+
+    public static <T> T gson2Object(String json, Class<T> cls) {
+        T newBean = (new Gson()).fromJson(json, cls);
+        return newBean;
+    }
 
-	
-	
-	/**
-	 * Transform json string to java bean object use Gson
-	 * @param <T>
-	 * @param json
-	 * @param Class
-	 * @return Object
-	 */
+    /**
+     * Transform java bean object to json string use Gson.
+     * 
+     * @param bean Object
+     * @return string
+     */
+    public static String gson2Json(Object bean) {
+        String json = "";
+        // convert java object to JSON format,
+        // and returned as JSON formatted string
+        json = (new Gson()).toJson(bean);
 
-	public static <T>  T gson2Object(String json,Class<T> cls){
-		T newBean  = (new Gson()).fromJson(json, cls);
-		return newBean;
-	}
-	
-	
-	/**
-	 * Transform  java bean object to json string use Gson
-	 * @param bean
-	 * @return string
-	 */
-	public static String gson2Json(Object bean){
-		String json="";
-		// convert java object to JSON format,
-		// and returned as JSON formatted string
-		json = (new Gson()).toJson(bean);
-		
-		return json;
-	}
-	
+        return json;
+    }
 
-	
 }

+ 452 - 460
maxkey-core/src/main/java/org/maxkey/util/StringUtils.java

@@ -10,464 +10,456 @@ import java.util.Map;
 import java.util.regex.Pattern;
 
 public final class StringUtils {
-	
-	/**
-	 * avoid null, and return value trim.
-	 * @param value string value.
-	 * @return the trim of value.
-	 */
-	public static String avoidNull(String value) {
-		return (value == null) ? "" : value.trim();
-	}
-	
-	public static boolean isNull(String value) {
-		return value == null;
-	}
-
-	/**
-	 * @param value string
-	 * value
-	 * @return value
-	 */
-	public static boolean isNullOrBlank(String value) {
-		return value == null || "".equals(value.trim());
-	}
-
-	public static boolean isNotEmpty(String value) {
-		return !isNullOrBlank(value);
-	}
-	
-	public static boolean isNotNullAndEquals(String value,String equalString) {
-		return !isNullOrBlank(value)&&value.equals(equalString);
-	}
-	
-	public static boolean isNotNullAndEqualsIgnoreCase(String value,String equalString) {
-		return !isNullOrBlank(value)&&value.equalsIgnoreCase(equalString);
-	}
-	
-	/*
-	 * 获取指定UTF-8模式字节长度的字符串
-	 */
-	public static String limitLength(String strValue, int bytelen){
-		
-		//中文汉字占用三个字节
-		int strlen = bytelen/3;
-		int real_bytelen = strlen*3;
-		
-		//如果为NULL或�?空,则直接返�?
-		if(null==strValue || "".equalsIgnoreCase(strValue)){
-			return "";
-		}
-		
-		try{
-			byte[] utf8_bytes = strValue.getBytes("UTF-8");
-			if(utf8_bytes.length<=bytelen) return strValue;
-			
-			byte[] cutoff_bytes = new byte[real_bytelen];
-			System.arraycopy(utf8_bytes, 0, cutoff_bytes, 0, real_bytelen);
-			
-			String strResult = new String(cutoff_bytes, "UTF-8");
-			
-			return strResult;
-			
-		}catch(Exception e){
-			if(strValue.length()<strlen) return strValue;
-			return strValue.substring(0, strlen);
-		}
-		
-	}
-	
-	/**
-	 * 对url进行编码�?
-	 * @param ori_url 要编码的url
-	 * @return 返回url
-	 */
-	public static String urlEncode(String url){
-		try {
-			String tempstr = URLEncoder.encode(url,"UTF-8");
-			return tempstr.replaceAll("\\+", "%20");
-		} catch (UnsupportedEncodingException e) {
-			e.printStackTrace();
-			return url;
-		}
-	}
-	
-	
-	/**
-	 * 编码url
-	 * @param ori_url
-	 * @return
-	 */
-	public static String urlDecode(String url){
-		try {
-			
-			String tempstr = URLDecoder.decode(url.replaceAll("%20", "\\+"),"UTF-8");
-			return tempstr;
-		} catch (UnsupportedEncodingException e) {
-			e.printStackTrace();
-			return url;
-		}
-	}
-	
-	
-	/**
-	 * �?��字符串是否包含特殊字�?
-	 * @param str
-	 * @return
-	 */
-	public static Boolean specialWord(String string)
-	{
-		String regEx="[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#�?…�?&*()—�?+|{}【�?‘;:�?“�?。,、?]";
-		return Pattern.compile(regEx).matcher(string).find();
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static Boolean startWithUpper(String string) {
-		return Pattern.compile("^[A-Z]").matcher(string).find();
-	}
-	
-
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static Boolean startWithLower(String string) {
-		return Pattern.compile("^[a-z]").matcher(string).find();
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static Boolean startWithNumber(String string) {
-		return Pattern.compile("^[0-9]").matcher(string).find();
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static Boolean containsLower(String string) {
-		return Pattern.compile("[a-z]").matcher(string).find();
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static Boolean containsUpper(String string) {
-		return Pattern.compile("[A-Z]").matcher(string).find();
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static Boolean containsChinese(String string)
-	{
-		String regEx = "[\u2E80-\u9FFF]+$";		
-		return Pattern.compile(regEx).matcher(string).find();
-	}
-	/**
-	 * 密码不包含全部或部分的用户账户名
-	 * �?��str2中是否包含str中全部或部分的数�?
-	 * @param str
-	 * @param str2
-	 * @return
-	 */
-	public static Boolean containsPartOrAll(String string,String string2) {
-		if(isNotEmpty(string) && isNotEmpty(string2)) {
-			 return Pattern.compile("["+string+"]").matcher(string2).find();
-		}
-		return false;
-	}
-	
-	public static boolean containsSpace(String string) {
-		return string.lastIndexOf(" ") != -1;
-	}
-	
-	public static Boolean isNumber(String string) {
-		return Pattern.compile("[0-9]").matcher(string).find();
-	}
-	
-	/**
-	 * 返回字符串中包含的大写字母的
-	 * @param str
-	 * @return
-	 */
-	public static int countUpper(String string) {
-		int count = 0;
-		for(int i = 0; i < string.toCharArray().length; i++) {
-			if(containsUpper(String.valueOf(string.charAt(i)))) {
-				count++;
-			}
-		}
-		return count;
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static int countLower(String string) {
-		int count = 0;
-		for(int i = 0; i < string.toCharArray().length; i++) {
-			if(containsLower(String.valueOf(string.charAt(i)))) {
-				count++;
-			}
-		}
-		return count;
-	}
-	
-	public static int countNumber(String string) {
-		int count = 0;
-		for(int i = 0; i < string.toCharArray().length; i++) {
-			if(isNumber(String.valueOf(string.charAt(i)))) {
-				count++;
-			}
-		}
-		return count;
-	}
-	
-	/**
-	 * @param str
-	 * @return
-	 */
-	public static int countSpecialWord(String string) {
-		int count = 0;
-		for(int i = 0; i < string.toCharArray().length; i++) {
-			if(specialWord(String.valueOf(string.charAt(i)))) {
-				count++;
-			}
-		}
-		return count;
-	}
-	
-	public static List<String> string2List(String string, String split) {
-		String[] strs = {};
-		if (string != null && !string.equals("")) {
-			strs = string.split(split);
-		}
-		ArrayList<String> resultList = new ArrayList<String>(0);
-		for (int i = 0; i < strs.length; i++) {
-			if (strs[i] != null&& !strs[i].equals("")) {
-				resultList.add(strs[i]);
-			}
-		}
-		resultList.trimToSize();
-		return resultList;
-	}
-
-	public static String list2String(List<String> list, String split) {
-		String string = "";
-		if (list == null)
-			return string;
-		for (int i = 0; i < list.size(); i++) {
-			if (list.get(i) != null && !list.get(i).equals("")) {
-				string += list.get(i) + split;
-			}
-		}
-		if (string.length() > 0) {
-			string = string.substring(0, string.length() - 1);
-		}
-		return string;
-	}
-    
-	
-	public static int parse2Integer(String string) {
-		Integer value = 0;
-		try {
-			value = Integer.parseInt(string);
-		} catch(Exception e){
-			throw new RuntimeException("parse "+string+" to  Integer error.");
-		}
-		return value;
-	}
-	
-	
-	/**
-	 * 处理如id=name形式的字符串
-	 * @param proValue
-	 * @param key
-	 * @param value
-	 * @return
-	 */
-	public static Map<String,List<String>> processStr(String proValue,String key,String value) {
-		Map<String,List<String>> map = new HashMap<String, List<String>>();
-		List<String> idList = new ArrayList<String>();
-		List<String> nameList = new ArrayList<String>();
-		if(StringUtils.isNotEmpty(proValue)) {
-			List<String> list = StringUtils.string2List(proValue, ",");
-			for(String str : list) {
-				idList.add(str.split("\\,")[0]);
-				nameList.add(str.split("\\,")[1]);
-			}
-		}
-		map.put(key, idList);
-		map.put(value, nameList);
-		return map;
-	}
-	
-	/**
-	 * 获得集合对象中某�?��性字段的值,中间用split隔开
-	 * @param list 集合对象
-	 * @param propName 属�?名称
-	 * @param split 分隔�?
-	 * @return
-	 */
-	@SuppressWarnings("rawtypes")
-	public static String convertPropVal(List list,String propName,String split) {
-		String retVal = "";
-		for(Object obj : list) {
-			if(StringUtils.isNotEmpty(retVal)) {
-				retVal += split;
-			}
-			retVal += BeanUtil.getValue(obj, propName);
-		}
-		return retVal;
-	}
-	
-	/**
-	 * 将对象类型的集合转换为String类型的集�?
-	 * @param list
-	 * @param propName
-	 * @return
-	 */
-	@SuppressWarnings("rawtypes")
-	public static List<String> convertList(List list, String propName) {
-		List<String> strList = new ArrayList<String>();
-		for(Object obj : list) {
-			strList.add(BeanUtil.getValue(obj, propName));
-		}
-		return strList;
-	}
-	
-	/**
-	 * 将对象类型的集合转换为String类型的Map集合
-	 * @param list
-	 * @param propNames
-	 * @return
-	 */
-	@SuppressWarnings("rawtypes")
-	public static Map<String,List<String>> convertListToMap(List list, String ... propNames) {
-		Map<String,List<String>> map = new HashMap<String, List<String>>();
-		for(String name : propNames) {
-			List<String> strList = new ArrayList<String>();
-			for(Object obj : list) {
-				strList.add(BeanUtil.getValue(obj, name));
-			}
-			map.put(name, strList);
-		}
-		return map;
-	}
-	
-	public static String convertWeekStr(String str,String split) {
-		String retVal = "";
-		try {
-			if(StringUtils.isNotEmpty(str)) {
-				String []array = str.split("\\"+split);
-				for(int i = 0; i < array.length; i++) {
-					if(StringUtils.isNotEmpty(retVal)) {
-						retVal += split;
-					}
-					retVal += (Integer.parseInt(array[i]) + 1);
-				}
-			}
-		} catch(NumberFormatException e) {
-			e.printStackTrace();
-		} 
-		return retVal;
-	}
-	
-	
-	public static List<String> convertToSingleList(List<String> list){
-		return convertToSingleList(list,",");
-	}
-	
-	public static List<String> convertToSingleList(List<String> list,String reg){
-		List<String> result = new ArrayList<String>();
-		if(list!=null && list.size()>0){
-			for(String str:list){
-				String[] arrStr = str.split(reg);
-				for(int i=0;i<arrStr.length; i++){
-					result.add(arrStr[i]);
-				}
-			}
-		}
-		return result;
-	}
-	
-    /** 
-    * 汉字转换位汉语拼音
-    * @param hanYu Chinese
-    * @param first true is Convert first,else all
-    * @return 拼音 
-    */  
- /*   public static String hanYu2Pinyin(String  hanYu,boolean first){         
-        String pinyin = "";  
-        char[] nameChar = hanYu.toCharArray();  
-        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();  
-        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);  
-        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);  
-        for (int i = 0; i < nameChar.length; i++) {  
-            if (nameChar[i] > 128) {  
-                try {  
-                	if(first){
-                		pinyin += PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0].charAt(0);  
-                	}else{
-                		pinyin += PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0];  
-                	}                	
-                } catch (BadHanyuPinyinOutputFormatCombination e) {  
-                    e.printStackTrace();  
-                }  
-            }else{  
-            	pinyin += nameChar[i];  
-            }  
-        }  
-        return pinyin;  
-    }  
-	*/
-	public static Map<String,String> aduserName2Map(String aduserName) {
-		if(isNullOrBlank(aduserName)) {
-			return null;
-		}
-		Map<String,String> map = new HashMap<String, String>();
-		int index = 0;
-		if((index = aduserName.indexOf("\\")) > 0) {
-			map.put("domain", aduserName.substring(0,index));
-			map.put("userName", aduserName.substring(index + 1, aduserName.length()));
-		} else if((index = aduserName.indexOf("@")) > 0) {
-			map.put("userName", aduserName.substring(0, index));
-			map.put("domain", aduserName.substring(index + 1));
-		} else {
-			map.put("userName", aduserName);
-		}
-		return map;
-	}
-	
-	
-	/**
-	 * 处理AD域中的用户名,将域名去掉
-	 * @param str
-	 * @return
-	 */
-	public static String takeoffDomain(String aduserName) {
-		Map<String,String> map = aduserName2Map(aduserName);
-		if(BeanUtil.isNotNull(map)) {
-			return map.get("userName");
-		}
-		return null; 
-	}
-	
-	public static String getAdDomin(String aduserName) {
-		Map<String,String> map = aduserName2Map(aduserName);
-		if(BeanUtil.isNotNull(map)) {
-			return map.get("domain");
-		}
-		return null; 
-	}
+
+    /**
+     * avoid null, and return value trim.
+     * 
+     * @param value string value.
+     * @return the trim of value.
+     */
+    public static String avoidNull(String value) {
+        return (value == null) ? "" : value.trim();
+    }
+
+    public static boolean isNull(String value) {
+        return value == null;
+    }
+
+    /**
+     * @param value string value
+     * @return value
+     */
+    public static boolean isNullOrBlank(String value) {
+        return value == null || "".equals(value.trim());
+    }
+
+    public static boolean isNotEmpty(String value) {
+        return !isNullOrBlank(value);
+    }
+
+    public static boolean isNotNullAndEquals(String value, String equalString) {
+        return !isNullOrBlank(value) && value.equals(equalString);
+    }
+
+    public static boolean isNotNullAndEqualsIgnoreCase(String value, String equalString) {
+        return !isNullOrBlank(value) && value.equalsIgnoreCase(equalString);
+    }
+
+    /*
+     * 获取指定UTF-8模式字节长度的字符串
+     */
+    public static String limitLength(String strValue, int bytelen) {
+
+        // 中文汉字占用三个字节
+        int strlen = bytelen / 3;
+        int real_bytelen = strlen * 3;
+
+        // 如果为NULL或�?空,则直接返�?
+        if (null == strValue || "".equalsIgnoreCase(strValue)) {
+            return "";
+        }
+
+        try {
+            byte[] utf8_bytes = strValue.getBytes("UTF-8");
+            if (utf8_bytes.length <= bytelen)
+                return strValue;
+
+            byte[] cutoff_bytes = new byte[real_bytelen];
+            System.arraycopy(utf8_bytes, 0, cutoff_bytes, 0, real_bytelen);
+
+            String strResult = new String(cutoff_bytes, "UTF-8");
+
+            return strResult;
+
+        } catch (Exception e) {
+            if (strValue.length() < strlen)
+                return strValue;
+            return strValue.substring(0, strlen);
+        }
+
+    }
+
+    /**
+     * 对url进行编码�?
+     * 
+     * @param ori_url 要编码的url
+     * @return 返回url
+     */
+    public static String urlEncode(String url) {
+        try {
+            String tempstr = URLEncoder.encode(url, "UTF-8");
+            return tempstr.replaceAll("\\+", "%20");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return url;
+        }
+    }
+
+    /**
+     * 编码url
+     * 
+     * @param ori_url
+     * @return
+     */
+    public static String urlDecode(String url) {
+        try {
+
+            String tempstr = URLDecoder.decode(url.replaceAll("%20", "\\+"), "UTF-8");
+            return tempstr;
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+            return url;
+        }
+    }
+
+    /**
+     * �?��字符串是否包含特殊字�?
+     * 
+     * @param str
+     * @return
+     */
+    public static Boolean specialWord(String string) {
+        String regEx = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#�?…�?&*()—�?+|{}【�?‘;:�?“�?。,、?]";
+        return Pattern.compile(regEx).matcher(string).find();
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static Boolean startWithUpper(String string) {
+        return Pattern.compile("^[A-Z]").matcher(string).find();
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static Boolean startWithLower(String string) {
+        return Pattern.compile("^[a-z]").matcher(string).find();
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static Boolean startWithNumber(String string) {
+        return Pattern.compile("^[0-9]").matcher(string).find();
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static Boolean containsLower(String string) {
+        return Pattern.compile("[a-z]").matcher(string).find();
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static Boolean containsUpper(String string) {
+        return Pattern.compile("[A-Z]").matcher(string).find();
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static Boolean containsChinese(String string) {
+        String regEx = "[\u2E80-\u9FFF]+$";
+        return Pattern.compile(regEx).matcher(string).find();
+    }
+
+    /**
+     * 密码不包含全部或部分的用户账户名 �?��str2中是否包含str中全部或部分的数�?
+     * 
+     * @param str
+     * @param str2
+     * @return
+     */
+    public static Boolean containsPartOrAll(String string, String string2) {
+        if (isNotEmpty(string) && isNotEmpty(string2)) {
+            return Pattern.compile("[" + string + "]").matcher(string2).find();
+        }
+        return false;
+    }
+
+    public static boolean containsSpace(String string) {
+        return string.lastIndexOf(" ") != -1;
+    }
+
+    public static Boolean isNumber(String string) {
+        return Pattern.compile("[0-9]").matcher(string).find();
+    }
+
+    /**
+     * 返回字符串中包含的大写字母的
+     * 
+     * @param str
+     * @return
+     */
+    public static int countUpper(String string) {
+        int count = 0;
+        for (int i = 0; i < string.toCharArray().length; i++) {
+            if (containsUpper(String.valueOf(string.charAt(i)))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static int countLower(String string) {
+        int count = 0;
+        for (int i = 0; i < string.toCharArray().length; i++) {
+            if (containsLower(String.valueOf(string.charAt(i)))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public static int countNumber(String string) {
+        int count = 0;
+        for (int i = 0; i < string.toCharArray().length; i++) {
+            if (isNumber(String.valueOf(string.charAt(i)))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * @param str
+     * @return
+     */
+    public static int countSpecialWord(String string) {
+        int count = 0;
+        for (int i = 0; i < string.toCharArray().length; i++) {
+            if (specialWord(String.valueOf(string.charAt(i)))) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    public static List<String> string2List(String string, String split) {
+        String[] strs = {};
+        if (string != null && !string.equals("")) {
+            strs = string.split(split);
+        }
+        ArrayList<String> resultList = new ArrayList<String>(0);
+        for (int i = 0; i < strs.length; i++) {
+            if (strs[i] != null && !strs[i].equals("")) {
+                resultList.add(strs[i]);
+            }
+        }
+        resultList.trimToSize();
+        return resultList;
+    }
+
+    public static String list2String(List<String> list, String split) {
+        String string = "";
+        if (list == null)
+            return string;
+        for (int i = 0; i < list.size(); i++) {
+            if (list.get(i) != null && !list.get(i).equals("")) {
+                string += list.get(i) + split;
+            }
+        }
+        if (string.length() > 0) {
+            string = string.substring(0, string.length() - 1);
+        }
+        return string;
+    }
+
+    public static int parse2Integer(String string) {
+        Integer value = 0;
+        try {
+            value = Integer.parseInt(string);
+        } catch (Exception e) {
+            throw new RuntimeException("parse " + string + " to  Integer error.");
+        }
+        return value;
+    }
+
+    /**
+     * 处理如id=name形式的字符串
+     * 
+     * @param proValue
+     * @param key
+     * @param value
+     * @return
+     */
+    public static Map<String, List<String>> processStr(String proValue, String key, String value) {
+        Map<String, List<String>> map = new HashMap<String, List<String>>();
+        List<String> idList = new ArrayList<String>();
+        List<String> nameList = new ArrayList<String>();
+        if (StringUtils.isNotEmpty(proValue)) {
+            List<String> list = StringUtils.string2List(proValue, ",");
+            for (String str : list) {
+                idList.add(str.split("\\,")[0]);
+                nameList.add(str.split("\\,")[1]);
+            }
+        }
+        map.put(key, idList);
+        map.put(value, nameList);
+        return map;
+    }
+
+    /**
+     * 获得集合对象中某�?��性字段的值,中间用split隔开
+     * 
+     * @param list     集合对象
+     * @param propName 属�?名称
+     * @param split    分隔�?
+     * @return
+     */
+    @SuppressWarnings("rawtypes")
+    public static String convertPropVal(List list, String propName, String split) {
+        String retVal = "";
+        for (Object obj : list) {
+            if (StringUtils.isNotEmpty(retVal)) {
+                retVal += split;
+            }
+            retVal += BeanUtil.getValue(obj, propName);
+        }
+        return retVal;
+    }
+
+    /**
+     * 将对象类型的集合转换为String类型的集�?
+     * 
+     * @param list
+     * @param propName
+     * @return
+     */
+    @SuppressWarnings("rawtypes")
+    public static List<String> convertList(List list, String propName) {
+        List<String> strList = new ArrayList<String>();
+        for (Object obj : list) {
+            strList.add(BeanUtil.getValue(obj, propName));
+        }
+        return strList;
+    }
+
+    /**
+     * 将对象类型的集合转换为String类型的Map集合
+     * 
+     * @param list
+     * @param propNames
+     * @return
+     */
+    @SuppressWarnings("rawtypes")
+    public static Map<String, List<String>> convertListToMap(List list, String... propNames) {
+        Map<String, List<String>> map = new HashMap<String, List<String>>();
+        for (String name : propNames) {
+            List<String> strList = new ArrayList<String>();
+            for (Object obj : list) {
+                strList.add(BeanUtil.getValue(obj, name));
+            }
+            map.put(name, strList);
+        }
+        return map;
+    }
+
+    public static String convertWeekStr(String str, String split) {
+        String retVal = "";
+        try {
+            if (StringUtils.isNotEmpty(str)) {
+                String[] array = str.split("\\" + split);
+                for (int i = 0; i < array.length; i++) {
+                    if (StringUtils.isNotEmpty(retVal)) {
+                        retVal += split;
+                    }
+                    retVal += (Integer.parseInt(array[i]) + 1);
+                }
+            }
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+        }
+        return retVal;
+    }
+
+    public static List<String> convertToSingleList(List<String> list) {
+        return convertToSingleList(list, ",");
+    }
+
+    public static List<String> convertToSingleList(List<String> list, String reg) {
+        List<String> result = new ArrayList<String>();
+        if (list != null && list.size() > 0) {
+            for (String str : list) {
+                String[] arrStr = str.split(reg);
+                for (int i = 0; i < arrStr.length; i++) {
+                    result.add(arrStr[i]);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 汉字转换位汉语拼音
+     * 
+     * @param hanYu Chinese
+     * @param first true is Convert first,else all
+     * @return 拼音
+     */
+    /*
+     * public static String hanYu2Pinyin(String hanYu,boolean first){ String pinyin
+     * = ""; char[] nameChar = hanYu.toCharArray(); HanyuPinyinOutputFormat
+     * defaultFormat = new HanyuPinyinOutputFormat();
+     * defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
+     * defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE); for (int i = 0;
+     * i < nameChar.length; i++) { if (nameChar[i] > 128) { try { if(first){ pinyin
+     * += PinyinHelper.toHanyuPinyinStringArray(nameChar[i],
+     * defaultFormat)[0].charAt(0); }else{ pinyin +=
+     * PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]; } }
+     * catch (BadHanyuPinyinOutputFormatCombination e) { e.printStackTrace(); }
+     * }else{ pinyin += nameChar[i]; } } return pinyin; }
+     */
+    public static Map<String, String> aduserName2Map(String aduserName) {
+        if (isNullOrBlank(aduserName)) {
+            return null;
+        }
+        Map<String, String> map = new HashMap<String, String>();
+        int index = 0;
+        if ((index = aduserName.indexOf("\\")) > 0) {
+            map.put("domain", aduserName.substring(0, index));
+            map.put("userName", aduserName.substring(index + 1, aduserName.length()));
+        } else if ((index = aduserName.indexOf("@")) > 0) {
+            map.put("userName", aduserName.substring(0, index));
+            map.put("domain", aduserName.substring(index + 1));
+        } else {
+            map.put("userName", aduserName);
+        }
+        return map;
+    }
+
+    /**
+     * 处理AD域中的用户名,将域名去掉
+     * 
+     * @param str
+     * @return
+     */
+    public static String takeoffDomain(String aduserName) {
+        Map<String, String> map = aduserName2Map(aduserName);
+        if (BeanUtil.isNotNull(map)) {
+            return map.get("userName");
+        }
+        return null;
+    }
+
+    public static String getAdDomin(String aduserName) {
+        Map<String, String> map = aduserName2Map(aduserName);
+        if (BeanUtil.isNotNull(map)) {
+            return map.get("domain");
+        }
+        return null;
+    }
 }

+ 25 - 0
maxkey-web-maxkey/src/main/java/org/maxkey/web/endpoint/LoginEndpoint.java

@@ -12,6 +12,7 @@ import org.maxkey.authn.support.rememberme.AbstractRemeberMeService;
 import org.maxkey.authn.support.socialsignon.service.SocialSignOnProviderService;
 import org.maxkey.authn.support.wsfederation.WsFederationConstants;
 import org.maxkey.config.ApplicationConfig;
+import org.maxkey.crypto.password.opt.AbstractOptAuthn;
 import org.maxkey.dao.service.UserInfoService;
 import org.maxkey.domain.UserInfo;
 import org.maxkey.util.StringUtils;
@@ -69,6 +70,11 @@ public class LoginEndpoint {
 	@Autowired
 	@Qualifier("authenticationProvider")
 	RealmAuthenticationProvider authenticationProvider ;
+	
+	@Autowired
+    @Qualifier("tfaOptAuthn")
+    protected AbstractOptAuthn tfaOptAuthn;
+	
 	/*
 	@Autowired
 	@Qualifier("jwtLoginService")
@@ -124,6 +130,11 @@ public class LoginEndpoint {
 			modelAndView.addObject("isRemeberMe", applicationConfig.getLoginConfig().isRemeberMe());
 			modelAndView.addObject("isKerberos", applicationConfig.getLoginConfig().isKerberos());
 			modelAndView.addObject("isOneTimePwd", applicationConfig.getLoginConfig().isOneTimePwd());
+			if(applicationConfig.getLoginConfig().isOneTimePwd()) {
+			    modelAndView.addObject("optType", tfaOptAuthn.getOptType());
+			    modelAndView.addObject("optInterval", tfaOptAuthn.getInterval());
+			}
+			
 			if( applicationConfig.getLoginConfig().isKerberos()){
 				modelAndView.addObject("userDomainUrlJson", kerberosService.buildKerberosProxys());
 				
@@ -183,4 +194,18 @@ public class LoginEndpoint {
  		
  		return authnType;
  	}
+ 	
+ 	@RequestMapping("/login/otp/{username}")
+    @ResponseBody
+    public String produceOtp(@PathVariable("username") String username) {
+        UserInfo userInfo = new UserInfo();
+        userInfo.setUsername(username);
+        UserInfo queryUserInfo=userInfoService.loadByUsername(username);//(userInfo);
+        if(queryUserInfo!=null) {
+            tfaOptAuthn.produce(queryUserInfo);
+            return "ok";
+        }
+        
+        return "fail";
+    }
 }

+ 8 - 11
maxkey-web-maxkey/src/main/resources/config/applicationConfig.properties

@@ -59,7 +59,14 @@ config.characterencoding.charset.to=UTF-8
 
 config.app.issuer=CN=ConSec,CN=COM,CN=SH
 ############################################################################ 
-
+#IP  
+config.redis.hostname=127.0.0.1
+#port  
+config.redis.port=6379
+#password  
+config.redis.password=password
+#
+config.redis.timeout=10000
 #
 config.redis.pool.maxtotal=1000
 # 
@@ -68,16 +75,6 @@ config.redis.pool.maxidle=200
 config.redis.pool.maxwaitmillis=1000
 # 
 config.redis.pool.testonborrow=true
-  
-#IP  
-config.redis.hostname=127.0.0.1
-#port  
-config.redis.port=6379
-#password  
-config.redis.password=password
-
-config.redis.timeout=10000
-
 ############################################################################
 #                Login configuration
 #enable captcha

+ 5 - 1
maxkey-web-maxkey/src/main/resources/spring/maxkey-security.xml

@@ -93,7 +93,11 @@
 	
 	<bean id="tfaOptAuthn" class="org.maxkey.crypto.password.opt.impl.TimeBasedOtpAuthn">
 	</bean>
-	
+	 
+	<!-- 
+	<bean id="tfaOptAuthn" class="org.maxkey.crypto.password.opt.impl.sms.netease.SmsOtpAuthnYunxin">
+	</bean>
+	-->
 	<!-- Authentication Password Encoder Config -->
 	<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>   
 	

+ 19 - 5
maxkey-web-maxkey/src/main/resources/templates/views/login.ftl

@@ -54,6 +54,7 @@ function formatTime(){
 	strTime+=(seconds<10?"0"+seconds:seconds);
 }
 
+<#if true==isOneTimePwd && "TOPT"==optType>
 function currentTime(){
 	seconds++;
 	if(seconds>59){
@@ -74,17 +75,18 @@ function currentTime(){
 	
 	$("#currentTime").val(strTime);
 }
+
 <#--timeBase Token  Interval default is 30s-->
 var timeBaseCount;
 function getTimeBaseCount(){
-	if(seconds<30){
-		timeBaseCount=30-seconds;
+	if(seconds<${optInterval}){
+		timeBaseCount=${optInterval}-seconds;
 	}else{
-		timeBaseCount=30-(seconds-30);
+		timeBaseCount=${optInterval}-(seconds-${optInterval});
 	}
 	$("#tfa_j_otp_captcha_button").val("<@locale code="login.text.login.twofactor.validTime"/>("+timeBaseCount+")<@locale code="login.text.login.twofactor.validTime.unit"/>");
 };
-
+</#if>
 var currentSwitchTab="div_commonLogin";
 <#--submit form-->		
 function doLoginSubmit(){
@@ -117,7 +119,9 @@ document.onkeydown=function(event){
 };
 	
 $(function(){
+	<#if true==isOneTimePwd && "TOPT"==optType>
 	setInterval("currentTime()", 1000);
+	</#if>
 	<#--on captcha image click ,new a captcha code-->
 	<#if true==isCaptcha>
 	$('#j_captchaimg').click(function () {//
@@ -153,6 +157,14 @@ $(function(){
 		if(captchaCount<60){
 			return;
 		}
+		var loginName=$("#tfa_j_username").val();
+		if(loginName==""){
+			return;
+		}
+		$.get("<@base />/login/otp/"+loginName,function(data,status){
+    		alert("Data: " + data + "\nStatus: " + status);
+  		});
+		
 		<#--todo:send captcha-->
 		captchaCountTimer=setInterval("getCaptchaCount()", 1000);
 	});
@@ -245,13 +257,15 @@ $(function(){
 								<td><@locale code="login.text.password"/>:</td>
 								<td><input class="form-control"  type='password' id='tfa_j_password'  name='password' value=""  tabindex="2" /></td>
 							</tr>
-							<#if true==isOneTimePwd>
+							<#if true==isOneTimePwd >
+							<#if "TOPT"==optType >
 							<tr>
 								<td><@locale code="login.text.currenttime"/>:</td>
 								<td>
 									<input class="form-control"  readonly type='text' id="currentTime" name="currentTime"  tabindex="3"  value="" />
 								</td>
 							</tr>
+							</#if>
 							<tr>
 								<td><@locale code="login.text.captcha"/>:</td>
 								<td>