SpringBoot + mybatis-plus + HMACOTP 实现双因素身份验证功能

这一系列课程将包含Springboot相关的各种功能实现及功能示例

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;

@Servicepublic 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;

@Componentpublic class HMACOTPInterceptor implements HandlerInterceptor {

@Autowiredprivate OtpUserService otpUserService;
    @Override    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;

@Configurationpublic class InterceptorConfig implements WebMvcConfigurer {

    private final JwtInterceptor jwtInterceptor;        @Autowired    private HMACOTPInterceptor hmacotpInterceptor;

    @Autowired    public InterceptorConfig(JwtInterceptor jwtInterceptor) {        this.jwtInterceptor = jwtInterceptor;    }

    @Override    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;

@Datapublic 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;

@RestControllerpublic class OtpLoginController {

    private final OtpUserService userService;

    @Autowired    public OtpLoginController(OtpUserService userService) {        this.userService = userService;    }

    @PostMapping("/secure/login")    public LoginResponseDTO login(@RequestParam String userName, @RequestParam String password, @RequestParam 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;    }        @GetMapping("/otp/generate-otp")    public String generateOTP(@RequestParam 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 blocke.printStackTrace();} catch (NoSuchAlgorithmException e) {// TODO Auto-generated catch blocke.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

评论0

显示验证码
没有账号?注册  忘记密码?