提交 3e578642 编写于 作者: 智布道's avatar 智布道 👁

jap-mfa, Time based one-time password (TOTP)

上级 85bcc926
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jap</artifactId>
<groupId>com.fujieid</groupId>
<version>1.0.0-alpha.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jap-mfa</artifactId>
<name>jap-mfa</name>
<description>
Time based one-time password (TOTP), which is suitable for Google authenticator and TOTP authenticator of binary
boot
</description>
<dependencies>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
</project>
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.fujieid.jap.sso;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.warrenstrange.googleauth.*;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.File;
import java.io.IOException;
/**
* Jap Multi-Factor Authenticator
* <p>
* Time based one-time password (TOTP)
* <p>
* https://tools.ietf.org/html/rfc6238
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class JapMfa {
private static final Log log = LogFactory.get();
private final GoogleAuthenticator authenticator;
private final JapMfaConfig mfaConfig;
public JapMfa(ICredentialRepository credentialRepository, JapMfaConfig mfaConfig) {
this.mfaConfig = mfaConfig;
this.authenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
.setCodeDigits(mfaConfig.getDigits())
.setTimeStepSizeInMillis(mfaConfig.getPeriod())
.setHmacHashFunction(HmacHashFunction.valueOf(mfaConfig.getAlgorithm().name()))
.build());
authenticator.setCredentialRepository(credentialRepository);
}
public JapMfa(ICredentialRepository credentialRepository) {
this(credentialRepository, new JapMfaConfig());
}
public GoogleAuthenticator getAuthenticator() {
return authenticator;
}
public String getSecretKey(String username) {
return authenticator.getCredentialRepository().getSecretKey(username);
}
/**
* Returns the URL of totp
*
* @param username The user name
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return {@code String}
*/
public String getTotpUrl(String username, String issuer) {
final GoogleAuthenticatorKey key = authenticator.createCredentials(username);
return GoogleAuthenticatorQRGenerator.getOtpAuthTotpURL(issuer, username, key);
}
/**
* Returns the URL of a Google Chart API call to generate a QR barcode to be loaded into the Google Authenticator application.
* <p>
* The user scans this bar code with the application on their smart phones or enters the secret manually.
*
* @param username The user name
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @return {@code String}
*/
public String getOtpQrCodeUrl(String username, String issuer) {
final GoogleAuthenticatorKey key = authenticator.createCredentials(username);
return GoogleAuthenticatorQRGenerator.getOtpAuthURL(issuer, username, key);
}
/**
* One time password verification by user name
*
* @param username The user name
* @param otpCode The totp code
* @return {@code bool}
*/
public boolean verifyByUsername(String username, int otpCode) {
return authenticator.authorizeUser(username, otpCode);
}
/**
* One time password verification by OTP secret key
*
* @param secret The totp secret Key.
* @param otpCode The totp code
* @return {@code bool}
*/
public boolean verifyBySecret(String secret, int otpCode) {
return authenticator.authorize(secret, otpCode);
}
/**
* Create OTP QR code and write it to browser through {@code HttpServletResponse}
*
* @param username The user name
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @param response HttpServletResponse
*/
public void createOtpQrcode(String username, String issuer, HttpServletResponse response) {
try {
QrCodeUtil.generate(getTotpUrl(username, issuer),
mfaConfig.getQrcodeWidth(), mfaConfig.getQrcodeHeight(),
mfaConfig.getQrcodeImgType(), response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Create and return the QR code file of OTP
*
* @param username The user name
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
*/
public File getOtpQrcodeFile(String username, String issuer) {
String tempFilePath = mfaConfig.getQrcodeTempPath();
String tempFileFullPath = tempFilePath.concat(username)
.concat(String.valueOf(System.currentTimeMillis()))
.concat(".")
.concat(mfaConfig.getQrcodeImgType());
log.debug("Create QR code: {}", tempFileFullPath);
File file = FileUtil.newFile(tempFileFullPath);
if (FileUtil.exist(file)) {
FileUtil.del(file);
} else {
FileUtil.mkParentDirs(file);
}
return QrCodeUtil.generate(getTotpUrl(username, issuer),
mfaConfig.getQrcodeWidth(), mfaConfig.getQrcodeHeight(), file);
}
/**
* Create and return the base64 string of OTP QR code
*
* @param username The user name
* @param issuer The issuer name. This parameter cannot contain the colon (:) character.
* @param deleteFile Delete temporary QR code file
*/
public String getOtpQrcodeFileBase64(String username, String issuer, boolean deleteFile) {
File imgFile = this.getOtpQrcodeFile(username, issuer);
Image image = ImgUtil.read(imgFile);
String base64Str = ImgUtil.toBase64DataUri(image, mfaConfig.getQrcodeImgType());
if (deleteFile) {
FileUtil.del(imgFile);
}
return base64Str;
}
}
package com.fujieid.jap.sso;
/**
* The cryptographic hash function used to calculate the HMAC (Hash-based Message Authentication Code).
* <p>
* This implementation uses the SHA1 hash function by default.
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public enum JapMfaAlgorithm {
/**
* SHA1
*/
HmacSHA1,
/**
* SHA256
*/
HmacSHA256,
/**
* SHA512
*/
HmacSHA512
}
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.fujieid.jap.sso;
import java.io.File;
/**
* Multi-Factor Authenticator Configuration
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class JapMfaConfig {
/**
* the number of digits in the generated code.
*/
private int digits = 6;
/**
* the time step size, in milliseconds, as specified by RFC 6238. The default value is 30.000s.
*
* @see <a href="https://tools.ietf.org/html/rfc6238#section-5.2" target="_blank">5.2. Validation and Time-Step Size</a>
*/
private long period = 30000;
/**
* the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512)
*/
private JapMfaAlgorithm algorithm = JapMfaAlgorithm.HmacSHA1;
private String qrcodeTempPath = System.getProperties().getProperty("user.home") + File.separator + "jap" + File.separator + "temp";
private int qrcodeWidth = 200;
private int qrcodeHeight = 200;
private String qrcodeImgType = "gif";
public int getDigits() {
return digits;
}
public JapMfaConfig setDigits(int digits) {
this.digits = digits;
return this;
}
public long getPeriod() {
return period;
}
public JapMfaConfig setPeriod(long period) {
this.period = period;
return this;
}
public JapMfaAlgorithm getAlgorithm() {
return algorithm;
}
public JapMfaConfig setAlgorithm(JapMfaAlgorithm algorithm) {
this.algorithm = algorithm;
return this;
}
public String getQrcodeTempPath() {
return qrcodeTempPath;
}
public JapMfaConfig setQrcodeTempPath(String qrcodeTempPath) {
this.qrcodeTempPath = qrcodeTempPath;
return this;
}
public int getQrcodeWidth() {
return qrcodeWidth;
}
public JapMfaConfig setQrcodeWidth(int qrcodeWidth) {
this.qrcodeWidth = qrcodeWidth;
return this;
}
public int getQrcodeHeight() {
return qrcodeHeight;
}
public JapMfaConfig setQrcodeHeight(int qrcodeHeight) {
this.qrcodeHeight = qrcodeHeight;
return this;
}
public String getQrcodeImgType() {
return qrcodeImgType;
}
public JapMfaConfig setQrcodeImgType(String qrcodeImgType) {
this.qrcodeImgType = qrcodeImgType;
return this;
}
}
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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.
*/
/**
* Time based one-time password (TOTP), which is suitable for Google authenticator and TOTP authenticator of binary boot
*
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.0
*/
package com.fujieid.jap.sso;
package com.fujieid.jap.sso;
import com.warrenstrange.googleauth.ICredentialRepository;
import java.util.List;
/**
* @author yadong.zhang (yadong.zhang0415(a)gmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class CredentialRepositoryImpl implements ICredentialRepository {
public CredentialRepositoryImpl(){
}
/**
* This method retrieves the Base32-encoded private key of the given user.
*
* @param userName the user whose private key shall be retrieved.
* @return the private key of the specified user.
*/
@Override
public String getSecretKey(String userName) {
return null;
}
/**
* This method saves the user credentials.
*
* @param userName the user whose data shall be saved.
* @param secretKey the generated key.
* @param validationCode the validation code.
* @param scratchCodes the list of scratch codes.
*/
@Override
public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
}
}
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com).
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* 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 com.fujieid.jap.sso;
import com.warrenstrange.googleauth.ICredentialRepository;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.Scanner;
public class JapMfaTest {
public static final String username = "japname";
public static final String issuer = "japissue";
public static void main(String[] args) {
JapMfa japMfa = new JapMfa(new CredentialRepositoryImpl());
// 以下三种方式,任选一个测试
// 1. 生成 file
// File file = japMfa.createOtpQrcodeFile(username, issuer);
// System.out.println(file);
// 生成 base64 字符串
// String base64 = japMfa.createOtpQrcodeFileBase64(username, issuer, "qrcode.png", true);
// System.out.println(base64);
// 生成 url 链接
String otpQrCodeUrl = japMfa.getOtpQrCodeUrl(username, issuer);
System.out.println(otpQrCodeUrl);
varifyCode(japMfa);
}
private static void varifyCode(JapMfa japMfa) {
String secretKey = japMfa.getSecretKey(username);
System.out.println("1. 你需要打开生成的文件(或者将 Base64 字符串直接粘贴到浏览器地址会回车)");
System.out.println("2. 然后使用 OTP 工具扫描二维码");
System.out.println("3. 在控制台输入 code");
Scanner scanner = new Scanner(System.in);
Integer consoleInput = null;
while (!(consoleInput = scanner.nextInt()).equals(-1)) {
System.out.println(japMfa.getAuthenticator().getTotpPassword(secretKey));
System.out.println(japMfa.getAuthenticator().getTotpPasswordOfUser(username));
boolean verifyByUsernameResult = japMfa.verifyByUsername(username, consoleInput);
System.out.println(new Date() + " 通过用户名检验 code 的结果:" + (verifyByUsernameResult ? "通过" : "code 错误"));
boolean verifyBySecretResult = japMfa.verifyBySecret(secretKey, consoleInput);
System.out.println(new Date() + " 通过 secretKey 检验 code 的结果:" + (verifyBySecretResult ? "通过" : "code 错误"));
}
System.out.println("结束...");
}
public static class CredentialRepositoryImpl implements ICredentialRepository {
public static String secret = "";
@Override
public String getSecretKey(String userName) {
//根据帐号查询secretKey
return secret;
}
@Override
public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
// secretKey要保存在数据库中
System.out.println("[saveUserCredentials] userName:" + userName);
System.out.println("[saveUserCredentials] secretKey:" + secretKey);
System.out.println("[saveUserCredentials] validationCode:" + validationCode);
secret = secretKey;
}
}
}
......@@ -45,6 +45,7 @@
<module>jap-oauth2</module>
<module>jap-sso</module>
<module>jap-oidc</module>
<module>jap-mfa</module>
</modules>
<properties>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册