原文地址:后端系统开发之——创建登录接口 - 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.:这一块本来应该放在最开始的时候讲的,但是那时候忘了……

全局异常处理器:在应用程序的一个地方集中处理所有未捕获的异常,而不必在每个可能引发异常的地方都进行异常处理。用于捕获和处理未预期的异常,以便进行适当的处理,例如记录错误信息、返回友好的错误页面或提供其他形式的错误处理。

文件作用

后端系统开发之——创建登录接口-LMLPHP

其中有一些文件夹在上一篇文章中已经解释过了,在这次登录接口的创建中就用到了拦截器以及全局异常处理器。

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的代码了,从下面的图片中可以看到令牌生成成功,也验证成功了。

后端系统开发之——创建登录接口-LMLPHP

代码展示

在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登录错误。

后端系统开发之——创建登录接口-LMLPHP

尾声

现在系统的开发已经完成了注册和登录两个功能,接下来就是获取用户信息的部分了。

大家点个关注,方便后续的跟进。

03-22 14:54