Skip to content

Commit

Permalink
⚡ jap-mfa, Time based one-time password (TOTP)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangyd-c committed Feb 6, 2021
1 parent 85bcc92 commit 3e57864
Show file tree
Hide file tree
Showing 8 changed files with 498 additions and 0 deletions.
32 changes: 32 additions & 0 deletions jap-mfa/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?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>
174 changes: 174 additions & 0 deletions jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfa.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 ([email protected] & 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;
}
}
25 changes: 25 additions & 0 deletions jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfaAlgorithm.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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
}
113 changes: 113 additions & 0 deletions jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfaConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 ([email protected] & 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;
}
}
23 changes: 23 additions & 0 deletions jap-mfa/src/main/java/com/fujieid/jap/sso/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2020-2040, 北京符节科技有限公司 ([email protected] & 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;
Loading

0 comments on commit 3e57864

Please sign in to comment.