做一个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.Driveurl: jdbc:mysql://localhost:3306/big_eventusername: rootpassword: 123456server: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 {@Autowiredprivate 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 {@Autowiredprivate UserMapper userMapper;@Overridepublic User findByUserName(String name) {return userMapper.findByUserName(name);}@Overridepublic 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())){//登录成功,生成TokenMap<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){//验证tokentry {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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取令牌String token = request.getHeader("Authorization");//验证tokentry {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 {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//放行登录接口和注册接口registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");}
}

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

测试

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

携带令牌时成功访问 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字典管理怎么使用,vue3项目使用若依的的字典管理模块

若依框架数据字典的使用_若依数据字典_哈哈水水水水的博客-CSDN博客 【精选】关于数据字典的理解与设计_数据字典怎么设计-CSDN博客 若依的字典值如何使用&#xff08;超详细图文教程&#xff09;_若依字典管理_丿BAIKAL巛的博客-CSDN博客 Vue3组合式API&#xff1a;getCurr…

行业追踪,2023-11-13

自动复盘 2023-11-13 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

CentOs7 NAT模式连接网络

1.配置动态网络 1.1 检查主机网卡配置 检查主机的网络设置 进入控制面板&#xff0c;找到网络共享中心 查看适配器是否都已经开启 1.2 设置虚拟机的网络配置 打开虚拟机网络配置设置&#xff0c;对网卡VMnet8 进行设置 记住网关 全部选择应用&#xff0c;确定 1.3 设置…

您的计算机已被Mallox勒索病毒感染?恢复您的数据的方法在这里!

尊敬的读者&#xff1a; 随着科技的迅速发展&#xff0c;网络安全问题日益凸显&#xff0c;其中勒索病毒是一种极具威胁性的恶意软件。在这些勒索病毒中&#xff0c;.mallox 勒索病毒尤为突出&#xff0c;它能够加密用户的数据文件&#xff0c;要求支付赎金才能解密。本文将介…

【Python基础】一个简单的TCP通信程序

&#x1f308;欢迎来到Python专栏 &#x1f64b;&#x1f3fe;‍♀️作者介绍&#xff1a;前PLA队员 目前是一名普通本科大三的软件工程专业学生 &#x1f30f;IP坐标&#xff1a;湖北武汉 &#x1f349; 目前技术栈&#xff1a;C/C、Linux系统编程、计算机网络、数据结构、Mys…

从0开始python学习-34.pytest常用插件

目录 1. pytest-html&#xff1a;生成HTML测试报告 2.pytest-xdist&#xff1a;并发执行用例 3. pytest-order&#xff1a;自定义用例的执行顺序 4. pytest-rerunfailures&#xff1a;用例失败时自动重试 5. pytest-result-log:用例执行结果记录到日志文件 1. pytest-html…

STM32笔记—USART

课外知识插入&#xff1a;STM32单片机extern全局变量_stm32全局变量-CSDN博客 如果你把temple定义在A中&#xff0c;然后让A.h和B.h包含在includes.h中&#xff0c;然后把includes.h放在A.c和B.c中单个编译是没有问题的&#xff0c;但是链接的时候会出现问题&#xff0c; “S…

Rust编程中的共享状态并发执行

1.共享状态并发 虽然消息传递是一个很好的处理并发的方式&#xff0c;但并不是唯一一个。另一种方式是让多个线程拥有相同的共享数据。在学习Go语言编程过程中大家应该听到过一句口号:"不要通过共享内存来通讯"。 在某种程度上&#xff0c;任何编程语言中的信道都类…

Django(五、视图层)

文章目录 一、视图层1.视图函数返回值的问题2.三板斧的使用结论&#xff1a;在视图文件中写视图函数的时候不能没有返回值&#xff0c;默认返回的是None&#xff0c;但是页面上会报错&#xff0c;用来处理请求的视图函数都必须返回httpResponse对象。 二、JsonReponse序列化类的…

力扣 232. 用栈实现队列(C语言实现)

目录 1.解题思路2.代码实现 1.解题思路 利用两个栈&#xff0c;设栈s1为入栈,s2为出栈&#xff0c;则当s2为空时&#xff0c;出队列只能将s1的后N-1项挪到s2后剩下的就为出栈的项&#xff0c;但如果s2不为空那就说明此时s2栈中的元素就已经是按照队列的顺序排好了&#xff0c;…

HTML5学习系列之主结构

HTML5学习系列之主结构 前言HTML5主结构定义页眉定义导航定义主要区域定义文章块定义区块定义附栏定义页脚 具体使用总结 前言 学习记录 HTML5主结构 定义页眉 head表示页眉&#xff0c;用来表示标题栏&#xff0c;引导和导航作用的结构元素。 <header role"banner…

解析JSON字符串:属性值为null的时候不被序列化

如果希望属性值为null及不序列化&#xff0c;只序列化不为null的值。 1、测试代码 配置代码&#xff1a; mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 或者通过注解JsonInclude(JsonInclude.Include.NON_NULL) //常见问题2&#xff1a;属性为null&a…