timebased otp 优化

This commit is contained in:
shimingxy
2024-08-27 17:33:21 +08:00
parent 60560bf72d
commit a098df18a3
12 changed files with 151 additions and 141 deletions

View File

@@ -48,7 +48,7 @@ import org.dromara.maxkey.password.sms.SmsOtpAuthnService;
import org.dromara.maxkey.authn.provider.scancode.ScanCodeService;
import org.dromara.maxkey.persistence.service.SocialsAssociatesService;
import org.dromara.maxkey.persistence.service.UserInfoService;
import org.dromara.maxkey.util.RQCodeUtils;
import org.dromara.maxkey.util.QRCodeUtils;
import org.dromara.maxkey.web.WebConstants;
import org.dromara.maxkey.web.WebContext;
import org.slf4j.Logger;
@@ -286,7 +286,7 @@ public class LoginEntryPoint {
String ticket = scanCodeService.createTicket();
log.debug("ticket: {}",ticket);
String encodeTicket = PasswordReciprocal.getInstance().encode(ticket);
BufferedImage bufferedImage = RQCodeUtils.write2BufferedImage(encodeTicket, "gif", 300, 300);
BufferedImage bufferedImage = QRCodeUtils.write2BufferedImage(encodeTicket, "gif", 300, 300);
String rqCode = Base64Utils.encodeImage(bufferedImage);
HashMap<String,String> codeMap = new HashMap<>();
codeMap.put("rqCode", rqCode);

View File

@@ -18,29 +18,29 @@
package org.dromara.maxkey.web.contorller;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils;
import org.dromara.maxkey.authn.annotation.CurrentUser;
import org.dromara.maxkey.crypto.Base32Utils;
import org.dromara.maxkey.crypto.Base64Utils;
import org.dromara.maxkey.crypto.password.PasswordReciprocal;
import org.dromara.maxkey.entity.Message;
import org.dromara.maxkey.entity.dto.TimeBasedDto;
import org.dromara.maxkey.entity.idm.UserInfo;
import org.dromara.maxkey.password.onetimepwd.algorithm.OtpKeyUriFormat;
import org.dromara.maxkey.password.onetimepwd.algorithm.OtpSecret;
import org.dromara.maxkey.password.onetimepwd.impl.TimeBasedOtpAuthn;
import org.dromara.maxkey.persistence.service.UserInfoService;
import org.dromara.maxkey.util.RQCodeUtils;
import org.dromara.maxkey.util.QRCodeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -48,8 +48,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Crystal.Sea
*
*/
@Controller
@RequestMapping(value = { "/config" })
@RestController
@RequestMapping(value = { "/config/timebased" })
public class OneTimePasswordController {
static final Logger logger = LoggerFactory.getLogger(OneTimePasswordController.class);
@@ -62,55 +62,73 @@ public class OneTimePasswordController {
@Autowired
TimeBasedOtpAuthn timeBasedOtpAuthn;
@RequestMapping(value = {"/timebased"})
@ResponseBody
public Message<?> timebased(
@RequestParam(name="generate") String generate,
@CurrentUser UserInfo currentUser) {
HashMap<String,Object >timebased =new HashMap<>();
generate(generate,currentUser);
String sharedSecret =
PasswordReciprocal.getInstance().decoder(currentUser.getSharedSecret());
otpKeyUriFormat.setSecret(sharedSecret);
String otpauth = otpKeyUriFormat.format(currentUser.getUsername());
byte[] byteSharedSecret = Base32Utils.decode(sharedSecret);
String hexSharedSecret = Hex.encodeHexString(byteSharedSecret);
BufferedImage bufferedImage = RQCodeUtils.write2BufferedImage(otpauth, "gif", 300, 300);
String rqCode = Base64Utils.encodeImage(bufferedImage);
timebased.put("displayName", currentUser.getDisplayName());
timebased.put("username", currentUser.getUsername());
timebased.put("digits", otpKeyUriFormat.getDigits());
timebased.put("period", otpKeyUriFormat.getPeriod());
timebased.put("sharedSecret", sharedSecret);
timebased.put("hexSharedSecret", hexSharedSecret);
timebased.put("rqCode", rqCode);
return new Message<HashMap<String,Object >>(timebased);
@GetMapping(value = {"/view"})
public Message<TimeBasedDto> view(@CurrentUser UserInfo currentUser) {
UserInfo user = userInfoService.get(currentUser.getId());
String sharedSecret = "";
String qrCode = "";
if(StringUtils.isNotBlank(user.getSharedSecret())) {
sharedSecret = PasswordReciprocal.getInstance().decoder(user.getSharedSecret());
qrCode = genQRCode(sharedSecret,currentUser.getUsername());
}
return new Message<>(
new TimeBasedDto(
user.getDisplayName(),
user.getUsername(),
otpKeyUriFormat.getDigits(),
otpKeyUriFormat.getPeriod(),
sharedSecret,
qrCode,
""
));
}
public void generate(String generate,@CurrentUser UserInfo currentUser) {
if((StringUtils.isNotBlank(generate)
&& generate.equalsIgnoreCase("YES"))
||StringUtils.isBlank(currentUser.getSharedSecret())) {
byte[] byteSharedSecret = OtpSecret.generate(otpKeyUriFormat.getCrypto());
String sharedSecret = Base32Utils.encode(byteSharedSecret);
sharedSecret = PasswordReciprocal.getInstance().encode(sharedSecret);
currentUser.setSharedSecret(sharedSecret);
userInfoService.updateSharedSecret(currentUser);
@GetMapping(value = {"/generate"})
public Message<TimeBasedDto> generate(@CurrentUser UserInfo currentUser) {
//generate
byte[] byteSharedSecret = OtpSecret.generate(otpKeyUriFormat.getCrypto());
String sharedSecret = Base32Utils.encode(byteSharedSecret);
String qrCode = genQRCode(sharedSecret,currentUser.getUsername());
return new Message<>(
new TimeBasedDto(
currentUser.getDisplayName(),
currentUser.getUsername(),
otpKeyUriFormat.getDigits(),
otpKeyUriFormat.getPeriod(),
sharedSecret,
qrCode,
""
));
}
@PutMapping(value = {"/update"})
public Message<String> update(@RequestBody TimeBasedDto timeBasedDto , @CurrentUser UserInfo currentUser) {
// 从当前用户信息中获取共享密钥
UserInfo user = new UserInfo();
user.setId(currentUser.getId());
user.setSharedSecret(PasswordReciprocal.getInstance().encode(timeBasedDto.sharedSecret()));
// 计算当前时间对应的动态密码
if (StringUtils.isNotBlank(timeBasedDto.otpCode()) && timeBasedOtpAuthn.validate(user, timeBasedDto.otpCode())) {
userInfoService.updateSharedSecret(user);
return new Message<>(Message.SUCCESS);
} else {
return new Message<>(Message.FAIL);
}
}
@RequestMapping("/verify")
public Message<?> verify(@RequestParam("otp") String otp, @CurrentUser UserInfo currentUser) {
public String genQRCode(String sharedSecret,String username) {
otpKeyUriFormat.setSecret(sharedSecret);
String otpauth = otpKeyUriFormat.format(username);
BufferedImage bufferedImage = QRCodeUtils.write2BufferedImage(otpauth, "gif", 300, 300);
return Base64Utils.encodeImage(bufferedImage);
}
@GetMapping("/verify")
public Message<String> verify(@RequestParam("otpCode") String otpCode, @CurrentUser UserInfo currentUser) {
// 从当前用户信息中获取共享密钥
String sharedSecret = PasswordReciprocal.getInstance().decoder(currentUser.getSharedSecret());
UserInfo user = userInfoService.get(currentUser.getId());
// 计算当前时间对应的动态密码
boolean validate = timeBasedOtpAuthn.validate(currentUser, otp);
boolean validate = timeBasedOtpAuthn.validate(user, otpCode);
if (validate) {
return new Message<>(0,"One-Time Password verification succeeded");
} else {