做一个springboot登陆注册功能

目录

一、环境搭建

1、数据库

2、引入依赖 

 3、配置信息

4、创建包结构和数据库实体类

二、接口开发-注册接口

前提准备

响应数据

需求分析

全局异常处理

代码编写

 测试

三、接口开发-登录接口

前提准备

响应数据

需求分析

代码编写

测试

拦截器 

测试


一、环境搭建

1、数据库

-- 创建数据库
create database big_event;

-- 使用数据库
use big_event;

-- 用户表
create table user (
                      id int unsigned primary key auto_increment comment 'ID',
                      username varchar(20) not null unique comment '用户名',
                      password varchar(32)  comment '密码',
                      nickname varchar(10)  default '' comment '昵称',
                      email varchar(128) default '' comment '邮箱',
                      user_pic varchar(128) default '' comment '头像',
                      create_time datetime not null comment '创建时间',
                      update_time datetime not null comment '修改时间'
) comment '用户表';

-- 分类表
create table category(
                         id int unsigned primary key auto_increment comment 'ID',
                         category_name varchar(32) not null comment '分类名称',
                         category_alias varchar(32) not null comment '分类别名',
                         create_user int unsigned not null comment '创建人ID',
                         create_time datetime not null comment '创建时间',
                         update_time datetime not null comment '修改时间',
                         constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束
);

-- 文章表
create table article(
                        id int unsigned primary key auto_increment comment 'ID',
                        title varchar(30) not null comment '文章标题',
                        content varchar(10000) not null comment '文章内容',
                        cover_img varchar(128) not null  comment '文章封面',
                        state varchar(3) default '草稿' comment '文章状态: 只能是[已发布] 或者 [草稿]',
                        category_id int unsigned comment '文章分类ID',
                        create_user int unsigned not null comment '创建人ID',
                        create_time datetime not null comment '创建时间',
                        update_time datetime not null comment '修改时间',
                        constraint fk_article_category foreign key (category_id) references category(id),-- 外键约束
                        constraint fk_article_user foreign key (create_user) references user(id) -- 外键约束
)

2、引入依赖 

        <dependency>    
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

 3、配置信息

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Drive
    url: jdbc:mysql://localhost:3306/big_event
    username: root
    password: 123456
    
server:
  port: 8888

4、创建包结构和数据库实体类


二、接口开发-注册接口

前提准备

先看user实体类,我们之所以没有为字段设置getter和setter就是因为我们可以为了代码的美观与简洁而使用lombok工具

lombok:在编译阶段,为实体类自动生成getter、setter、toString

使用方法就是再类上加上注解@Data

响应数据

在实际的项目开发中,不会像之前写的代码一样,直接return回去一个字符串或者数据。而是有统一的响应结果的,也就是Result类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;//业务状态码  0-成功  1-失败
    private String message;//提示信息
    private T data;//响应数据

    //快速返回操作成功响应结果(带响应数据)
    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    //快速返回操作成功响应结果
    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static Result error(String message) {
        return new Result(1, message, null);
    }
}

@NoArgsConstructor:自动创建无参构造器
@AllArgsConstructor:自动创建有参构造器

需求分析

        注册功能,首先就要去确认用户名是否被占用,之后才能注册。其次既然是提交注册表单请求,那么一般就属于POST请求,而查询等业务才是GET请求。

        既然要注册用户,那么就不能原封不动的将密码加入数据库,一定要对数据进行加密,这样才安全。我们这里使用MD5加密,可以直接使用引入工具类也可以选择引入依赖。这里就使用MD5工具类了。

        除次之外还要对参数进行校验,比如要求是密码要是5~16位非空字符,那么就要先检查是否符合要求,其次才能添加进数据库。

Spring提供了一个参数校验框架,使用预定义的注解完成参数校验,叫做Spring Validation。

MD5加密与SpringValidation框架的使用方法icon-default.png?t=N7T8https://blog.csdn.net/m0_56308072/article/details/131101062?spm=1001.2014.3001.5501

全局异常处理

这个的目的就是因为当我们测试失败时,响应得到的接过并不符合result风格,也不美观。

我们更希望即使报这样的错误也要符合result类,因此我们可以定义一个全局类

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e){
        e.printStackTrace();
        //有的异常可能没有message,所以要先用三元运算符判断一下
        return Result.error(StringUtils
                .hasLength(e.getMessage()) ? e.getMessage() : "操作失败");
    }

}

这样就可以捕获全局的异常了。

代码编写

Controller层

@Validated
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/register")   //校验
    public Result register(@Pattern(regexp = "^\\S{5,16}$") String username,
                           @Pattern(regexp = "^\\S{5,16}$") String password) {

        //查询用户
        User user = userService.findByUserName(username);
        if (user == null){
            //注册
            userService.register(username,password);
            return Result.success();
        }else {
            //占用
            return Result.error("用户名以及被占用");
        }

    }
}

 Service层

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public User findByUserName(String name) {
        return userMapper.findByUserName(name);
    }

    @Override
    public void register(String username, String password) {
        //加密
        String md5Password = Md5Util.getMD5String(password);
        //添加
        userMapper.register(username,md5Password);
    }
}

 Mapper层

@Mapper
public interface UserMapper {

    @Select("select * from user where username = #{name}")
    User findByUserName(String name);

    @Insert("insert into user(username,password,create_time,update_time)" +
            "values(#{username},#{md5Password},now(),now())")
    void register(String username, String md5Password);
}

 测试


三、接口开发-登录接口

前提准备

        既然是开发登录接口,那么最多的就是使用JWT令牌进行登录

什么是JWT?如何使用icon-default.png?t=N7T8https://blog.csdn.net/m0_56308072/article/details/131144785?spm=1001.2014.3001.5501

导入依赖

        <!-- JWT -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

响应数据

需求分析

为什么要使用jwt令牌,简单来说就是因为,我们其实是可以直接跳过登录验证/login,直接进入/list去访问数据。这当然是不符合安全的。

因此才需要一块jwt令牌,只有通过/login才能可以拿到令牌,然后访问/list的时候只有持有令牌才可以进行访问

令牌就是一段字符串,作用是承载业务数据,减少后续请求查询数据库的次数。

防止篡改,保证信息的合法性和有效性

        用户登陆后,系统会自动下发JWT令牌,然后在后续的请求中,浏览器都需要在请求头header中携带到服务端,请求头的名称为Authorization,值为登陆时下发的JWT令牌。就是jwt携带在请求头中,我们在获取验证的时候就要hetHeader得到token然后解析验证。

        如果检测到用户未登录,则http响应状态码为401。那么就需要一个responese对象(HttpServletResponse),它可以更改状态码

首先就要根据输入的用户名进行查询,判断用户是否存在,然后再查询密码进行登录验证

代码编写

JwtUtil

public class JwtUtil {

    private static final String KEY = "wal";

    //接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256(KEY));
    }

    //接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

Controller层

登录成功,生成token

    @PostMapping("/login")
    public Result<String> login(@Pattern(regexp = "^\\S{5,16}$") String username,
                                @Pattern(regexp = "^\\S{5,16}$") String password){
        //查询用户
        User loginUser = userService.findByUserName(username);

        //判断用户是否存在
        if (loginUser == null){
            return Result.error("用户名错误");
        }

        //判断密码是否正确 loginUser对象中password是密文
        if(Md5Util.getMD5String(password).equals(loginUser.getPassword())){
            //登录成功,生成Token
            Map<String,Object> claims = new HashMap<>();
            claims.put("id",loginUser.getId());
            claims.put("username",loginUser.getUsername());
            String token = JwtUtil.genToken(claims);
            return Result.success(token);
        }

        return Result.error("密码错误");
    }

 访问其他接口,验证token

@RestController
@RequestMapping("/article")
public class ArticleController {
    @GetMapping("/list")
    public Result<String> list(@RequestHeader(name = "Authorization") String token,
                               HttpServletResponse response){
        //验证token
        try {
            Map<String, Object> claim = JwtUtil.parseToken(token);

            return Result.success("访问成功");
        } catch (Exception e) {
            //未登录
            response.setStatus(401);
            throw new RuntimeException(e);
        }

    }
}

测试

尝试跳过登录验证直接访问数据接口

正常登录,生成密钥

携带密钥访问数据接口

注:如果觉得每次都复制粘贴返回的token用来测试太麻烦,可以直接在这里设置一个全局的token,这样就不用每次测试接口的时候都在header里面粘贴上去token了。当然,改完记得save一下tab,不然不会生效。

但是注意,这里配置完全局token之后就不要再单独携带token,这样就会携带两个token过去,并报错

com.auth0.jwt.exceptions.JWTDecodeException: The token was expected to have 3 parts, but got > 3.  

拦截器 

向上面的例子中,我们只有一个数据访问接口的时候体现不出来,但是假如我们现在已经写了很多个接口,如果每个接口中都写验证token的逻辑未免也太繁琐了。我们希望这种复用性强的代码只写一次就好了,然后我们可以再不惊动原代码的情况下对功能进行添加,也就是AOP的思想。在这里应用的技术就是拦截器。

使用拦截器,首先要创建一个拦截器类然后集成HandlerInterceptor接口。其中重要的是preHandle方法

        preHandle方法:在目标方法执行前执行,也就是在你访问接口的时候就直接拦截下来。那么拦截下来之后就要验证token了。

我们之前是通过参数声明的方式直接拿到,但是在重写的这个方法中并没有。这是因为他直接被包含在了参数request中(HttpServletRequest),这个对象顾名思义,就是代表请求,所有的请求数据都在request对象中;反之,与其相对的是参数response(HttpServletRequest)代表响应,所有的响应数据都在response数据中。

@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取令牌
        String token = request.getHeader("Authorization");
        //验证token
        try {
            Map<String,Object> claims = JwtUtil.parseToken(token);
            //没有异常就放行
            return true;
        } catch (Exception e) {
            //未登录,不放行
            response.setStatus(401);
            return false;
        }

    }
}

         此时虽然完成了拦截器,但是他还没有生效,需要先把他注册之后才会生效,保证安全。在拦截器上加上@Component注解那么就代表交给了IOC容器管理变成了一个bean对象。

        那么我们就在config创建一个拦截器的config表示启用哪些拦截器,重写其中的addIntercepter方法。顾名思义,就是添加那些拦截器。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //放行登录接口和注册接口
        registry.addInterceptor(loginInterceptor)
                .excludePathPatterns("/user/login","/user/register");
    }
}

注意:既然是拦截器,那么它就能拦截所有的接口。一定要注意不能让它拦截登录接口和注册接口,不然要访问数据接口就要登录生成的token,要登陆的token就要访问登录接口,由于登录接口也被拦截了,那么访问登录接口就要登录生成的token,要登录的token就要访问登录接口......开启了无限套娃。addInterceptor就是添加要使用的拦截器,excludePathPatterns就是要排除拦截的接口.

测试

当我们不携带令牌时,无响应,且状态码为401

携带令牌时成功访问 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/137818.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超图Web许可无法访问

1. 报错 docker 容器(7f6f88XXXXX)找不到许可&#xff0c;查看日志&#xff0c;发现报错日志 2. 原因&#xff1a; 查看管理页面&#xff0c;发现许可被172.17.0.8占用 根据容器id寻找容器&#xff0c;找不到&#xff0c;猜测可能是以前删除过的容器&#xff0c;占用了名额 解决…

第十八章DOM操作控制

DOM操作分类&#xff1a; jQuery中的DOM操作 一。设置和获取样式值 1.追加样式 addClass&#xff1a;追加CSS效果 removeClass&#xff1a;去除CSS内容 2.切换样式 3.判断是否含有指定样式&#xff1a;判断的是布尔类型的值 二。内容操作&#xff1a; 1.HTML代码操作 2.TEXT代…

用示波器测量高压电

示波器本身是不可以测试几千v的高压电电路的&#xff0c;一般自带的探头衰减倍数不够&#xff0c;需要使用高压差分探头或者高压探棒&#xff0c;将测试信号衰减到合适的范围再接入示波器。 普通探头能测差分电压吗&#xff1f;差分探头和普通探头有什么区别&#xff1f;全网最…

1002:简单多项式求值

题目描述&#xff1a;输入任一整数&#xff0c;输出以下多项式的值。 y2x2x8 输入&#xff1a;输入整数x的值。 输出&#xff1a;输出一个整数&#xff0c;即多项式的值。 程序代码&#xff1a; #include <stdio.h> int main() {int x,y;//定义两个整型变量x和yscanf(&q…

ADC学习系列(一):ADC基础概念

本章主要是进行ADC的基础概念学习&#xff0c;从模拟和数字信号进行入手&#xff0c;分析各自的优缺点和应用场合&#xff0c;从而引出数模转换的重要性。紧接着提到了ADC部分最重要的奈奎斯特采样定理&#xff0c;了解采样频率和被测信号频率之间的关系。最后介绍了ADC的采样保…

matlab背景部分最小化算法人脸检测

1、内容简介 略 18-可以交流、咨询、答疑 matlab背景部分最小化算法人脸检测 2、内容说明 matlab人脸检测 matlab人脸检测&#xff0c;背景部分最小化算法 3、仿真分析 略. 4、参考论文 略 链接&#xff1a;https://pan.baidu.com/s/1yQ1yDfk-_Qnq7tGpa23L7g 提取码&…

【LeetCode刷题-二分查找】--704.二分查找

704.二分查找 class Solution {public int search(int[] nums, int target) {if(nums.length 0){return -1;}int left 0,right nums.length - 1;while(left < right){int mid (right - left) / 2 left;if(nums[mid] target){return mid;}else if(nums[mid] < targe…

Pytorch损失函数、反向传播和优化器、Sequential使用

Pytorch_Sequential使用、损失函数、反向传播和优化器 文章目录 nn.Sequential搭建小实战损失函数与反向传播优化器 nn.Sequential nn.Sequential是一个有序的容器&#xff0c;用于搭建神经网络的模块被按照被传入构造器的顺序添加到nn.Sequential()容器中。 import torch.nn …

不可思议,才一周,阅读量就突破千万了

这段时间&#xff0c;我发布的文章阅读量看上去还算可以&#xff0c;但我知道&#xff0c;这并不是终点&#xff0c;而是起点。我深知&#xff0c;写作的道路永无止境&#xff0c;只有不断努力&#xff0c;才能在文字的海洋中游得更远。 在这条道路上&#xff0c;我始终坚持用心…

学习率范围测试(LR Finder)脚本

简介 深度学习中的学习率是模型训练中至关重要的超参数之一。合适的学习率可以加速模型的收敛&#xff0c;提高训练效率&#xff0c;而不恰当的学习率可能导致训练过慢或者无法收敛。为了找到合适的学习率&#xff0c;LR Finder成为了一种强大的工具。 学习率范围测试&#x…

springboot项目基本配置

接口入口日志 参数校验 业务逻辑执行 异常捕获-统一异常处理 统一数据返回体 接口返回日志 使用的是springboot2.x版本。 Mybatisplus 官网地址&#xff1a;https://baomidou.com/ 导入依赖 <dependency><groupId>com.baomidou</groupId><artifactId&g…

前端工具nvm实现node自由

node的自由之路 前言 大家使用vue框架开发的朋友可能会遇到首次运行公司项目环境的时候&#xff0c;会出现使用npm install命令安装依赖包的时候出现各种各样的问题&#xff0c;其中很重要的一个错误原因就是因为你的nodejs版本和当时搭建环境的版本不一致造成的。今天就来给…

c语言-数据结构-栈和队列的实现和解析

目录 一、栈 1、栈的概念 1.2 栈的结构 2、栈的创建及初始化 3、压栈操作 4、出栈操作 5、显示栈顶元素 6、显示栈空间内元素的总个数 7、释放栈空间 8、测试栈 二、队列 1、队列的概念 1.2 队列的结构 2、队列的创建及初始化 3、入队 4、出队 5、显示队头、队…

creo之混合和扫描混合

案例一&#xff1a;杯子 步骤&#xff1a; 在top平面画一个草图圆角矩形&#xff1a; 然后形状–》混合 然后绘制新增的截面2&#xff1a; 用中心线将圆分割成八分&#xff0c;因为底部的圆角矩形是八份线段组成&#xff0c;所以我们要和他一样分成八份&#xff1a;先画中心线…

详解Java:抽象类和接口

前言&#xff1a;在前文中我们学习认知到了多态的使用和相关知识&#xff0c;算是打开了Java世界的大门&#xff0c;而本次要分享的抽象类和接口则是我们在面向对象编程中最常用的编程结构之一 目录 一.抽象类 abstract 抽象类特性 二.接口 语法规则 接口使用 接口特…

第十七章jQuery中的事件与动画

一。常用事件&#xff1a; 1.鼠标事件&#xff1a; mouseover()&#xff1a;在鼠标进入内容后一直显示事件 mouseout()&#xff1a;在鼠标离开内容后一直显示事件 mouseenter()&#xff1a;在进入刹那间显示事件 mouseleave()&#xff1a;在退出刹那间显示事件 案例&#xf…

windows系统升级powershell7

利用winget在线升级脚本 REM --accept-package-agreements 接受包的所有许可协议 REM --accept-source-agreements 在源操作期间接受所有源协议 winget install Microsoft.PowerShell --accept-source-agreements 应该在路径C:\Program Files\PowerShel…

功能案例 -- 拖拽上传文件,生成缩略图

直接看效果 实现代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>拖拽上传文件</title>&l…

两个序列(数论)

两个序列 Problem:B Time Limit:1000ms Memory Limit:65535K Description Gugu 有两个长度无限长的序列A,BA0a^0/0!,A1a^1/1!,A2a^2/2!,A3a^3/3!…. B00, B1b^1/1!,B20,B3b^3/3!,B40, B5b^5/5! … Douge 看到这道这两个序列很觉得很麻烦&#xff0c;所以他想到一个好点子&…

EMNLP 2023 | 基于知识图谱嵌入的关系感知集成学习算法

©PaperWeekly 原创 作者 | 黄鉦皓 单位 | 清华大学 研究方向 | 图神经网络 本文介绍《基于知识图谱嵌入的关系感知集成学习算法》&#xff08;Relation-aware Ensemble Learning for Knowledge Graph Embedding&#xff09;&#xff0c;该论文提出的 RelEns-DSC 方法针对…