【笑小枫的SpringBoot系列】【九】SpringBoot用户登录功能实现(下)

简介: 【笑小枫的SpringBoot系列】【九】SpringBoot用户登录功能实现(下)

密码加密与验证


首先再自定义一个异常吧,创建一个通用点的MapleCommonException.java,后续都偷懒统一抛这个异常了


代码如下:

package com.maple.demo.config.exception;
import com.maple.demo.config.bean.ErrorCode;
/**
 * 通用异常,偷懒就抛出此异常吧
 *
 * @author 笑小枫
 * @date 2022/07/20
 */
public class MapleCommonException extends MapleBaseException {
    public MapleCommonException(String code, String errorMsg) {
        super(code, errorMsg);
    }
    public MapleCommonException(ErrorCode code) {
        super(code);
    }
    public MapleCommonException(ErrorCode code, String errorMsg) {
        super(code, errorMsg);
    }
}

密码加密就简单的使用md5加盐值吧,代码如下👇

package com.maple.demo.util;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.exception.MapleCommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
/**
 * MD5撒盐加密 及MD5加密
 *
 * @author 笑小枫
 * @date 2022/7/20
 */
@Slf4j
public class Md5Util {
    private Md5Util() {
    }
    /**
     * 密码加密处理
     *
     * @param password 密码明文
     * @param salt     盐
     * @return 加密后密文
     */
    public static String encrypt(String password, String salt) {
        if (StringUtils.isEmpty(password) || StringUtils.isEmpty(salt)) {
            log.error("密码加密失败原因: password and salt cannot be empty");
            throw new MapleCommonException(ErrorCode.PARAM_ERROR);
        }
        return DigestUtils.md5DigestAsHex((salt + password).getBytes());
    }
    /**
     * 校验密码
     *
     * @param target 待校验密码
     * @param source 原密码
     * @param salt   加密原密码的盐
     */
    public static boolean verifyPassword(String target, String source, String salt) {
        if (StringUtils.isEmpty(target) || StringUtils.isEmpty(source) || StringUtils.isEmpty(salt)) {
            log.info("校验密码失败,原因 target ={}, source ={}, salt ={}", target, source, salt);
            return false;
        }
        String targetEncryptPwd = encrypt(target, salt);
        return targetEncryptPwd.equals(source);
    }
    public static void main(String[] args) {
        log.info(encrypt("admin111", "123456"));
    }
}


通过main方法生成一个加密后的值吧,然后把盐值和密码都扔到数据库里面,后面我们就根据账号(account)和密码(password)进行登录。


注意:创建用户的时候先随机生成一个盐(salt),后续根据盐值再去生成密码。


用户登录接口


model和param

创建一个vo包吧,后续的model和query对象统一放在这里了~

在vo包下创建一个query包,然后创建登录请求对象LoginQuery.java

package com.maple.demo.vo.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author 笑小枫
 * @date 2022/7/20
 */
@Data
@ApiModel(value = "用户登录请求对象", description = "用户中心-用户登录请求对象")
public class LoginQuery {
    @ApiModelProperty(value = "登录账号")
    private String account;
    @ApiModelProperty(value = "登录密码")
    private String password;
}


在vo包下创建一个model包,然后创建返回的用户信息对象UserModel.java

package com.maple.demo.vo.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
/**
 * 用户中心-用户信息
 *
 * @author 笑小枫
 * @date 2022/7/20
 */
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@ApiModel(value = "用户视图对象", description = "用户中心-用户信息")
public class UserModel {
    @ApiModelProperty(value = "用户ID")
    private Long id;
    @ApiModelProperty(value = "用户账号")
    private String account;
    @ApiModelProperty(value = "用户姓名")
    private String userName;
    @ApiModelProperty(value = "用户昵称")
    private String nickName;
    @ApiModelProperty(value = "用户类型")
    private String userType;
    @ApiModelProperty(value = "用户邮箱")
    private String email;
    @ApiModelProperty(value = "手机号码")
    private String phone;
    @ApiModelProperty(value = "用户性别")
    private String sex;
    @ApiModelProperty(value = "头像地址")
    private String avatar;
    @ApiModelProperty(value = "帐号状态")
    private String status;
    @ApiModelProperty(value = "备注")
    private String remark;
    @ApiModelProperty(value = "用户验证Token")
    private String token;
}


controller

package com.maple.demo.controller;
import com.maple.demo.service.IUserService;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * 系统登录
 *
 * @author 笑小枫
 * @date 2022/7/20
 */
@Api(tags = "管理系统-系统登录操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController {
    private final IUserService userService;
    @ApiOperation(value = "用户登录")
    @PostMapping("/login")
    public UserModel login(@RequestBody LoginQuery req) {
        return userService.login(req);
    }
    @ApiOperation(value = "用户退出登录")
    @GetMapping("/logout")
    public void logout() {
        userService.logout();
    }
}


service

package com.maple.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.maple.demo.entity.User;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
/**
 * <p>
 * 用户中心-用户信息表 服务类
 * </p>
 *
 * @author 笑小枫
 * @since 2022-07-11
 */
public interface IUserService extends IService<User> {
    /**
     * 用户登录
     *
     * @param req 用户信息
     * @return 用户登录信息
     */
    UserModel login(LoginQuery req);
    /**
     * 退出系统,清除用户token
     */
    void logout();
}


serviceImpl

package com.maple.demo.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.maple.demo.config.bean.ErrorCode;
import com.maple.demo.config.bean.GlobalConfig;
import com.maple.demo.config.bean.TokenBean;
import com.maple.demo.config.exception.MapleCheckException;
import com.maple.demo.entity.User;
import com.maple.demo.mapper.UserMapper;
import com.maple.demo.service.IUserService;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.util.Md5Util;
import com.maple.demo.util.RedisUtil;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
 * <p>
 * 用户中心-用户信息表 服务实现类
 * </p>
 *
 * @author Maple
 * @since 2022-07-11
 */
@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private final UserMapper userMapper;
    private final RedisUtil redisUtil;
    @Override
    public UserModel login(LoginQuery req) {
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class)
                .eq(User::getAccount, req.getAccount())
                .last("LIMIT 1"));
        if (Objects.isNull(user)) {
            throw new MapleCheckException(ErrorCode.USER_LOGIN_ERROR);
        }
        if ("1".equals(user.getStatus())) {
            throw new MapleCheckException(ErrorCode.USER_STATUS_ERROR);
        }
        if (!Md5Util.verifyPassword(req.getPassword(), user.getPassword(), user.getSalt())) {
            throw new MapleCheckException(ErrorCode.USER_LOGIN_ERROR);
        }
        TokenBean tokenBean = TokenBean.builder()
                .userId(user.getId())
                .userType(user.getUserType())
                .account(user.getUserName())
                .build();
        UserModel userModel = new UserModel();
        BeanUtils.copyProperties(user, userModel);
        String token;
        try {
            token = JwtUtil.createToken(tokenBean);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new MapleCheckException(ErrorCode.COMMON_ERROR);
        }
        userModel.setToken(token);
        redisUtil.set(GlobalConfig.getRedisUserKey(user.getAccount()), token);
        return userModel;
    }
    @Override
    public void logout() {
        redisUtil.remove(GlobalConfig.getRedisUserKey(JwtUtil.getAccount()));
    }
}


使用JWT的用户信息


直接使用JwtUtil.class工具类里的方法,即可拿到对应的数据


JwtUtil.getUserId();
JwtUtil.getAccount();


功能测试


测试之前需要在数据中添加一条数据


INSERT INTO `maple`.`usc_user`( `account`, `user_name`, `nick_name`, `user_type`, `email`, `phone`, `sex`, `avatar`, `salt`, `password`, `status`, `create_id`, `create_name`, `create_time`, `update_id`, `update_name`, `update_time`, `delete_flag`, `remark`) VALUES ('admin', 'admin', '笑小枫', '00', '1150640979@qq.com', '18300000001', '0', '', '123456', 'e9c764f9b51772f00af80a54d38a692e', '0', 1, '笑小枫', '2022-07-11 13:48:44', 1, '笑小枫', '2022-07-11 13:48:44', 0, '管理员');


直接在LoginController.java里面添加getUserId方法进行测试,详细代码如下:

package com.maple.demo.controller;
import com.maple.demo.service.IUserService;
import com.maple.demo.util.JwtUtil;
import com.maple.demo.vo.model.UserModel;
import com.maple.demo.vo.query.LoginQuery;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
 * 系统登录
 *
 * @author 笑小枫
 * @date 2022/7/20
 */
@Api(tags = "管理系统-系统登录操作")
@RestController
@AllArgsConstructor
@RequestMapping(value = "/sso")
public class LoginController {
    private final IUserService userService;
    @ApiOperation(value = "用户登录")
    @PostMapping("/login")
    public UserModel login(@RequestBody LoginQuery req) {
        return userService.login(req);
    }
    @ApiOperation(value = "用户退出登录")
    @GetMapping("/logout")
    public void logout() {
        userService.logout();
    }
    @ApiOperation(value = "获取登录用户信息")
    @GetMapping("/getUserId")
    public String getUserId() {
        return "当前登录用户的ID为" + JwtUtil.getUserId();
    }
}


在未登录状态请求接口,返回信息如下:


aea2befd4c03ee4b6f25a085bb67f59a.png


调用登录接口,进行用户登录


4ef28b777ebb34bd5a0634e6c9fa210a.png


登录后,拿到token,在请求头设置Authorization参数

a87def92f603455910aa65ac3eadffe0.png


添加完之后,记得要把tab页关闭,再打开,然后header参数才会生效,在请求头部可以看到

再次调用,返回信息如下:


ad0ef6df11f352f00b053a6c81789bdf.png



可以看到,到此我们的登录拦截功能就已经完全实现了。


小结


好啦,本文就到这里了,我们简单的总结一下,主要介绍了以下内容👇👇

  • 介绍了什么是JWT
  • 用户登录拦截
  • 用户登录实现


关于笑小枫💕


本章到这里结束了,喜欢的朋友关注一下我呦😘😘,大伙的支持,就是我坚持写下去的动力。

老规矩,懂了就点赞收藏;不懂就问,日常在线,我会就会回复哈~🤪

笑小枫个人博客:https://www.xiaoxiaofeng.com

本文源码:https://github.com/hack-feng/maple-demo

目录
相关文章
|
2月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
439 2
|
8月前
|
XML 前端开发 Java
SpringBoot实现文件上传下载功能
本文介绍了如何使用SpringBoot实现文件上传与下载功能,涵盖配置和代码实现。包括Maven依赖配置(如`spring-boot-starter-web`和`spring-boot-starter-thymeleaf`)、前端HTML页面设计、WebConfig路径映射配置、YAML文件路径设置,以及核心的文件上传(通过`MultipartFile`处理)和下载(利用`ResponseEntity`返回文件流)功能的Java代码实现。文章由Colorful_WP撰写,内容详实,适合开发者学习参考。
785 0
|
5月前
|
缓存 前端开发 Java
SpringBoot 实现动态菜单功能完整指南
本文介绍了一个动态菜单系统的实现方案,涵盖数据库设计、SpringBoot后端实现、Vue前端展示及权限控制等内容,适用于中后台系统的权限管理。
494 1
|
7月前
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
|
6月前
|
监控 安全 Java
Java 开发中基于 Spring Boot 3.2 框架集成 MQTT 5.0 协议实现消息推送与订阅功能的技术方案解析
本文介绍基于Spring Boot 3.2集成MQTT 5.0的消息推送与订阅技术方案,涵盖核心技术栈选型(Spring Boot、Eclipse Paho、HiveMQ)、项目搭建与配置、消息发布与订阅服务实现,以及在智能家居控制系统中的应用实例。同时,详细探讨了安全增强(TLS/SSL)、性能优化(异步处理与背压控制)、测试监控及生产环境部署方案,为构建高可用、高性能的消息通信系统提供全面指导。附资源下载链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)。
1120 0
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
243 0
|
8月前
|
SQL 前端开发 Java
深入理解 Spring Boot 项目中的分页与排序功能
本文深入讲解了在Spring Boot项目中实现分页与排序功能的完整流程。通过实际案例,从Service层接口设计到Mapper层SQL动态生成,再到Controller层参数传递及前端页面交互,逐一剖析每个环节的核心逻辑与实现细节。重点包括分页计算、排序参数校验、动态SQL处理以及前后端联动,确保数据展示高效且安全。适合希望掌握分页排序实现原理的开发者参考学习。
543 4
|
8月前
|
存储 Java 定位技术
SpringBoot整合高德地图完成天气预报功能
本文介绍了如何在SpringBoot项目中整合高德地图API实现天气预报功能。从创建SpringBoot项目、配置依赖和申请高德地图API开始,详细讲解了实体类设计、服务层实现(调用高德地图API获取实时与预报天气数据)、控制器层接口开发以及定时任务的设置。通过示例代码,展示了如何获取并处理天气数据,最终提供实时天气与未来几天天气预报的接口。文章还提供了测试方法及运行步骤,帮助开发者快速上手并扩展功能。
|
消息中间件 缓存 Java
手写模拟Spring Boot启动过程功能
【11月更文挑战第19天】Spring Boot自推出以来,因其简化了Spring应用的初始搭建和开发过程,迅速成为Java企业级应用开发的首选框架之一。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,帮助读者深入理解其工作机制。
185 3