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

原创 路条编程 路条编程 2023-08-04 19:30 发表于北京

收录于合集

#java30个

#SpringBoot29个

图片

这一系列课程将包含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文件中添加如下依赖:

<!-- Spring Boot --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency>

<!-- MyBatis-Plus --><dependency>    <groupId>com.baomidou</groupId>    <artifactId>mybatis-plus-boot-starter</artifactId>    <version>3.5.3.1</version></dependency>

<!-- MySQL数据库驱动 --><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId></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<OtpUserMapper, OtpUser> {}

创建双因素身份验证工具类 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.<OtpUser>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.<OtpUser>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.<OtpUser>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验证码是否正确。

示例中代码,可以从 https://github.com/icoderoad/wxdemo.git 下载。

今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码关注公众号。也可以添加 happyzjp 微信受邀加入学习社群,我们会尽力为你解答。练习网站已经正式上线,大家可以登陆网站 http://www.icoderoad.com 进行文章中示例的练习。

Spring BootMyBatis-Plus是现代Java应用开发中的两个流行框架组合,它们一起简化了企业级应用程序的快速搭建。以下是一个基本的Spring Boot + MyBatis-Plus的整体开发流程: 1. **项目初始化**: - 创建一个新的Spring Boot项目(使用Maven或Gradle构建工具)。 - 添加Spring Boot Web、Spring Data JPA和MyBatis-Plus的依赖。 2. **数据库配置**: - 配置数据源(如HikariCP, Druid, 或者Spring Boot内置的DataSource)。 - 定义数据库连接池和JDBC驱动。 3. **实体类映射**: - 创建Java实体类(Entity),它们将对应数据库表结构。 - 使用MyBatis-Plus的Model Generator工具自动生成基础CRUD操作的代码。 4. **Mapper接口生成**: - 通过MyBatis-Plus的全局配置文件(GlobalConfig),指定Mapper接口生成的位置。 5. **Service层编写**: - 实现业务逻辑的服务类(Service),这些类通常会依赖于MyBatis-Plus的Mapper接口进行数据库操作。 6. **Repository接口和实现**: - 如果需要,可以创建Repository接口,继承自MyBatis-Plus的BaseMapper,进一步定制化查询。 7. **Controller层处理**: - 创建RESTful API控制器(Controller),调用Service层的方法,并返回JSON响应给客户端。 8. **配置数据初始化**: - 编写Spring Boot的启动类(Application)中的main方法,可能包含一些数据初始化操作。 9. **测试**: - 使用单元测试(JUnit, TestNG等)确保各个组件正常工作,如Service和Mapper接口的测试。 10. **部署**: - 部署项目到生产环境,如Tomcat, Jetty, 或者云服务器上的Web容器。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值