Bootstrap

Java基于JWT实现Token登录验证

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、JWT是什么?

1、先看利用token登录的实现流程:

1、客户端使用账号密码登录
2、服务端接受到请求验证账号和密码
3、服务端验证请求后,会生成一个token,并把token返回给客户端
4、客户端收到token,并存储起来,在每次请求的时候带上这个token,可以在head中携带
5、服务端接受到请求后,验证token,如果验证成功,则返回请求数据

2、JWT介绍:

JWT代表JSON Web Token,是一种用于在网络上安全地传输信息的开放标准(RFC 7519)。它以紧凑和自包含的方式表示信息,通常用于在用户和服务之间传递身份验证和授权信息。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

3、JWT的验证流程:

1、解析Token: 首先,将收到的JWT分割成头部、载荷和签名三个部分。这个过程通常会使用编程语言中相应的库或函数来完成。

2、验证头部和签名: 获取JWT中的头部和载荷信息后,根据头部中指定的加密算法和使用的密钥(通常是一个密钥或公钥)对头部和载荷进行签名验证。验证过程会使用相同的算法和密钥来生成签名,并将生成的签名与JWT中的签名进行比较,以确认JWT的完整性和真实性。

3、检查有效期和其他声明: JWT中的载荷部分可能包含了诸如令牌的过期时间(Expiration Time)等声明。在验证过程中,需要检查这些声明以确保令牌在有效期内,并可能进行其他额外的声明验证,如角色、权限等。

4、可选的其他验证步骤: 根据需求,还可以进行其他的验证步骤,比如验证令牌是否在黑名单中,是否与存储的用户信息匹配等。

JWT的验证流程主要依赖于对JWT规范和实现的理解,以及使用的编程语言或框架提供的相应功能。在验证JWT时,确保密钥的安全性是非常重要的,因为泄露密钥可能会导致令牌被篡改或伪造。

二、使用步骤

1.数据库

在这里插入图片描述

2.项目结构

在这里插入图片描述

3.相关依赖

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot</artifactId>
            <version>2.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.11.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
    </dependencies>

4.代码

User:

package com.explord.bean;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/15:55
 * @Description:
 */
@Data
@TableName("user")
public class User {

    private int userId;

    @TableField(value = "user_name")
    private String userName;

    @TableField(value = "user_password")
    private String userPassword;


}

UserDao:

package com.explord.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.explord.bean.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/16:07
 * @Description:
 */
@Mapper
public interface UserDao extends BaseMapper<User> {
}

UserService:

package com.explord.service;

import com.explord.bean.User;

import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/16:03
 * @Description:
 */
public interface UserService {

    User loing(String userName, String userPassword);

    List<User> getUserList();
}

UserServiceImpl:

package com.explord.service.impl;

import com.explord.bean.User;
import com.explord.dao.UserDao;
import com.explord.service.UserService;
import org.omg.CORBA.OBJ_ADAPTER;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/16:04
 * @Description:
 */
@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserDao userDao;

    @Override
    public User loing(String userName, String userPassword) {
        Map<String, Object> map = new HashMap<>();
        map.put("user_name", userName);
        map.put("user_password", userPassword);
        List<User> users = userDao.selectByMap(map);
        return users.get(0);
    }

    @Override
    public List<User> getUserList() {
        return userDao.selectList(null);
    }
}

TokenUtil:

package com.explord.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.print.DocFlavor;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/10:33
 * @Description:
 */
public class TokenUtil {

    /**
     * 设置延期的时间
     */
    private static final long EXPIR_DATE = 360000;

    /**
     * 设置token的秘钥
     */
    private static final String TOKEN_SECRET = "my_secret_key";


    /**
     * 获取token
     * @param userName
     * @param passWord
     * @return
     */
    public static String getTokenSecret(String userName, String passWord){
        String token = "";
        try {
            // 过期时间
            Date date = new Date(System.currentTimeMillis() + EXPIR_DATE);
            // 设置头部信息
            Map<String, Object> head = new HashMap<>();
            head.put("typ", "JWT");
            head.put("alg", "HS256");

            // 设置秘钥
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);

            // 生成签名
            token = JWT.create()
                    .withHeader(head)
                    .withClaim("userName", userName)
                    .withClaim("passWord", passWord)
                    .withExpiresAt(date)
                    .sign(algorithm);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        return token;
    }

    /**
     * 验证token
     * @param token
     * @return
     */
    public static DecodedJWT verify(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            return jwt;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

}

CodeMessage:

package com.explord.util;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/22:12
 * @Description:
 */
public class CodeMessage {

    private int retCode;
    private String message;
    // 按照模块定义CodeMessage
    // 通用异常
    public static CodeMessage SUCCESS = new CodeMessage(0,"success");
    // 业务异常
    public static CodeMessage USER_OR_PASS_ERROR = new CodeMessage(500102,"账号或者密码错误,请重试!");


    private CodeMessage(int retCode, String message) {
        this.retCode = retCode;
        this.message = message;
    }

    public int getRetCode() {
        return retCode;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }

}

Result:

package com.explord.util;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/22:11
 * @Description:
 */
public class Result<T> {

    private String message;
    private int retCode;
    private T data;

    private Result(T data) {
        this.retCode = 200;
        this.message = "成功";
        this.data = data;
    }

    private Result(CodeMessage cm) {
        if(cm == null){
            return;
        }
        this.retCode = cm.getRetCode();
        this.message = cm.getMessage();
    }

    /**
     * 成功时候的调用
     * @return
     */
    public static <T> Result<T> success(T data){
        return new Result<T>(data);
    }

    /**
     * 成功,不需要传入参数
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> Result<T> success(){
        return (Result<T>) success("");
    }
    /**
     * 失败时候的调用
     * @return
     */
    public static <T> Result<T> error(CodeMessage cm){
        return new Result<T>(cm);
    }
    /**
     * 失败时候的调用,扩展消息参数
     * @param cm
     * @param msg
     * @return
     */
    public static <T> Result<T> error(CodeMessage cm,String msg){
        cm.setMessage(cm.getMessage()+"--"+msg);
        return new Result<T>(cm);
    }
    public T getData() {
        return data;
    }
    public String getMessage() {
        return message;
    }
    public int getRetCode() {
        return retCode;
    }

}

LoginController:

package com.explord.controller;

import com.alibaba.fastjson.JSONObject;
import com.explord.annotation.UserLoginToken;
import com.explord.bean.User;
import com.explord.service.UserService;
import com.explord.util.CodeMessage;
import com.explord.util.Result;
import com.explord.util.TokenUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/15:54
 * @Description:
 */
@Controller
@RequestMapping("/login")
public class LoginController {

    @Resource
    private UserService userService;

    @RequestMapping("/toLogin")
    @ResponseBody
    public Result<Object> toLogin(String userName, String userPassword, HttpServletResponse response){
        User user = userService.loing(userName, userPassword);
        JSONObject jsonObject = new JSONObject();

        if(user == null){
           Result.error(CodeMessage.USER_OR_PASS_ERROR);
        }

        String token = TokenUtil.getTokenSecret(userName, userPassword);
        jsonObject.put("token", token);
        response.setHeader("token", token);
        Cookie cookie = new Cookie("token", token);
        cookie.setPath("/");
        response.addCookie(cookie);

        return Result.success(jsonObject);
    }

    @UserLoginToken
    @RequestMapping("/list")
    @ResponseBody
    public Result<Object> list(){
        List<User> userList = userService.getUserList();
        return  Result.success(userList);
    }
}

UserLoginToken(自定义注解)

package com.explord.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/16:40
 * @Description: 登录以后才能进行的token
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {

    boolean required() default true;
}

LoginrequiredInterceptor

package com.explord.controller.interceptor;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.explord.annotation.UserLoginToken;
import com.explord.util.TokenUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/16:43
 * @Description:
 */
@Component
public class LoginrequiredInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getHeader("token");

        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();

            if (method.isAnnotationPresent(UserLoginToken.class)) {
                UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);

                if(userLoginToken.required()){
                    if(token == null){
                        throw new RuntimeException("token为空,请重新登录");
                    }else{
                        //查询用户是否存在

                        // 验证token
                        DecodedJWT verify = TokenUtil.verify(token);
                        return  true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
}

WebMvcConfig

package com.explord.config;

import com.explord.controller.interceptor.LoginrequiredInterceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: hang
 * @Date: 2023/11/17/17:07
 * @Description:
 */
@Component
public class WebMvcConfig implements WebMvcConfigurer {


    @Resource
    private LoginrequiredInterceptor loginrequiredInterceptor;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginrequiredInterceptor)
                .excludePathPatterns("/login/toLogin")
                .addPathPatterns("/**");
    }
}


三、测试结果

1、登录验证:

在这里插入图片描述

2、查询用户信息

方法上加了@UserLoginToken,因此需要token才能进行验证
在这里插入图片描述

在这里插入图片描述

3、不加token进行查询

在这里插入图片描述
在这里插入图片描述

;