原文地址:后端系统开发之——创建登录接口 - Pleasure的博客
下面是正文内容:
前言
在开发系统的时候,个人喜欢从后端向前端来进行推进。
在注册接口开发完成之后,下面就需要来开发登录的接口了。
正文
前置知识
不是初学者的可以直接跳过了。我是,所以我需要记录,为方便后续的复习再理解。
JWT令牌——JSON Web Token。通常用于身份验证和信息交换,它可以在用户和服务器之间传递声明(claims),以便服务器可以对用户进行授权并提供相应的服务。通常由头部载荷密钥三部分组成,由“.”分隔连接。
头部:记录令牌类型,签名算法{"alg":"HS256","type":"JWT"}
载荷:携带业务数据 Base64编码方式
密钥:数字签名,防止token被篡改{header.payload,secret}
Spring框架——的主要组成部分
IoC容器(Inversion of Control): 通过依赖注入(Dependency Injection,DI)实现了控制反转,即将对象的创建和管理交给了容器。开发人员只需描述组件之间的依赖关系,而无需直接管理对象的创建和销毁。
AOP支持(Aspect-Oriented Programming): Spring框架提供了对面向切面编程的支持,可以通过AOP实现横切关注点的模块化,例如日志记录、性能监控、事务管理等。
Spring框架鼓励面向接口编程,提供了IOC容器和AOP等特性来支持接口的依赖注入和动态代理。
P.S.:这一块本来应该放在最开始的时候讲的,但是那时候忘了……
全局异常处理器:在应用程序的一个地方集中处理所有未捕获的异常,而不必在每个可能引发异常的地方都进行异常处理。用于捕获和处理未预期的异常,以便进行适当的处理,例如记录错误信息、返回友好的错误页面或提供其他形式的错误处理。
文件作用
其中有一些文件夹在上一篇文章中已经解释过了,在这次登录接口的创建中就用到了拦截器以及全局异常处理器。
interceptor文件夹用于存放拦截器相关的文件,定义拦截器接口。
config文件夹用于存放配置文件,用于定义Bean、配置拦截器等。
exception文件夹用于存放(全局异常处理器)自定义异常类的文件。(在上一章参数校验是没有解释)
test文件夹是一个用于测试的独立的开发环境,用于测试部分代码的可行性。
创建接口
这里就直接展示代码带过了,重点是登录验证即拦截器的部署。
现更新UserController.java文件内容为下:
package org.example.controller;
import jakarta.validation.constraints.Pattern;
import org.example.pojo.Result;
import org.example.pojo.User;
import org.example.service.UserService;
import org.example.utils.JwtUtil;
import org.example.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/user")
@Validated
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 u = userService.findByUserName(username);
if (u == null) {
//没有占用
//注册
userService.register(username, password);
return Result.success();
} else {
//占用
return Result.error("用户名已被占用");
}
//注册
}
@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("用户名不存在");
}
//判断密码是否正确,传入参数加密后与数据库中的密文进行比对
if (Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
//登录成功
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("密码错误");
}
}
登录认证
其实接口的创建很简单,大体操作上和注册接口的差不多,只是在主逻辑上有一定的区别。
但是作为登录接口,最主要的作用就是,发放登录令牌,用于系统内部界面的登录状态认证。
给用户发放登录令牌,可以减少查询数据库的次数降低载荷,防止用户信息篡改等等。
有点类似于之前php的开源商城系统中登录成功后设置的session或者是cookie所起到的作用。
由控制层进行筛选及管理,也就是controller文件夹下的内容。
在用JWT进行校验时失效的几种情况,篡改头部数据验证失效,密钥更改验证失效,token过期验证失效
所以登录接口的认证分为生成令牌和验证令牌两部分。
生成令牌由登录接口文件来实现,而验证令牌由系统内部文件来实现,这里就以ArticleController.java文件为例来实现。
由于后续需要用到验证令牌的接口会非常多,所以这里就需要用到“拦截器”,而不要直接去硬编码(增加代码篇幅)。
可以跳过的部分
如果要进行硬编码,就需要在每个系统的内部文件里,放置下面注释部分的代码,以进行令牌的校验。
package org.example.controller;
import jakarta.servlet.http.HttpServletResponse;
import org.example.pojo.Result;
import org.example.utils.JwtUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/article")
public class ArticleController {
@GetMapping("/list")
public Result<String> list(/*@RequestHeader(name = "Authorization") String token, HttpServletResponse response*/){
//验证token
/*try {
Map<String, Object> claims = JwtUtil.parseToken(token);
return Result.success("文章数据");
} catch (Exception e) {
response.setStatus(401);
return Result.error("用户未登录");
}*/
return Result.success("文章数据");
}
}
在测试环境下编写Jwt令牌生成文件(要用直接用下面的JwtUtil.java)
这里就不另行复制JwtTest.java的代码了,从下面的图片中可以看到令牌生成成功,也验证成功了。
代码展示
在pom.xml文件中引入下面的新依赖代码
<!--java-jwt坐标-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<!--单元测试坐标-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
用来生成JWT的代码,JwtUtil.java的内容
package org.example.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
private static final String KEY = "example";
//接收业务数据,生成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();
}
}
拦截器的文件内容,接口文档要求不放行时返回401状态错误,不喜欢可以根据实际状况进行更改。
LoginInterceptor.java的内容
package org.example.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.pojo.Result;
import org.example.utils.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
@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;
}
}
}
WebConfig.java的内容
package org.example.config;
import org.example.interceptors.LoginInterceptor;
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;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录接口和注册接口不拦截
registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
}
}
可以看到在数据包的Header部分不添加生成的Authorization JWT登录令牌时,就会返回401登录错误。
尾声
现在系统的开发已经完成了注册和登录两个功能,接下来就是获取用户信息的部分了。
大家点个关注,方便后续的跟进。