SpringBoot + mybatis-plus + HMACOTP 实现双因素身份验证功能
双因素身份验证(Two-Factor Authentication,简称2FA)是一种增强用户身份验证安全性的方法,通常结合使用密码和另外一种身份验证因素,如一次性密码(OTP)来进行验证。在这里,我们将使用Spring Boot、MyBatis-Plus和HMACOTP来实现2FA功能。HMACOTP是一种基于HMAC(哈希消息认证码)的OTP算法。
要实现双因素身份验证功能,我们将使用拦截器来对用户进行验证。首先,我们需要在数据库中创建一个表来保存用户信息,包含字段id、user_name、password、nick_name、create_time和 secret_key 。接下来,我们会创建一个名为com.icoderoad.example.demo的包,并在其中提供相应的代码和详细注释。
创建用户表及初始化用户的SQL脚本:
CREATE TABLE `otp_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(50) NOT NULL,
`password` varchar(80) NOT NULL,
`nick_name` varchar(50) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`secret_key` varchar(120) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `otp_user` (`id`, `user_name`, `password`, `nick_name`, `create_time`, `secret_key`)
VALUES
(1, 'admin', '123456', '管理员', '2023-08-03 19:09:48', 'nl2pm0s078qbqg');
我们需要配置Spring Boot项目,并添加MyBatis-Plus和HMACOTP的相关依赖。在pom.xml文件中添加如下依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
创建用户实体类 OtpUser.java,使用Lombok注解简化代码:
package com.icoderoad.example.demo.entity;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
@Data
@TableName("otp_user")
public class OtpUser {
private Long id;
private String userName;
private String password;
private String nickName;
private Date createTime;
private String secretKey;
}
创建用户Mapper接口 OtpUserMapper.java:
package com.icoderoad.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.icoderoad.example.demo.entity.OtpUser;
public interface OtpUserMapper extends BaseMapper<OtpUser> {
}
创建用户服务类 OtpUserService.java,用于操作数据库:
package com.icoderoad.example.demo.service;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.icoderoad.example.demo.entity.OtpUser;
import com.icoderoad.example.demo.mapper.OtpUserMapper;
@Service
public class OtpUserService extends ServiceImpl {
}
创建双因素身份验证工具类 HMACOTPUtil.java:
package com.icoderoad.example.demo.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Base64;
public class HMACOTPUtil {
// 有效的HMACOTP验证时间窗口(秒)
private static final int VALID_WINDOW_SECONDS = 300;
public static boolean isValidHMACOTP(String otp, String secretKey) {
try {
// 获取当前时间的时间戳(秒)
long currentTimestamp = Instant.now().getEpochSecond();
// 尝试5分钟内验证时间窗口内的HMACOTP
for (int i = -1; i <= VALID_WINDOW_SECONDS; i++) {
long timestamp = currentTimestamp + i;
// 计算HMACOTP
String generatedOTP = generateHMACOTP(secretKey, timestamp);
// 比较生成的HMACOTP和传入的HMACOTP是否相同
if (otp.equals(generatedOTP)) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static String generateHMACOTP(String secretKey, long timestamp) throws NoSuchAlgorithmException, InvalidKeyException {
// 将时间戳转换为字节数组
byte[] timestampBytes = String.valueOf(timestamp).getBytes();
// 创建HMAC算法实例,这里使用HmacSHA256
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
// 使用秘钥初始化HMAC算法
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
hmacSha256.init(secretKeySpec);
// 计算HMAC
byte[] hmacBytes = hmacSha256.doFinal(timestampBytes);
// 对HMAC进行Base64编码
return Base64.getEncoder().encodeToString(hmacBytes);
}
}
上面的代码中,我们创建了一个HMACOTPUtil类,其中 isValidHMACOTP 方法用于验证传入的HMACOTP是否在指定时间内有效。该方法会尝试验证当前时间的5分钟内时间戳的HMACOTP,允许一定的时间差。
创建双因素身份验证拦截器HMACOTPInterceptor.java:
package com.icoderoad.example.demo.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.icoderoad.example.demo.entity.OtpUser;
import com.icoderoad.example.demo.service.OtpUserService;
import com.icoderoad.example.demo.util.HMACOTPUtil;
public class HMACOTPInterceptor implements HandlerInterceptor {
private OtpUserService otpUserService;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userName = request.getParameter("userName");
String password = request.getParameter("password");
String otp = request.getParameter("otp"); // 第二因素:HMACOTP验证码
// 检查用户名和密码是否正确
OtpUser user = otpUserService.getOne(Wrappers.lambdaQuery().eq(OtpUser::getUserName, userName));
if (user == null || !user.getPassword().equals(password)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("用户名或密码错误");
return false;
}
// 检查HMACOTP验证码是否正确
String secretKey = user.getSecretKey();
if (!HMACOTPUtil.isValidHMACOTP(otp, secretKey)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("HMACOTP验证码错误");
return false;
}
// 用户身份验证通过
return true;
}
}
注册拦截器到Spring Boot应用程序中,修改类InterceptorConfig.java:
package com.icoderoad.example.demo.conf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.icoderoad.example.demo.interceptor.HMACOTPInterceptor;
import com.icoderoad.example.demo.interceptor.JwtInterceptor;
public class InterceptorConfig implements WebMvcConfigurer {
private final JwtInterceptor jwtInterceptor;
private HMACOTPInterceptor hmacotpInterceptor;
public InterceptorConfig(JwtInterceptor jwtInterceptor) {
this.jwtInterceptor = jwtInterceptor;
}
public void addInterceptors(InterceptorRegistry registry) {
// 添加 JwtInterceptor 拦截器,并指定拦截的路径
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/jwt/user/**"); // 可根据实际路径配置
registry.addInterceptor(hmacotpInterceptor)
.addPathPatterns("/secure/**"); // 在此处添加需要进行身份验证的URL路径
}
}
让我们增加一个Controller来实现相应的HMACOTP验证逻辑。假设我们的Controller路径为/secure/login,用于处理用户登录请求。以下是相应的代码:
创建登录响应DTO类LoginResponseDTO.java:
package com.icoderoad.example.demo.dto;
import lombok.Data;
@Data
public class LoginResponseDTO {
private String message;
}
创建登录Controller类 OtpLoginController.java:
package com.icoderoad.example.demo.controller;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.icoderoad.example.demo.dto.LoginResponseDTO;
import com.icoderoad.example.demo.entity.OtpUser;
import com.icoderoad.example.demo.service.OtpUserService;
import com.icoderoad.example.demo.util.HMACOTPUtil;
public class OtpLoginController {
private final OtpUserService userService;
public OtpLoginController(OtpUserService userService) {
this.userService = userService;
}
public LoginResponseDTO login( String userName, String password, String otp) {
LoginResponseDTO responseDTO = new LoginResponseDTO();
// 检查用户名和密码是否正确
OtpUser user = userService.getOne(Wrappers.lambdaQuery().eq(OtpUser::getUserName, userName));
if (user == null || !user.getPassword().equals(password)) {
responseDTO.setMessage("用户名或密码错误");
return responseDTO;
}
// 检查HMACOTP验证码是否正确
String secretKey = user.getSecretKey();
if (!HMACOTPUtil.isValidHMACOTP(otp, secretKey)) {
responseDTO.setMessage("HMACOTP验证码错误");
return responseDTO;
}
responseDTO.setMessage("登录成功");
return responseDTO;
}
public String generateOTP( String userName) {
OtpUser user = userService.getOne(Wrappers.lambdaQuery().eq(OtpUser::getUserName, userName));
if (user == null) {
return "用户不存在";
}
// 获取当前时间的时间戳(秒)
long currentTimestamp = Instant.now().plusSeconds(300).getEpochSecond();
// 生成HMACOTP
String otp="";
try {
otp = HMACOTPUtil.generateHMACOTP(user.getSecretKey(), currentTimestamp);
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "生成的OTP:" + otp;
}
}
在上述代码中,我们创建了一个LoginController,调用 /secure/generate-otp 请求获取 指定用户 admin 获取 otp值,根据生成的otp值,处理POST请求/secure/login。该控制器接收LoginRequestDTO对象,其中包含用户名、密码和HMACOTP验证码。然后,它对用户名和密码进行验证,并调用之前定义的isValidHMACOTP方法来验证HMACOTP验证码是否正确。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=16568,转载请注明出处。
评论0