02 SpringBoot实战 -微头条之用户模块功能(第一次登录根据账号密码生成token+后续登录根据token获取用户信息+)

发布时间:2024年01月22日

1 用户模块

1.1 jwt和token

1.1.1 token介绍

令牌(Token):在计算机领域,令牌是一种代表某种访问权限或身份认证信息的令牌。它可以是一串随机生成的字符或数字,用于验证用户的身份或授权用户对特定资源的访问。普通的令牌可能以各种形式出现,如访问令牌、身份令牌、刷新令牌等。

简单理解 : 每个用户生成的唯一字符串标识 , 可以进行用户识别和校验

优势: token验证标识无法直接识别用户的信息,盗取token后也无法`登录`程序! 相对安全!

1.1.2 jwt介绍

Token是一项规范和标准(接口)

JWT(JSON Web Token)是具体可以生成,校验,解析等动作Token的技术(实现类)

1.1.3 jwt工作流程

  • 用户提供其凭据(通常是用户名和密码)进行身份验证。
  • 服务器对这些凭据进行验证,并在验证成功后创建一个JWT。
  • 服务器将JWT发送给客户端,并客户端在后续的请求中将JWT附加在请求头或参数中。
  • 服务器接收到请求后,验证JWT的签名和有效性,并根据JWT中的声明进行身份验证和授权操作
    - 在这里插入图片描述

1.1.4 jwt数据组成和包含信息

JWT由三部分组成: header(头部).payload(载荷).signature(签名)

我们需要理解的是, jwt可以携带很多信息! 一般情况,需要加入:有效时间,签名秘钥,其他用户标识信息!

有效时间的作用 : 保证token的时效性,过期可以重新登录获取!

签名秘钥的作用 : 防止其他人随意解析和校验token数据!

用户信息的作用 : 系统解析的时候,分辨Token对应的具体用户!

1.1.5 jwt使用和测试

  1. 导入依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>
  1. 编写配置application.yaml
#jwt配置
jwt:
  token:
    tokenExpiration: 120 #有效时间,单位分钟
    tokenSignKey: headline123456  #当前程序签名秘钥 自定义
  1. 封装jwt技术工具类
    分析 : 从逻辑上讲,该工具类必须有如下功能
    a. 根据传入的用户id生成token并返回 ,以便用户免密登录
    b. 根据传入的token判断用户id , 以便知道登录的是哪个用户
    c. 判断传入的token是否还在有效期
package com.sunsplanter.utils;

import com.alibaba.druid.util.StringUtils;
import io.jsonwebtoken.*;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Date;

@Data
@Component
//读取配置文件中所有前缀为jwt.token的属性,只要最后后缀一样就自动注入
@ConfigurationProperties(prefix = "jwt.token")
public class JwtHelper {

    private  long tokenExpiration; //有效时间,单位毫秒 1000毫秒 == 1秒
    private  String tokenSignKey;  //当前程序签名秘钥

    //生成token字符串
    public  String createToken(Long userId) {
        System.out.println("tokenExpiration = " + tokenExpiration);
        System.out.println("tokenSignKey = " + tokenSignKey);
        String token = Jwts.builder()

                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration*1000*60)) //单位分钟
                .claim("userId", userId)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    //从token字符串获取userid
    public  Long getUserId(String token) {
        if(StringUtils.isEmpty(token)) return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }

    //判断token是否有效
    public  boolean isExpiration(String token){
        try {
            boolean isExpire = Jwts.parser()
                    .setSigningKey(tokenSignKey)
                    .parseClaimsJws(token)
                    .getBody()
                    .getExpiration().before(new Date());
            //没有过期,有效,返回false
            return isExpire;
        }catch(Exception e) {
            //过期出现异常,返回true
            return true;
        }
    }
}
4. 使用和测试
package com.sunsplanter.test;

import com.sunsplanter.utils.JwtHelper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class SBTest {

    @Autowired
    private JwtHelper jwtHelper;

    @Test
    public void test(){
        //生成 传入用户标识
        String token = jwtHelper.createToken(1L);
        System.out.println("token = " + token);

        //解析用户标识
        int userId = jwtHelper.getUserId(token).intValue();
        System.out.println("userId = " + userId);

        //校验是否到期! false 未到期 true到期
        boolean expiration = jwtHelper.isExpiration(token);
        System.out.println("expiration = " + expiration);
    }

}

1.2 登录

  1. 需求描述

    用户在客户端输入用户名密码并向后端提交,后端根据用户名和密码判断登录是否成功,用户有误或者密码有误响应不同的提示信息!

  2. 接口描述

    url地址: user/login

    请求方式:POST

    请求参数:

{
    "username":"zhangsan", //用户名
    "userPwd":"123456"     //明文密码
}

响应数据:

成功

{
   "code":"200",         // 成功状态码 
   "message":"success"   // 成功状态描述
   "data":{
    "token":"... ..." // 根据用户id的token
  }
}

失败

{
   "code":"501",
   "message":"用户名有误"
   "data":{}
}
{
   "code":"503",
   "message":"密码有误"
   "data":{}
}
  1. 实现代码
    1. controller
@RestController
@RequestMapping("user")
//解决跨域访问问题
@CrossOrigin
public class UserController {


    @Autowired
    private UserService userService;

    @PostMapping("login")
    public Result login(@RequestBody User user){
        Result result = userService.login(user);
        System.out.println("result = " + result);
        return result;
    }

}
  1. service
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{
    @Autowired
    private JwtHelper jwtHelper;
    @Autowired
    private  UserMapper userMapper;

    /**
     * 登录业务实现
     * @param user
     * @return result封装'
     * 
     * 大概流程:
     *    1. 账号进行数据库查询 返回用户对象
     *    2. 对比用户密码(md5加密)
     *    3. 成功,根据userId生成token -> map key=token value=token值 - result封装
     *    4. 失败,判断账号还是密码错误,封装对应的枚举错误即可
     * 
     * 登录需求
     * 地址: /user/login
     * 方式: post
     * 参数:
     *    {
     *     "username":"zhangsan", //用户名
     *     "userPwd":"123456"     //明文密码
     *    }
     * 返回:
     *   {
     *    "code":"200",         // 成功状态码
     *    "message":"success"   // 成功状态描述
     *    "data":{
     *         "token":"... ..." // 用户id的token
     *     }
     *  }
     */
    @Override
    public Result login(User user) {

        //根据账号查询
        //创建一个条件构造器对象,并将等于条件封装入内(数据库中查找username值与传入user.username值一样的记录)
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,user.getUsername());
        User loginUser = userMapper.selectOne(queryWrapper);

        //账号判断
        if (loginUser == null) {
            //账号错误
            return Result.build(null, ResultCodeEnum.USERNAME_ERROR);
        }

        //判断密码
        if (!StringUtils.isEmpty(user.getUserPwd())
                && loginUser.getUserPwd().equals(MD5Util.encrypt(user.getUserPwd())))
        {
            //账号密码正确
            //根据用户唯一标识生成token
            String token = jwtHelper.createToken(Long.valueOf(loginUser.getUid()));

            Map data = new HashMap();
            data.put("token",token);

            return Result.ok(data);
        }

        //密码错误
        return Result.build(null,ResultCodeEnum.PASSWORD_ERROR);
    }
}

在idea中模拟发送Json数据,可以得到响应 , 返回一个token.
在这里插入图片描述
在这里插入图片描述

1.3 根据token获取用户信息

  1. 需求 : 第一次登录以后 , 客户一段时间内都不用再输入账号密码就能登录了 . 实现的原理就是 , 客户端发送请求头中包含token , 后端可以根据token获取登录用户的详细信息. 本次要求后端获取用户信息(密码除外)后返回给前端展示.

  2. 接口描述

    url地址:user/getUserInfo

    请求方式:GET

    请求头:token: token内容

    响应数据:

    成功

{
    "code": 200,
    "message": "success",
    "data": {
        "loginUser": {
            "uid": 1,
            "username": "zhangsan",
            "userPwd": "",
            "nickName": "张三"
        }
    }
}
失败
{
    "code": 504,
    "message": "notLogin",
    "data": null
}
  1. 代码编写
    userController 新增:
    @GetMapping("getUserInfo")
    //RequstHeader注解提取请求头
    public Result userInfo(@RequestHeader String token){
        Result result = userService.getUserInfo(token);
        return result;
    }

userService新增:

	    Result getUserInfo(String token);

userServiceImpl新增:

    /**
     * 大概流程:
     *    1.获取token,解析token对应的userId
     *    2.根据userId,查询用户数据
     *    3.将用户数据的密码置空,并且把用户数据封装到结果中key = loginUser
     *    4.失败返回504 (本次先写到当前业务,后期提取到拦截器和全局异常处理器)
     */
    @Override
    public Result getUserInfo(String token) {
        //判断是否过期,
        boolean expiration = jwtHelper.isExpiration(token);

        //若失效,返回响应码
        if (expiration) {
            return Result.build(null,ResultCodeEnum.NOTLOGIN);
        } else {
            int userId = jwtHelper.getUserId(token).intValue();
            User user = userMapper.selectById(userId);
            user.setUserPwd("");

            Map data = new HashMap();
            data.put("loginUser",user);
            return Result.ok(data);
        }
    }

在这里插入图片描述

1.4 1. 用户注册时检查用户名是否已被占用

  1. 需求描述 : 用户在注册时输入用户名时,立刻将用户名发送给后端,后端根据用户名查询用户名是否可用(是否被占用)并做出响应

  2. 接口描述

    url地址:user/checkUserName

    请求方式:POST

    请求参数:param形式
    username=zhangsan

响应数据:

成功

{
   "code":"200",
   "message":"success"
   "data":{}
}
失败
{
    "code":"505",
   "message":"用户名占用"
   "data":{}
}
  1. 代码编写
    userController 新增:

userService新增:

	  

userServiceImpl新增:

文章来源:https://blog.csdn.net/m0_46671240/article/details/135752562
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。