separate common
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Class-Path:
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp;
|
||||
|
||||
import org.maxkey.crypto.password.otp.token.AbstractOtpTokenStore;
|
||||
import org.maxkey.crypto.password.otp.token.InMemoryOtpTokenStore;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.maxkey.util.StringGenerator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* AbstractOTPAuthn.
|
||||
* @author Administrator
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractOtpAuthn {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractOtpAuthn.class);
|
||||
|
||||
protected AbstractOtpTokenStore optTokenStore = new InMemoryOtpTokenStore();
|
||||
|
||||
//验证码有效間隔
|
||||
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 String MOBILE = "MOBILE";
|
||||
// 短信
|
||||
public static String SMS = "SMS";
|
||||
// 邮箱
|
||||
public static String EMAIL = "EMAIL";
|
||||
//TIMEBASED_OPT
|
||||
public static String TIMEBASED_OPT = "TOPT";
|
||||
// HmacOTP
|
||||
public static String HOTP_OPT = "HOTP";
|
||||
|
||||
public static String RSA_OPT = "RSA";
|
||||
|
||||
public static String CAP_OPT = "CAP";
|
||||
|
||||
}
|
||||
|
||||
public abstract boolean produce(UserInfo userInfo);
|
||||
|
||||
public abstract boolean validate(UserInfo userInfo, String token);
|
||||
|
||||
protected String defaultProduce(UserInfo userInfo) {
|
||||
return genToken(userInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* genToken.
|
||||
* @param userInfo UserInfo
|
||||
* @return
|
||||
*/
|
||||
public String genToken(UserInfo userInfo) {
|
||||
if (stringGenerator == null) {
|
||||
stringGenerator = new StringGenerator(StringGenerator.DEFAULT_CODE_NUMBER, digits);
|
||||
}
|
||||
String token = stringGenerator.randomGenerate();
|
||||
logger.debug("Generator token " + token);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* the interval.
|
||||
* @return the interval
|
||||
*/
|
||||
public int getInterval() {
|
||||
return interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* interval the interval to set.
|
||||
* @param interval the interval to set
|
||||
*/
|
||||
public void setInterval(int interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
/**
|
||||
* digits.
|
||||
* @return the digits
|
||||
*/
|
||||
public int getDigits() {
|
||||
return digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* digits the digits to set.
|
||||
* @param digits the digits to set
|
||||
*/
|
||||
public void setDigits(int digits) {
|
||||
this.digits = digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* crypto.
|
||||
* @return the crypto
|
||||
*/
|
||||
public String getCrypto() {
|
||||
return crypto;
|
||||
}
|
||||
|
||||
/**
|
||||
* crypto the crypto to set.
|
||||
* @param crypto the crypto to set
|
||||
*/
|
||||
public void setCrypto(String crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
public String getOptType() {
|
||||
return optType;
|
||||
}
|
||||
|
||||
public void setOptType(String optType) {
|
||||
this.optType = optType;
|
||||
}
|
||||
|
||||
public void setOptTokenStore(AbstractOtpTokenStore optTokenStore) {
|
||||
this.optTokenStore = optTokenStore;
|
||||
}
|
||||
|
||||
public void initPropertys() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class OneTimePassword implements Serializable {
|
||||
private static final long serialVersionUID = -1637133296702014021L;
|
||||
private String id;
|
||||
private String type;
|
||||
private String token;
|
||||
private String username;
|
||||
private String receiver;
|
||||
private String createTime;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getReceiver() {
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public void setReceiver(String receiver) {
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
public String getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(String createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("OneTimePassword [id=");
|
||||
builder.append(id);
|
||||
builder.append(", type=");
|
||||
builder.append(type);
|
||||
builder.append(", token=");
|
||||
builder.append(token);
|
||||
builder.append(", username=");
|
||||
builder.append(username);
|
||||
builder.append(", receiver=");
|
||||
builder.append(receiver);
|
||||
builder.append(", createTime=");
|
||||
builder.append(createTime);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.algorithm;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* IETF RFC 4226 http://tools.ietf.org/html/rfc4226 HOTP Include HmacOTP's
|
||||
* implement same as HmacOTP's, when addChecksum = false & truncationOffset = -1
|
||||
*
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
public class HOTP {
|
||||
// These are used to calculate the check-sum digits.
|
||||
// 0 1 2 3 4 5 6 7 8 9
|
||||
private static final int[] doubleDigits = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
|
||||
|
||||
/**
|
||||
* Calculates the checksum using the credit card algorithm. This algorithm has
|
||||
* the advantage that it detects any single mistyped digit and any single
|
||||
* transposition of adjacent digits.
|
||||
*
|
||||
* @param num the number to calculate the checksum for
|
||||
* @param digits number of significant places in the number
|
||||
*
|
||||
* @return the checksum of num
|
||||
*/
|
||||
public static int calcChecksum(long num, int digits) {
|
||||
boolean doubleDigit = true;
|
||||
int total = 0;
|
||||
while (0 < digits--) {
|
||||
int digit = (int) (num % 10);
|
||||
num /= 10;
|
||||
if (doubleDigit) {
|
||||
digit = doubleDigits[digit];
|
||||
}
|
||||
total += digit;
|
||||
doubleDigit = !doubleDigit;
|
||||
}
|
||||
int result = total % 10;
|
||||
if (result > 0) {
|
||||
result = 10 - result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method uses the JCE to provide the HMAC-SHA-1 * algorithm. HMAC computes
|
||||
* a Hashed Message Authentication Code and in this case SHA1 is the hash
|
||||
* algorithm used.
|
||||
*
|
||||
* @param keyBytes the bytes to use for the HMAC-SHA-1 key
|
||||
* @param text the message or text to be authenticated.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException if no provider makes either HmacSHA1 or
|
||||
* HMAC-SHA-1 digest algorithms available.
|
||||
* @throws InvalidKeyException The secret provided was not a valid
|
||||
* HMAC-SHA-1 key.
|
||||
*
|
||||
*/
|
||||
|
||||
public static byte[] hmac_sha1(byte[]
|
||||
keyBytes, byte[] text) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// try {
|
||||
Mac hmacSha1;
|
||||
try {
|
||||
hmacSha1 = Mac.getInstance("HmacSHA1");
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
hmacSha1 = Mac.getInstance("HMAC-SHA-1");
|
||||
}
|
||||
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
|
||||
hmacSha1.init(macKey);
|
||||
return hmacSha1.doFinal(text);
|
||||
// } catch (GeneralSecurityException gse) {
|
||||
// throw new UndeclaredThrowableException(gse);
|
||||
// }
|
||||
}
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
private static final int[] DIGITS_POWER
|
||||
= { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
|
||||
|
||||
/**
|
||||
* This method generates an OTP value for the given set of parameters.
|
||||
*
|
||||
* @param secret the shared secret
|
||||
* @param movingFactor the counter, time, or other value that changes on a
|
||||
* per use basis.
|
||||
* @param codeDigits the number of digits in the OTP, not including the
|
||||
* checksum, if any.
|
||||
* @param addChecksum a flag that indicates if a checksum digit
|
||||
*
|
||||
*
|
||||
*
|
||||
* M'Raihi, et al. Informational [Page 29]
|
||||
*
|
||||
* RFC 4226 HOTP Algorithm December 2005
|
||||
*
|
||||
*
|
||||
* should be appended to the OTP.
|
||||
* @param truncationOffset the offset into the MAC result to begin truncation.
|
||||
* If this value is out of the range of 0 ... 15, then
|
||||
* dynamic truncation will be used. Dynamic truncation
|
||||
* is when the last 4 bits of the last byte of the MAC
|
||||
* are used to determine the start offset.
|
||||
* @return A numeric String in base 10 that includes {@link codeDigits} digits
|
||||
* @throws NoSuchAlgorithmException if no provider makes either HmacSHA1 or
|
||||
* HMAC-SHA-1 digest algorithms available.
|
||||
* @throws InvalidKeyException The secret provided was not a valid
|
||||
* HMAC-SHA-1 key.
|
||||
*
|
||||
* plus the optional checksum digit if requested.
|
||||
*/
|
||||
public static String generateOTP(byte[]
|
||||
secret, long movingFactor, int codeDigits, boolean addChecksum,
|
||||
int truncationOffset) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// put movingFactor value into text byte array
|
||||
String result = null;
|
||||
final int digits = addChecksum ? (codeDigits + 1) : codeDigits;
|
||||
|
||||
byte[] text = new byte[8];
|
||||
for (int i = text.length - 1; i >= 0; i--) {
|
||||
text[i] = (byte) (movingFactor & 0xff);
|
||||
movingFactor >>= 8;
|
||||
}
|
||||
|
||||
// compute hmac hash
|
||||
byte[] hash = hmac_sha1(secret, text);
|
||||
|
||||
// put selected bytes into result int
|
||||
int offset = hash[hash.length - 1] & 0xf;
|
||||
|
||||
if ((0 <= truncationOffset) && (truncationOffset < (hash.length - 4))) {
|
||||
offset = truncationOffset;
|
||||
}
|
||||
|
||||
int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
|
||||
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||
|
||||
int otp = binary % DIGITS_POWER[codeDigits];
|
||||
if (addChecksum) {
|
||||
otp = (otp * 10) + calcChecksum(otp, codeDigits);
|
||||
}
|
||||
result = Integer.toString(otp);
|
||||
while (result.length() < digits) {
|
||||
result = "0" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.algorithm;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* same as HOTP,but addChecksum = false & truncationOffset = -1.
|
||||
*
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
public class HmacOTP {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HmacOTP.class);
|
||||
|
||||
/**
|
||||
* gen.
|
||||
* @param seed byte
|
||||
* @param count int
|
||||
* @param digits int
|
||||
* @return
|
||||
*/
|
||||
public static String gen(byte[] seed, int count, int digits) {
|
||||
try {
|
||||
return generateOTP(seed, count, digits);
|
||||
} catch (InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
LoggerFactory.getLogger(HmacOTP.class).error(e.getMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
LoggerFactory.getLogger(HmacOTP.class).error(e.getMessage());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyBytes
|
||||
* @param text
|
||||
* @return
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws InvalidKeyException
|
||||
*/
|
||||
public static byte[] hmac_sha1(byte[] keyBytes, byte[] text)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
Mac hmacSha1;
|
||||
try {
|
||||
hmacSha1 = Mac.getInstance("HmacSHA1");
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
hmacSha1 = Mac.getInstance("HMAC-SHA-1");
|
||||
}
|
||||
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
|
||||
hmacSha1.init(macKey);
|
||||
return hmacSha1.doFinal(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secret
|
||||
* @param movingFactor
|
||||
* @param codeDigits
|
||||
* @return
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws InvalidKeyException
|
||||
*/
|
||||
static private String generateOTP(byte[] secret,
|
||||
long movingFactor, int codeDigits)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// put movingFactor value into text byte array
|
||||
String result = null;
|
||||
byte[] text = new byte[8];
|
||||
|
||||
for (int i = text.length - 1; i >= 0; i--) {
|
||||
text[i] = (byte) (movingFactor & 0xff);
|
||||
movingFactor >>= 8;
|
||||
}
|
||||
|
||||
// compute hmac hash
|
||||
byte[] hash = hmac_sha1(secret, text);
|
||||
|
||||
// put selected bytes into result int
|
||||
int offset = hash[hash.length - 1] & 0xf;
|
||||
|
||||
int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
|
||||
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||
|
||||
int otp = (int) (binary % Math.pow(10, codeDigits));
|
||||
// int otp = binary % DIGITS_POWER[codeDigits];
|
||||
result = Integer.toString(otp);
|
||||
|
||||
while (result.length() < codeDigits) {
|
||||
result = "0" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.algorithm;
|
||||
|
||||
public class KeyUriFormat {
|
||||
|
||||
public class Types {
|
||||
public static final String HOTP = "hotp";
|
||||
public static final String TOTP = "totp";
|
||||
|
||||
}
|
||||
|
||||
String crypto = "HmacSHA1";
|
||||
String type;
|
||||
String secret;
|
||||
String issuer;
|
||||
String domain;
|
||||
int digits = 6;
|
||||
// just for hotp
|
||||
Long counter = 0L;
|
||||
// just for totp
|
||||
int period = 30;
|
||||
|
||||
String account;
|
||||
|
||||
public KeyUriFormat() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type
|
||||
* @param secret
|
||||
*/
|
||||
public KeyUriFormat(String type, String secret) {
|
||||
this.type = type;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type
|
||||
* @param secret
|
||||
* @param issuer
|
||||
*/
|
||||
public KeyUriFormat(String type, String secret, String issuer) {
|
||||
this.type = type;
|
||||
this.secret = secret;
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type the type to set
|
||||
*/
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the secret
|
||||
*/
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secret the secret to set
|
||||
*/
|
||||
public void setSecret(String secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the issuer
|
||||
*/
|
||||
public String getIssuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param issuer the issuer to set
|
||||
*/
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the digits
|
||||
*/
|
||||
public int getDigits() {
|
||||
return digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param digits the digits to set
|
||||
*/
|
||||
public void setDigits(int digits) {
|
||||
this.digits = digits;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the counter
|
||||
*/
|
||||
public Long getCounter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param counter the counter to set
|
||||
*/
|
||||
public void setCounter(Long counter) {
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the period
|
||||
*/
|
||||
public int getPeriod() {
|
||||
return period;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param period the period to set
|
||||
*/
|
||||
public void setPeriod(int period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the account
|
||||
*/
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param account the account to set
|
||||
*/
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the crypto
|
||||
*/
|
||||
public String getCrypto() {
|
||||
return crypto;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param crypto the crypto to set
|
||||
*/
|
||||
public void setCrypto(String crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return format(this.account);
|
||||
}
|
||||
|
||||
/**
|
||||
* format account.
|
||||
* @param account String
|
||||
* @return
|
||||
*/
|
||||
public String format(String account) {
|
||||
StringBuffer uri = new StringBuffer("otpauth://");
|
||||
uri.append(type).append("/");
|
||||
if (null != this.domain) {
|
||||
uri.append(this.domain).append("/").append(account);
|
||||
} else {
|
||||
uri.append(account);
|
||||
}
|
||||
uri.append("?secret=").append(secret);
|
||||
|
||||
if (null != issuer) {
|
||||
uri.append("&issuer=").append(issuer);
|
||||
}
|
||||
if (digits != 6) {
|
||||
uri.append("&digits=").append(digits);
|
||||
}
|
||||
|
||||
if (type.equalsIgnoreCase(Types.TOTP) && period != 30) {
|
||||
uri.append("&period=").append(period);
|
||||
}
|
||||
|
||||
if (type.equalsIgnoreCase(Types.HOTP)) {
|
||||
uri.append("&counter=").append(counter);
|
||||
}
|
||||
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyUriFormat [crypto=" + crypto + ", type=" + type + ", secret=" + secret + ", issuer=" + issuer
|
||||
+ ", domain=" + domain + ", digits=" + digits + ", counter=" + counter + ", period=" + period
|
||||
+ ", account=" + account + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.algorithm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
public class OtpSecret {
|
||||
|
||||
private static final Random rand = new Random();
|
||||
|
||||
/**
|
||||
* Seed for HMAC-SHA1 - 20 bytes Generates random 20 bytes long TOTP Secret.
|
||||
*
|
||||
* @return generated secret
|
||||
*/
|
||||
public static final byte[] generate() {
|
||||
int size = 20;
|
||||
byte[] b = new byte[size];
|
||||
rand.nextBytes(b);
|
||||
return Arrays.copyOf(b, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed by crypto.
|
||||
*
|
||||
* @return generated secret
|
||||
*/
|
||||
public static final byte[] generate(String crypto) {
|
||||
if (crypto.equalsIgnoreCase("HmacSHA1") || crypto.equalsIgnoreCase("HMAC-SHA-1")) {
|
||||
return generate();
|
||||
}
|
||||
if (crypto.equalsIgnoreCase("HmacSHA256") || crypto.equalsIgnoreCase("HMAC-SHA-256")) {
|
||||
return generate32();
|
||||
}
|
||||
if (crypto.equalsIgnoreCase("HmacSHA512") || crypto.equalsIgnoreCase("HMAC-SHA-512")) {
|
||||
return generate64();
|
||||
}
|
||||
return generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed for HMAC-SHA256 - 32 bytes Generates random 32 bytes long TOTP Secret.
|
||||
*
|
||||
* @return generated secret
|
||||
*/
|
||||
public static final byte[] generate32() {
|
||||
int size = 32;
|
||||
byte[] b = new byte[size];
|
||||
rand.nextBytes(b);
|
||||
return Arrays.copyOf(b, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed forHMAC-SHA512 - 64 bytes Generates random 64 bytes long TOTP Secret.
|
||||
*
|
||||
* @return generated secret
|
||||
*/
|
||||
public static final byte[] generate64() {
|
||||
int size = 64;
|
||||
byte[] b = new byte[size];
|
||||
rand.nextBytes(b);
|
||||
return Arrays.copyOf(b, size);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.algorithm;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class TimeBasedOTP {
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
private static final int[] DIGITS_POWER
|
||||
= { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
|
||||
|
||||
static String seed64 = "";
|
||||
|
||||
static String steps = "0";
|
||||
|
||||
static String steps2 = "0";
|
||||
|
||||
TimeBasedOTP() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method uses the JCE to provide the crypto algorithm. HMAC computes a
|
||||
* Hashed Message Authentication Code with the crypto hash algorithm as a
|
||||
* parameter.
|
||||
*
|
||||
* @param crypto the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
|
||||
* @param keyBytes the bytes to use for the HMAC key
|
||||
* @param text the message or text to be authenticated
|
||||
*/
|
||||
|
||||
private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
|
||||
try {
|
||||
Mac hmac;
|
||||
hmac = Mac.getInstance(crypto);
|
||||
SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
|
||||
hmac.init(macKey);
|
||||
return hmac.doFinal(text);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new UndeclaredThrowableException(gse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a HEX string to Byte[].
|
||||
*
|
||||
* @param hex the HEX string
|
||||
*
|
||||
* @return: a byte array
|
||||
*/
|
||||
private static byte[] hexStr2Bytes(String hex) {
|
||||
// Adding one byte to get the right conversion
|
||||
// Values starting with "0" can be converted
|
||||
byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
|
||||
|
||||
// Copy all the REAL bytes, not the "first"
|
||||
byte[] ret = new byte[bArray.length - 1];
|
||||
for (int i = 0; i < ret.length; i++) {
|
||||
ret[i] = bArray[i + 1];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a OTP value for the given set of parameters. Default
|
||||
* Crypto HmacSHA512
|
||||
*
|
||||
* @param key the shared secret, HEX encoded
|
||||
* @param time a value that reflects a time
|
||||
* @param returnDigits number of digits to return
|
||||
*
|
||||
* @return: a numeric String in base 10 that includes {@link truncationDigits}
|
||||
* digits
|
||||
*/
|
||||
public static String genOTP(String key, String time, String returnDigits) {
|
||||
|
||||
return generateOTP(key, time, returnDigits, "HmacSHA1");
|
||||
}
|
||||
|
||||
/*
|
||||
* * This method generates a OTP value for the given set of parameters. Crypto
|
||||
* HmacSHA1
|
||||
*
|
||||
* @param key
|
||||
*
|
||||
* @param time
|
||||
*
|
||||
* @param returnDigits
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String generateTOTPHmacSHA1(String key, String time, String returnDigits) {
|
||||
return generateOTP(key, time, returnDigits, "HmacSHA1");
|
||||
}
|
||||
|
||||
/*
|
||||
* * This method generates a OTP value for the given set of parameters. Crypto
|
||||
* HmacSHA256
|
||||
*
|
||||
* @param key: the shared secret, HEX encoded
|
||||
*
|
||||
* @param time: a value that reflects a time
|
||||
*
|
||||
* @param returnDigits: number of digits to return
|
||||
*
|
||||
* @return: a numeric String in base 10 that includes {@link truncationDigits}
|
||||
* digits
|
||||
*/
|
||||
|
||||
public static String genOTPHmacSHA256(String key, String time, String returnDigits) {
|
||||
return generateOTP(key, time, returnDigits, "HmacSHA256");
|
||||
}
|
||||
|
||||
/*
|
||||
* * This method generates a OTP value for the given set of parameters. Crypto
|
||||
* HmacSHA256
|
||||
*
|
||||
* @param key: the shared secret, HEX encoded
|
||||
*
|
||||
* @param time: a value that reflects a time
|
||||
*
|
||||
* @param returnDigits: number of digits to return
|
||||
*
|
||||
* @return: a numeric String in base 10 that includes {@link truncationDigits}
|
||||
* digits
|
||||
*/
|
||||
public static String genOTPHmacSHA512(String key, String time, String returnDigits) {
|
||||
return generateOTP(key, time, returnDigits, "HmacSHA512");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method generates a TOTP value for the given set of parameters.
|
||||
*/
|
||||
public static void actualiseTime() {
|
||||
// double random = Math.random();
|
||||
long T0 = 0;
|
||||
long X = 30;
|
||||
Date d = new Date();
|
||||
Long k = d.getTime() / 1000;
|
||||
|
||||
long testTime[] = { d.getTime() };
|
||||
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
for (int i = 0; i < testTime.length; i++) {
|
||||
long T = (k - T0) / X;
|
||||
long T2 = ((k - T0) / X) - 1;
|
||||
// System.out.println(T);
|
||||
steps = Long.toHexString(T).toUpperCase();
|
||||
steps2 = Long.toHexString(T2).toUpperCase();
|
||||
while (steps.length() < 16) {
|
||||
steps = "0" + steps;
|
||||
}
|
||||
//String fmtTime = String.format("%1$-11s", k/1000);
|
||||
//String utcTime = df.format(new Date(testTime[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateOTP.
|
||||
* @param key String
|
||||
* @param time String
|
||||
* @param returnDigits String
|
||||
* @param crypto String
|
||||
* @return
|
||||
*/
|
||||
public static String generateOTP(String key, String time, String returnDigits, String crypto) {
|
||||
int codeDigits = Integer.decode(returnDigits).intValue();
|
||||
|
||||
String result = null;
|
||||
|
||||
// Using the counter
|
||||
// First 8 bytes are for the movingFactor
|
||||
// Compliant with base RFC 4226 (HOTP)
|
||||
while (time.length() < 16) {
|
||||
time = "0" + time;
|
||||
}
|
||||
// Get the HEX in a Byte[]
|
||||
byte[] msg = hexStr2Bytes(time);
|
||||
byte[] k = hexStr2Bytes(key);
|
||||
|
||||
byte[] hash = hmac_sha(crypto, k, msg);
|
||||
|
||||
// put selected bytes into result int
|
||||
int offset = hash[hash.length - 1] & 0xf;
|
||||
|
||||
int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
|
||||
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
|
||||
|
||||
int otp = binary % DIGITS_POWER[codeDigits];
|
||||
|
||||
result = Integer.toString(otp);
|
||||
while (result.length() < codeDigits) {
|
||||
result = "0" + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
|
||||
/**
|
||||
* Chip Authentication Program EMV stands for Europay, MasterCard and Visa, a
|
||||
* global standard for inter-operation of integrated circuit cards (IC cards or
|
||||
* "chip cards") and IC card capable point of sale (POS) terminals and automated
|
||||
* teller machines (ATMs), for authenticating credit and debit card
|
||||
* transactions.
|
||||
*
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
public class CapOtpAuthn extends AbstractOtpAuthn {
|
||||
|
||||
|
||||
|
||||
public CapOtpAuthn() {
|
||||
optType = OptTypes.CAP_OPT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.maxkey.crypto.Base32Utils;
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.crypto.password.otp.algorithm.TimeBasedOTP;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CounterBasedOtpAuthn extends AbstractOtpAuthn {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(CounterBasedOtpAuthn.class);
|
||||
|
||||
|
||||
public CounterBasedOtpAuthn() {
|
||||
optType = OptTypes.HOTP_OPT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
_logger.debug("SharedCounter : " + userInfo.getSharedCounter());
|
||||
byte[] byteSharedSecret = Base32Utils.decode(userInfo.getSharedSecret());
|
||||
String hexSharedSecret = Hex.encodeHexString(byteSharedSecret);
|
||||
String counterBasedToken = "";
|
||||
if (crypto.equalsIgnoreCase("HmacSHA1")) {
|
||||
counterBasedToken = TimeBasedOTP.genOTP(
|
||||
hexSharedSecret,
|
||||
userInfo.getSharedCounter(),
|
||||
"" + digits
|
||||
);
|
||||
} else if (crypto.equalsIgnoreCase("HmacSHA256")) {
|
||||
counterBasedToken = TimeBasedOTP.genOTPHmacSHA256(
|
||||
hexSharedSecret,
|
||||
userInfo.getSharedCounter(),
|
||||
"" + digits
|
||||
);
|
||||
} else if (crypto.equalsIgnoreCase("HmacSHA512")) {
|
||||
counterBasedToken = TimeBasedOTP.genOTPHmacSHA512(
|
||||
hexSharedSecret,
|
||||
userInfo.getSharedCounter(),
|
||||
"" + digits
|
||||
);
|
||||
}
|
||||
|
||||
_logger.debug("token : " + token);
|
||||
_logger.debug("counterBasedToken : " + counterBasedToken);
|
||||
if (token.equalsIgnoreCase(counterBasedToken)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import org.maxkey.crypto.Base32Utils;
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.crypto.password.otp.algorithm.HOTP;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HotpOtpAuthn extends AbstractOtpAuthn {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(HotpOtpAuthn.class);
|
||||
|
||||
boolean addChecksum;
|
||||
int truncation = -1;
|
||||
|
||||
public HotpOtpAuthn() {
|
||||
optType = OptTypes.HOTP_OPT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
_logger.debug("SharedCounter : " + userInfo.getSharedCounter());
|
||||
byte[] byteSharedSecret = Base32Utils.decode(userInfo.getSharedSecret());
|
||||
String hotpToken;
|
||||
try {
|
||||
hotpToken = HOTP.generateOTP(
|
||||
byteSharedSecret,
|
||||
Long.parseLong(userInfo.getSharedCounter()),
|
||||
digits,
|
||||
addChecksum, truncation
|
||||
);
|
||||
_logger.debug("token : " + token);
|
||||
_logger.debug("hotpToken : " + hotpToken);
|
||||
if (token.equalsIgnoreCase(hotpToken)) {
|
||||
return true;
|
||||
}
|
||||
} catch (InvalidKeyException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* the addChecksum.
|
||||
*/
|
||||
public boolean isAddChecksum() {
|
||||
return addChecksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* addChecksum the addChecksum to set.
|
||||
*/
|
||||
public void setAddChecksum(boolean addChecksum) {
|
||||
this.addChecksum = addChecksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* the truncation.
|
||||
*/
|
||||
public int getTruncation() {
|
||||
return truncation;
|
||||
}
|
||||
|
||||
/**
|
||||
* truncation the truncation to set.
|
||||
*/
|
||||
public void setTruncation(int truncation) {
|
||||
this.truncation = truncation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import org.apache.commons.mail.DefaultAuthenticator;
|
||||
import org.apache.commons.mail.Email;
|
||||
import org.apache.commons.mail.SimpleEmail;
|
||||
import org.maxkey.configuration.EmailConfig;
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class MailOtpAuthn extends AbstractOtpAuthn {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(MailOtpAuthn.class);
|
||||
|
||||
@Autowired
|
||||
EmailConfig emailConfig;
|
||||
String subject = "One Time PassWord";
|
||||
|
||||
String messageTemplate = "{0} You Token is {1} , it validity in {2} minutes.";
|
||||
|
||||
public MailOtpAuthn() {
|
||||
optType = OptTypes.EMAIL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
try {
|
||||
String token = this.genToken(userInfo);
|
||||
Email email = new SimpleEmail();
|
||||
email.setHostName(emailConfig.getSmtpHost());
|
||||
email.setSmtpPort(emailConfig.getPort());
|
||||
email.setSSLOnConnect(emailConfig.isSsl());
|
||||
email.setAuthenticator(
|
||||
new DefaultAuthenticator(emailConfig.getUsername(), emailConfig.getPassword()));
|
||||
|
||||
email.setFrom(emailConfig.getSender());
|
||||
email.setSubject(subject);
|
||||
email.setMsg(
|
||||
MessageFormat.format(
|
||||
messageTemplate,userInfo.getUsername(),token,(interval / 60)));
|
||||
|
||||
email.addTo(userInfo.getEmail());
|
||||
email.send();
|
||||
_logger.debug(
|
||||
"token " + token + " send to user " + userInfo.getUsername()
|
||||
+ ", email " + userInfo.getEmail());
|
||||
//成功返回
|
||||
this.optTokenStore.store(
|
||||
userInfo,
|
||||
token,
|
||||
userInfo.getMobile(),
|
||||
OptTypes.EMAIL);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
return this.optTokenStore.validate(userInfo, token, OptTypes.EMAIL, interval);
|
||||
}
|
||||
|
||||
public void setEmailConfig(EmailConfig emailConfig) {
|
||||
this.emailConfig = emailConfig;
|
||||
}
|
||||
|
||||
public String getSubject() {
|
||||
return subject;
|
||||
}
|
||||
|
||||
public void setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
}
|
||||
|
||||
public String getMessageTemplate() {
|
||||
return messageTemplate;
|
||||
}
|
||||
|
||||
public void setMessageTemplate(String messageTemplate) {
|
||||
this.messageTemplate = messageTemplate;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
|
||||
public class MobileOtpAuthn extends AbstractOtpAuthn {
|
||||
|
||||
|
||||
|
||||
public MobileOtpAuthn() {
|
||||
optType = OptTypes.SMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
|
||||
/**
|
||||
* Chip Authentication Program EMV stands for Europay, MasterCard and Visa, a
|
||||
* global standard for inter-operation of integrated circuit cards (IC cards or
|
||||
* "chip cards") and IC card capable point of sale (POS) terminals and automated
|
||||
* teller machines (ATMs), for authenticating credit and debit card
|
||||
* transactions.
|
||||
*
|
||||
* @author Crystal.Sea
|
||||
*
|
||||
*/
|
||||
public class RsaOtpAuthn extends AbstractOtpAuthn {
|
||||
|
||||
|
||||
|
||||
public RsaOtpAuthn() {
|
||||
optType = OptTypes.RSA_OPT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import org.maxkey.constants.ConstantsProperties;
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
public class SmsOtpAuthn extends AbstractOtpAuthn {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SmsOtpAuthn.class);
|
||||
|
||||
protected Properties properties;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
String token = this.genToken(userInfo);
|
||||
// TODO:You must add send sms code here
|
||||
logger.debug("send sms code" + token);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void loadProperties() throws IOException {
|
||||
Resource resource = new ClassPathResource(
|
||||
ConstantsProperties.classPathResource(
|
||||
ConstantsProperties.classPathResource(
|
||||
ConstantsProperties.maxKeyPropertySource)));
|
||||
properties = new Properties();
|
||||
properties.load(resource.getInputStream());
|
||||
}
|
||||
|
||||
public void initPropertys() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.maxkey.crypto.Base32Utils;
|
||||
import org.maxkey.crypto.password.otp.AbstractOtpAuthn;
|
||||
import org.maxkey.crypto.password.otp.algorithm.TimeBasedOTP;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TimeBasedOtpAuthn extends AbstractOtpAuthn {
|
||||
private static final Logger _logger = LoggerFactory.getLogger(TimeBasedOtpAuthn.class);
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public TimeBasedOtpAuthn() {
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
_logger.debug("utcTime : " + dateFormat.format(new Date()));
|
||||
long currentTimeSeconds = System.currentTimeMillis() / 1000;
|
||||
byte[] byteSharedSecret = Base32Utils.decode(userInfo.getSharedSecret());
|
||||
String hexSharedSecret = Hex.encodeHexString(byteSharedSecret);
|
||||
String timeBasedToken = "";
|
||||
if (crypto.equalsIgnoreCase("HmacSHA1")) {
|
||||
timeBasedToken = TimeBasedOTP.genOTP(
|
||||
hexSharedSecret,
|
||||
Long.toHexString(currentTimeSeconds / interval).toUpperCase() + "",
|
||||
digits + "");
|
||||
} else if (crypto.equalsIgnoreCase("HmacSHA256")) {
|
||||
timeBasedToken = TimeBasedOTP.genOTPHmacSHA256(
|
||||
hexSharedSecret,
|
||||
Long.toHexString(currentTimeSeconds / interval).toUpperCase() + "",
|
||||
digits + "");
|
||||
} else if (crypto.equalsIgnoreCase("HmacSHA512")) {
|
||||
timeBasedToken = TimeBasedOTP.genOTPHmacSHA512(
|
||||
hexSharedSecret,
|
||||
Long.toHexString(currentTimeSeconds / interval).toUpperCase() + "",
|
||||
digits + "");
|
||||
}
|
||||
_logger.debug("token : " + token);
|
||||
_logger.debug("timeBasedToken : " + timeBasedToken);
|
||||
if (token.equalsIgnoreCase(timeBasedToken)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl.sms;
|
||||
|
||||
import com.aliyuncs.CommonRequest;
|
||||
import com.aliyuncs.CommonResponse;
|
||||
import com.aliyuncs.DefaultAcsClient;
|
||||
import com.aliyuncs.IAcsClient;
|
||||
import com.aliyuncs.http.MethodType;
|
||||
import com.aliyuncs.profile.DefaultProfile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.maxkey.crypto.password.otp.impl.SmsOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 阿里云短信验证.
|
||||
* @author shimingxy
|
||||
*
|
||||
*/
|
||||
public class SmsOtpAuthnAliyun extends SmsOtpAuthn {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SmsOtpAuthnAliyun.class);
|
||||
|
||||
public SmsOtpAuthnAliyun() {
|
||||
optType = OptTypes.SMS;
|
||||
}
|
||||
|
||||
//请替换你在管理后台应用下申请的accessKeyId
|
||||
private String accessKeyId = "94395d754eb55693043f5d6a2b772ef3";
|
||||
//请替换你在管理后台应用下申请的accessSecret
|
||||
private String accessSecret = "05d5485357bc";
|
||||
// 短信模板ID
|
||||
private String templateCode = "SMS_187590021";
|
||||
|
||||
private String signName = "MaxKey";
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
// 手机号
|
||||
String mobile = userInfo.getMobile();
|
||||
if (mobile != null && !mobile.equals("")) {
|
||||
try {
|
||||
DefaultProfile profile = DefaultProfile.getProfile(
|
||||
"cn-hangzhou", accessKeyId, accessSecret);
|
||||
IAcsClient client = new DefaultAcsClient(profile);
|
||||
|
||||
String token = this.genToken(userInfo);
|
||||
CommonRequest request = new CommonRequest();
|
||||
request.setSysMethod(MethodType.POST);
|
||||
request.setSysDomain("dysmsapi.aliyuncs.com");
|
||||
request.setSysVersion("2017-05-25");
|
||||
request.setSysAction("SendSms");
|
||||
request.putQueryParameter("RegionId", "cn-hangzhou");
|
||||
request.putQueryParameter("PhoneNumbers", mobile);
|
||||
request.putQueryParameter("SignName", signName);
|
||||
request.putQueryParameter("TemplateCode", templateCode);
|
||||
request.putQueryParameter("TemplateParam", "{\"code\":\"" + token + "\"}");
|
||||
CommonResponse response = client.getCommonResponse(request);
|
||||
logger.debug("responseString " + response.getData());
|
||||
//成功返回
|
||||
if (response.getData().indexOf("OK") > -1) {
|
||||
this.optTokenStore.store(
|
||||
userInfo,
|
||||
token,
|
||||
userInfo.getMobile(),
|
||||
OptTypes.SMS);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error(" produce code error ", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
return this.optTokenStore.validate(userInfo, token, OptTypes.SMS, interval);
|
||||
}
|
||||
|
||||
public String getAccessKeyId() {
|
||||
return accessKeyId;
|
||||
}
|
||||
|
||||
public void setAccessKeyId(String accessKeyId) {
|
||||
this.accessKeyId = accessKeyId;
|
||||
}
|
||||
|
||||
public String getAccessSecret() {
|
||||
return accessSecret;
|
||||
}
|
||||
|
||||
public void setAccessSecret(String accessSecret) {
|
||||
this.accessSecret = accessSecret;
|
||||
}
|
||||
|
||||
public String getTemplateCode() {
|
||||
return templateCode;
|
||||
}
|
||||
|
||||
public void setTemplateCode(String templateCode) {
|
||||
this.templateCode = templateCode;
|
||||
}
|
||||
|
||||
public String getSignName() {
|
||||
return signName;
|
||||
}
|
||||
|
||||
public void setSignName(String signName) {
|
||||
this.signName = signName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPropertys() {
|
||||
try {
|
||||
this.loadProperties();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.accessKeyId = this.properties.getProperty("config.otp.sms.aliyun.accesskeyid");
|
||||
this.accessSecret = this.properties.getProperty("config.otp.sms.aliyun.accesssecret");
|
||||
this.templateCode = this.properties.getProperty("config.otp.sms.aliyun.templatecode");
|
||||
this.signName = this.properties.getProperty("config.otp.sms.aliyun.signname");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl.sms;
|
||||
|
||||
import com.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.common.profile.ClientProfile;
|
||||
import com.tencentcloudapi.common.profile.HttpProfile;
|
||||
import com.tencentcloudapi.sms.v20190711.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
|
||||
import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.maxkey.crypto.password.otp.impl.SmsOtpAuthn;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
/**
|
||||
* 腾讯云短信验证.
|
||||
* @author shimingxy
|
||||
*
|
||||
*/
|
||||
public class SmsOtpAuthnTencentCloud extends SmsOtpAuthn {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SmsOtpAuthnTencentCloud.class);
|
||||
|
||||
//
|
||||
String secretId;
|
||||
//
|
||||
String secretKey;
|
||||
//短信SDKAPPID
|
||||
String smsSdkAppid;
|
||||
//短信模板
|
||||
String templateId;
|
||||
//签名
|
||||
String sign;
|
||||
|
||||
public SmsOtpAuthnTencentCloud() {
|
||||
optType = OptTypes.SMS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean produce(UserInfo userInfo) {
|
||||
// 手机号
|
||||
String mobile = userInfo.getMobile();
|
||||
if (mobile != null && !mobile.equals("")) {
|
||||
try {
|
||||
Credential cred = new Credential(secretId, secretKey);
|
||||
|
||||
HttpProfile httpProfile = new HttpProfile();
|
||||
httpProfile.setEndpoint("sms.tencentcloudapi.com");
|
||||
|
||||
ClientProfile clientProfile = new ClientProfile();
|
||||
clientProfile.setHttpProfile(httpProfile);
|
||||
|
||||
SmsClient client = new SmsClient(cred, "ap-beijing", clientProfile);
|
||||
String token = this.genToken(userInfo);
|
||||
String params = "{\"PhoneNumberSet\":[\"" + mobile + "\"],"
|
||||
+ "\"TemplateID\":\"" + templateId + "\",\"Sign\":\"" + sign + "\","
|
||||
+ "\"TemplateParamSet\":[\"" + token + "\",\"" + this.interval + "\"],"
|
||||
+ "\"SmsSdkAppid\":\"" + smsSdkAppid + "\"}";
|
||||
|
||||
SendSmsRequest req = SendSmsRequest.fromJsonString(params, SendSmsRequest.class);
|
||||
|
||||
SendSmsResponse resp = client.SendSms(req);
|
||||
|
||||
logger.debug("responseString " + SendSmsRequest.toJsonString(resp));
|
||||
if (resp.getSendStatusSet()[0].getCode().equalsIgnoreCase("Ok")) {
|
||||
this.optTokenStore.store(
|
||||
userInfo,
|
||||
token,
|
||||
userInfo.getMobile(),
|
||||
OptTypes.SMS);
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error(" produce code error ", e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
return this.optTokenStore.validate(userInfo, token, OptTypes.SMS, interval);
|
||||
}
|
||||
|
||||
|
||||
public String getSecretId() {
|
||||
return secretId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setSecretId(String secretId) {
|
||||
this.secretId = secretId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setSecretKey(String secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getSmsSdkAppid() {
|
||||
return smsSdkAppid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setSmsSdkAppid(String smsSdkAppid) {
|
||||
this.smsSdkAppid = smsSdkAppid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getTemplateId() {
|
||||
return templateId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setTemplateId(String templateId) {
|
||||
this.templateId = templateId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public String getSign() {
|
||||
return sign;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void setSign(String sign) {
|
||||
this.sign = sign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPropertys() {
|
||||
try {
|
||||
this.loadProperties();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.secretId = this.properties.getProperty("config.otp.sms.tencentcloud.secretid");
|
||||
this.secretKey = this.properties.getProperty("config.otp.sms.tencentcloud.secretkey");
|
||||
this.smsSdkAppid = this.properties.getProperty("config.otp.sms.tencentcloud.smssdkappid");
|
||||
this.templateId = this.properties.getProperty("config.otp.sms.tencentcloud.templateid");
|
||||
this.sign = this.properties.getProperty("config.otp.sms.tencentcloud.sign");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl.sms;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.maxkey.crypto.password.otp.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 String
|
||||
appKey = "94395d754eb55693043f5d6a2b772ef3";
|
||||
//网易云信分配的密钥,请替换你在管理后台应用下申请的appSecret
|
||||
private String appSecret = "05d5485357bc";
|
||||
// 短信模板ID
|
||||
private String templateId = "14860099";
|
||||
|
||||
@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");
|
||||
|
||||
// 设置请求的的参数,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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(UserInfo userInfo, String token) {
|
||||
return this.optTokenStore.validate(userInfo, token, OptTypes.SMS, interval);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getAppKey() {
|
||||
return appKey;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPropertys() {
|
||||
try {
|
||||
this.loadProperties();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.appKey = this.properties.getProperty("config.otp.sms.yunxin.appkey");
|
||||
this.appSecret = this.properties.getProperty("config.otp.sms.yunxin.appsecret");
|
||||
this.templateId = this.properties.getProperty("config.otp.sms.yunxin.templateid");
|
||||
}
|
||||
|
||||
/**
|
||||
* main.
|
||||
* @param args String
|
||||
* @throws Exception throws
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.impl.sms;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
|
||||
public class SmsOtpAuthnYunxinCheckSumBuilder {
|
||||
// 计算并获取CheckSum
|
||||
public static String getCheckSum(String appSecret, String nonce, String curTime) {
|
||||
return encode("sha1", appSecret + nonce + curTime);
|
||||
}
|
||||
|
||||
// 计算并获取md5值
|
||||
public static String getMD5(String requestBody) {
|
||||
return encode("md5", requestBody);
|
||||
}
|
||||
|
||||
private static String encode(String algorithm, String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
|
||||
messageDigest.update(value.getBytes());
|
||||
return getFormattedText(messageDigest.digest());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFormattedText(byte[] bytes) {
|
||||
int len = bytes.length;
|
||||
StringBuilder buf = new StringBuilder(len * 2);
|
||||
for (int j = 0; j < len; j++) {
|
||||
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
|
||||
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private static final char[] HEX_DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd','e', 'f' };
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.token;
|
||||
|
||||
import org.maxkey.domain.UserInfo;
|
||||
|
||||
public abstract class AbstractOtpTokenStore {
|
||||
|
||||
public abstract void store(UserInfo userInfo, String token, String receiver, String type);
|
||||
|
||||
public abstract boolean validate(UserInfo userInfo, String token, String type,int interval);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.token;
|
||||
|
||||
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.otp.OneTimePassword;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class InMemoryOtpTokenStore extends AbstractOtpTokenStore {
|
||||
private static final Logger logger = LoggerFactory.getLogger(InMemoryOtpTokenStore.class);
|
||||
|
||||
protected static final UserManagedCache<String, OneTimePassword> optTokenStore =
|
||||
UserManagedCacheBuilder.newUserManagedCacheBuilder(String.class, OneTimePassword.class)
|
||||
.withExpiry(
|
||||
ExpiryPolicyBuilder.timeToLiveExpiration(
|
||||
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 InMemoryOtpTokenStore() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.maxkey.crypto.password.otp.token;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.maxkey.constants.ConstantsTimeInterval;
|
||||
import org.maxkey.crypto.password.otp.OneTimePassword;
|
||||
import org.maxkey.domain.UserInfo;
|
||||
import org.maxkey.persistence.redis.RedisConnection;
|
||||
import org.maxkey.persistence.redis.RedisConnectionFactory;
|
||||
|
||||
public class RedisOtpTokenStore extends AbstractOtpTokenStore {
|
||||
|
||||
protected int validitySeconds = ConstantsTimeInterval.ONE_MINUTE * 5;
|
||||
|
||||
RedisConnectionFactory connectionFactory;
|
||||
|
||||
public RedisOtpTokenStore(RedisConnectionFactory connectionFactory) {
|
||||
super();
|
||||
this.connectionFactory = 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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user