做一个springboot登陆注册功能

news2024/11/25 5:37:23

目录

一、环境搭建

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.coloradmin.cn/o/1202715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IP地址如何实现定位功能?

网络犯罪、保护网络安全的重要手段。近日&#xff0c;一则新闻引起了广大网友的关注&#xff1a;IP也能实现定位功能&#xff0c;这是如何做到的呢&#xff1f;本文将对此进行深入解析。 首先&#xff0c;我们需要了解什么是IP地址定位。IP地址定位是通过IP地址确定网络用户所在…

面试被问答3-5年职业规划,该怎么回答

面试官问这些问题的目的是什么&#xff1f;他想得到什么满意的答案。只要清楚这些&#xff0c;就不难回答这个问题。 1、你有没有上进心&#xff1f;公司是否值得培养呢&#xff1f; 你需要对专业能力充满向往&#xff0c;希望自己在3~5年内&#xff0c;把专业能力做好&#…

字符串和内存函数(1)

文章目录 目录1. 前言2. 函数介绍2.1 strlen2.2 strcpy2.3 strcat2.4 strcmp2.5 strncpy2.6 strncat2.7 strncmp2.8 strstr2.9 strtok2.10 strerror2.11 字符分类函数2.12 字符转换函数 目录 求字符串长度函数长度不受限制的字符串函数长度受限制的字符串函数字符串查找函数错…

TikTok影响力经济:解锁社交媒体的商业机遇

社交媒体平台的崛起改变了我们与世界互动的方式&#xff0c;而TikTok作为其中的一员&#xff0c;已经成为全球范围内的现象。这个短视频应用不仅让用户在几秒钟内分享创意和娱乐&#xff0c;还为企业和创作者提供了巨大的商业机会。本文将深入探讨TikTok的影响力经济&#xff0…

linux grub2 不引导修复 grub2-install:error:/usr/lib/grub/x86_64-efi/modinfo.sh

系统部署在物理机上&#xff0c;开机后一直pxe不进系统&#xff0c;怀疑GRUB丢失。 查看bios 里 采用uefi 启动方式&#xff0c; 无硬盘系统引导选项&#xff0c; 且BMC设置为硬盘永久启动也无效。 挂载光驱ISO进入救援模式,sda为系统盘&#xff0c;重装grub报错 grub2-inst…

如何用自然语言 5 分钟构建个人知识库应用?我的 GPTs builder 尝试

开发者的想象力闸门一旦打开&#xff0c;迎接我们的必然是目不暇接的 AI 应用浪潮冲击。 兴奋 早晨&#xff0c;我突然发现 ChatGPT 最新的 Create GPTs 功能可以用了。 这太让我意外了&#xff0c;没想到这么快。根据页面上的提示&#xff0c;我一直以为还得等上一周左右。于是…

腾讯待办为什么停止运营?ics文件如何导入日程APP继续使用?

有不少网友表示自己想要记录待办事项、设置待办提醒的时候&#xff0c;会直接使用微信中的腾讯待办小程序来记录。但是最近这段时间在使用这款小程序设置待办提醒的时候&#xff0c;看到了“业务关停通知”的弹窗&#xff0c;大意就是说&#xff0c;腾讯待办将于2023年12月20日…

CoRL 2023 获奖论文公布,manipulation、强化学习等主题成热门

今年大模型及具身智能领域有了非常多的突破性进展&#xff0c;作为机器人学与机器学习交叉领域的全球顶级学术会议之一&#xff0c;CoRL也得到了更多的关注。 CoRL 是面向机器人学习的顶会&#xff0c;涵盖机器人学、机器学习和控制等多个主题&#xff0c;包括理论与应用。今年…

CCF ChinaSoft 2023 论坛巡礼 | 生成式AI与软件自动化论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

说说react中引入css的方式有哪几种?区别?

一、是什么 组件式开发选择合适的css解决方案尤为重要 通常会遵循以下规则: 可以编写局部css,不会随意污染其他组件内的原生;可以编写动态的css,可以获取当前组件的一些状态,根据状态的变化生成不同的css样式;支持所有的css特性:伪类、动画、媒体查询等;编写起来简洁…

牛客网:OR36 链表的回文结构

一、题目 函数原型&#xff1a; bool chkPalindrome(ListNode* A) 二、思路 判断一个单链表是否为回文结构&#xff0c;由于单链表不能倒序遍历&#xff0c;所以需要找到单链表的后半段&#xff0c;并将其逆置&#xff0c;再与前半段链表进行比较。 如何找到单链表的后半段呢&a…

3D造型渲染软件DAZ Studio mac中文版介绍

DAZ Studio mac是一款3D造型和渲染软件&#xff0c;由 Daz 3D 公司开发。它允许用户创建、编辑、动画化并渲染精美的数字图像与动画。DAZ Studio 还提供了一个虚拟的3D艺术家工作室环境&#xff0c;让用户可以轻松地设置场景、布置角色和应用材质。 用户可以通过 DAZ Studio 中…

麒麟KYLINOS中使用Ghost镜像文件还原系统

原文链接&#xff1a;麒麟KYLINOS中使用Ghost镜像文件还原系统 hello&#xff0c;大家好啊&#xff0c;今天给大家带来麒麟KYLINOS备份还原的第三篇文章&#xff0c;使用Ghost镜像文件还原系统&#xff0c;将之前做好的Ghost镜像文件拷贝到u盘里&#xff0c;然后在另一台终端上…

如何在群晖虚拟机快速部署线上web网站并实现公网访问

文章目录 前言1. 安装网页运行环境1.1 安装php1.2 安装webstation 2. 下载网页源码文件2.1 访问网站地址并下载压缩包2.2 解压并上传至群辉NAS 3. 配置webstation3.1 配置网页服务3.2 配置网络门户 4. 局域网访问静态网页配置成功5. 使用cpolar发布静态网页&#xff0c;实现公网…

2023年【汽车驾驶员(高级)】证考试及汽车驾驶员(高级)实操考试视频

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车驾驶员&#xff08;高级&#xff09;证考试考前必练&#xff01;安全生产模拟考试一点通每个月更新汽车驾驶员&#xff08;高级&#xff09;实操考试视频题目及答案&#xff01;多做几遍&#xff0c;其实通过汽车…

ASAM OpenDRIVE V1.7协议超详解(一)

文章目录 前言一、仿真场景的构成二、openDRIVE框架三、g_additionalData四、openDRIVE-header五、openDRIVE-road1、Road总拓扑结构2、Road-link介绍1&#xff09;link的拓扑结构2&#xff09;link链接示例3&#xff09;link前继后继4&#xff09;道路link规则 3、road-type介…

从0到0.01入门React | 005.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

理事长走进统信软件,深度探讨社区发展规划 | 理事长走进系列

10 月 19 日&#xff0c;龙蜥社区“理事长走进理事单位系列交流会”活动第二期开展&#xff0c;本期走进龙蜥社区副理事长单位——统信软件&#xff0c;会议共出席 17 人。会上回顾了统信软件过去在龙蜥社区的贡献和投入&#xff0c;并共同对未来的合作计划和诉求进行了深度讨论…

cpcd 使用

cpcd 是支持多协议融合的一种解决方案&#xff0c;应用场景是一个无线模块支持大于一种协议栈&#xff0c;通过cpcd 可以分发不同协议&#xff0c;使用说明主要查看readme.md 文件说明 编译 使用cpcd 4.3.2 提示需要安装mbedtls 编译成功了 运行 关闭加密&#xff0c;通信…

普通线性回归和评估指标代码实战

我们用加州房价预测来讲述普通线性回归的算法实战和预测指标。在这里省去数据预处理和特征工程的步骤。首先导入相应的模块&#xff1a; from sklearn.linear_model import LinearRegression as LR from sklearn.model_selection import train_test_split from sklearn.model_…