SpringBoot实现JWT认证

SpringBoot实现JWT认证

本文会从Token、JWT、JWT的实现、JWTUtil封装到SpringBoot中使用JWT,如果有一定的基础,可以跳过前面的内容~

Token

简介

Token 是一个临时、唯一、保证不重复的令牌,例如智能门锁,它可以生成一个临时密码,具有一定时间内的有效期。

实现思路

UUID具有上述的特性,所以我们可以使用UUID作为token,生产UUID后放入Redis,设置Redis的过期时间。

Token的SessionID

token和SESSIONID非常的相似,但是SESSIONID在分布式项目中不能共享,虽然SESSION可以通过Redis等技术实现共享,但是使用这类技术会降低项目的性能和可用性。所以现在普通使用Token代替Session使用。

简单的Token实现思路

实现思路:

  1. 验证用户的账号密码
  2. 如果正确,生成UUID作为Key
  3. 将此Key作为Key,将用户信息作为Value,存入Redis
  4. 最后返回Token给客户端,客户端将Cookie保存到Cookie中

用户在每次请求时,都会携带此Token,后端在拦截器中校验Token是否存在,如果存在找到对应的用户信息,判断其有哪些权限。

Token优点:

  1. 可以通过Header、Body提交,实现跨域操作
  2. 可以隐藏参数的真实性,实现参数的脱敏
  3. 临时、唯一

存在问题:

  1. 使用Token,必须依赖Redis和Cookie
  2. 需要频繁操作Redis

JWT

简介

JWT,全称Json Web Token,是目前最流行的跨域认证解决方案。它的实现思想和上面的token是基本一致的,是一种更加成熟和完善的解决方案。

原理

JWT的原理就是,当服务器认证账号密码通过后,生成一个JSON对象,返回给用户,保存在Cookie中。当用户下一次访问的时候自动携带这个JSON对象,服务器可以根据这个对象判断用户的身份。为了防止用户篡改数据信息,服务器生成这个JSON的时候,会进行一些加密操作。此时服务器中就不需要保存session数据。

JWT的数据结构

JWT中的数据分为三部分,每部分都是一串很长的字符串,中间用.间隔

  • header: 头部,标记加密算法
  • Payload: 负载,存放具体数据
  • Signature:签名,Payload采用MD5加密后的签名值

完整的格式为:header.Payload.Signature

Header部分是由一个JSON对象组成,它描述JWT的元数据,通常是下面的样子:

{

	'alg' : "HS256",
	"typ" : "JWT"

}

alg表示签名的算法,默认为HMAC SHA256(可以写成 HS256)

typ属性表示令牌的类型,JWT令牌统一写为JWT

生成JWT后,此部分会进行BASE64编码,最终被解析为:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

jwt中的Header部分的内容默认是没有加密的,只是进行了Base64处理。可以直接使用Base64反加密获取原文。

Payload

Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段:

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,还可以支持自定义字段,下面就是一个例子:

{
  "name": "rayfoo",
  "phone": 18338862369
}

生产JWT后的:

eyJuYW1lIjoicmF5Zm9vIiwicGhvbmUiOjE4MzM4ODYyMzY5fQ

注意,这部分的内容默认也是没有加密的,只是进行了Base64编码。可以直接使用Base64反加密获取原文。但是我们可以对其进行一些混淆操作。

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

这一段并不是使用base64加密,而是使用header中提供的加密方式进行的加密.

可以浅显的理解为将Payload中的数据按照header,payload+密钥(secret)作为一个整体进行MD5(也可能是任意类型的)加密。在下面这段代码中,密钥就是:rayfoo。

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  rayfoo
)

jwt生成的signature

AIwKf4x_nYr1N_cmw_VQ5t_nuaX5b-gTN8RgHtkTO4w

完整的token

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。这就是完整的JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicmF5Zm9vIiwicGhvbmUiOjE4MzM4ODYyMzY5fQ.AIwKf4x_nYr1N_cmw_VQ5t_nuaX5b-gTN8RgHtkTO4w

可以在https://jwt.io/#encoded-jwt进行测试

Base64URL编码

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+/=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-/替换成_ 。这就是 Base64URL 算法。

JWT的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

JWT 的几个特点

优点:

(1)JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次,不容易被客户端修改。

(2)JWT 不加密的情况下,不能将秘密数据写入 JWT。

(3)JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。效率也比token高。

缺点:

(1)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(2)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(3)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

(4)如果jwt中payload的数据过多,会占用服务器的带宽资源。

如何手写一个JWT

了解了上面的一些概念后,我们可以自己动手实现一个jwt

  1. 创建两个JSONObject对象,分别作为Header和Payload
  2. 初始化Header,添加相应内容,进行base64编码
  3. 初始化Payload,添加相应内容,进行base64编码+混淆
  4. 对Payload进行md5加盐、加密

对三部分内容进行拼接,使用.间隔

建议在payload中增加一个时间戳,用于指定过期时间。

在Java中使用JWT

jwt提供了不止一种的实现

  • Auth0实现 的 java-jwt

  • Brian Campbell实现的 jose4j

  • connect2id实现的 nimbus-jose-jwt

  • Les Haziewood实现的 jjwt

  • Inversoft实现的prime-jwt

  • Vertx实现的vertx-auth-jwt.

几乎所有库都要求JAVA版本1.7或更高版本, 1.6或以下的版本需要二次开发(或不支持)

从易用性, 扩展性, 完整性等来看, 使用首先推荐 jose4j, 其次是 Nimbus-jose-jwt.

关于这些类库的评测:http://andaily.com/blog/?p=956

封装JWT工具类&payload加密

引入依赖

下面的代码都是基于auth0 提供的 java-jwt实现的

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

封装工具类

由于JWT中的payload是不安全的,没有进行加密,所以在工具类中进行了加密操作。

这里的加密操作只是一种加密思路,你也可以使用自己的任意加密方式来让payload中的内容更加安全。

package cn.rayfoo.common.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * <p>JSON WEB TOKEN 工具类</p>
 * @date 2020/8/11 9:19
 */
public class JWTUtil {

    /**
     * 签名 此签名为 rayfoo 的16位 大写 MD5
     */
    private static final String SIGN_KEY = "5A1332068BA9FD17";

    /**
     * 默认的过期时间,30分钟
     */
    private static final Integer DEFAULT_EXPIRES = 60 * 30;

    /**
     * token默认的长度
     */
    private static final Integer DEFAULT_TOKEN_SIZE = 3;


    /**
     * 生成令牌
     *
     * @param map     数据正文
     * @param expires 过期时间,单位(秒)
     */
    public static String getToken(Map<String, String> map, Integer expires) throws Exception {

        //创建日历
        Calendar instance = Calendar.getInstance();
        //设置过期时间
        instance.add(Calendar.SECOND, expires);

        //创建jwt builder对象
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });

        //指定过期时间
        String token = builder.withExpiresAt(instance.getTime())
                //设置加密方式
                .sign(Algorithm.HMAC256(SIGN_KEY));
        //返回tokean
        return confoundPayload(token);
    }

    /**
     * 解析token
     *
     * @param token 输入混淆payload后的token
     */
    public static DecodedJWT verify(String token) throws Exception {
        //如果token无效
        if (token == null || "".equals(token)) {
            throw new JWTDecodeException("无效的token!");
        }
        //解析token
        String dToken = deConfoundPayload(token);
        //创建返回结果
        return JWT.require(Algorithm.HMAC256(SIGN_KEY)).build().verify(dToken);

    }

    /**
     * 重载getToken 此方法为获取默认30分钟有效期的token
     *
     * @param map 数据正文
     */
    public static String getToken(Map<String, String> map) throws Exception {
        return getToken(map, DEFAULT_EXPIRES);
    }


    /**
     * 对一个base64编码进行混淆  此处还可以进行replace混淆,考虑到效率问题,这里就不做啦~
     * 对于加密的思路还有位移、字符替换等~
     *
     * @param token 混淆payload前的token
     */
    private static String confoundPayload(String token) throws Exception {
        //分割token
        String[] split = token.split("\.");
        //如果token不符合规范
        if (split.length != DEFAULT_TOKEN_SIZE) {
            throw new JWTDecodeException("签名不正确");
        }
        //取出payload
        String payload = split[1];
        //获取长度
        int length = payload.length() / 2;
        //指定截取点
        int index = payload.length() % 2 != 0 ? length + 1 : length;
        //混淆处理后的token
        return split[0] + "." + reversePayload(payload, index) + "." + split[2];
    }

    /**
     * 对一个混淆后的base编码进行解析
     *
     * @param token 混淆后的token
     */
    private static String deConfoundPayload(String token) throws Exception {
        //分割token
        String[] split = token.split("\.");
        //如果token不符合规范
        if (split.length != DEFAULT_TOKEN_SIZE) {
            throw new JWTDecodeException("签名不正确");
        }
        //取出payload
        String payload = split[1];
        //返回解析后的token
        return split[0] + "." + reversePayload(payload, payload.length() / 2) + "." + split[2];
    }

    /**
     * 将md5编码位移
     *
     * @param payload payload编码
     * @param index   位移处
     */
    private static String reversePayload(String payload, Integer index) {
        return payload.substring(index) + payload.substring(0, index);
    }


}

此时,我们就可以使用此工具类颁发token、解析token了~

package cn.rayfoo;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import org.apache.commons.codec.binary.StringUtils;

import java.util.*;


/**
 * @author rayfoo@qq.com
 * @version 1.0
 * <p></p>
 * @date 2020/8/10 16:18
 */
public class JwtTest {

    //密钥
    private static final String SIGN_KEY = "rayfoo";

    public static void main(String[] args) throws Exception{

        //创建map
        Map<String, String> map = new HashMap<>();
        map.put("username", "rayfoo");
        //颁发token
        String token = JWTUtil.getToken(map);
        System.out.println(token);

        //解析token
        DecodedJWT verify = JWTUtil.verify(token);
        System.out.println(verify.getClaim("username").asString());

    }

}

此时,使用混淆后的token解析,发现无法解析到payload:

常见异常的处理

  • JWTDecodeException:header、payload被修改会出现的异常
  • SignatureVerificationException:签名不匹配异常
  • TokenExpiredException:令牌过期异常
  • AlgorithmMismatchException:算法不匹配异常

建议使用全局异常处理进行细粒度异常处理

在SpringBoot中使用JWT

使用JWT之前

先基于SpringBoot+MyBatis实现一个简单的查询操作,完整的代码稍后会上传到Github,这里只进行关键部分的介绍。

传统的密码校验:

    @Override
    public User login(User user) throws Exception {
        //这里假设user、user内的username、password数据都是正确的
        User example = User.builder().username(user.getUsername()).password(user.getPassword()).build();
        //查询用户是否存在
        List<User> reslut = userMapper.select(example);
        //如果没找到代表用户名或者密码错误
        if (ObjectUtils.isEmpty(reslut)) {
            throw new Exception("用户名或密码错误!");
        }
        return reslut.get(0);
    }

上面时service层代码,如果执行没有报错说明拿到了正确的查询结果,此时在Controller中可以将用户的登录信息保存到Session或者Redis中,用于校验。

    @PostMapping("/login")
    public Map<String, Object> login(@RequestBody User user) {

        //初始化返回值
        Map<String, Object> map = new HashMap<>(2);
        try {
            //用户登录校验
            User loginUser = userService.login(user);
            //没有抛出异常表示正常
            map.put("code", 200);
            map.put("msg", "认证成功!");
            //使用session或者redis记录。。。
        } catch (Exception exception) {
            //如果出现异常记录错误信息
            map.put("code", 500);
            map.put("msg", exception.getMessage());
        }
        //返回结果
        return map;
    }

使用JWT

有了JWT以后,我们可以使用token来代替Session/Redis

 @PostMapping("/login")
    public Map<String, Object> login(@RequestBody User user) {

        //初始化返回值
        Map<String, Object> map = new HashMap<>(3);
        try {
            //用户登录校验
            User loginUser = userService.login(user);

            //没有抛出异常表示正常
            map.put("code", 200);
            map.put("msg", "认证成功!");

            //声明payload
            Map<String, String> payload = new HashMap<>(2);

            //初始化payload
            payload.put("id", loginUser.getId().toString());
            payload.put("username", loginUser.getUsername());

            //获取令牌
            String token = JWTUtil.getToken(payload,20);

            //在响应结果中添加token
            map.put("token", token);


        } catch (Exception exception) {
            //如果出现异常记录错误信息
            map.put("code", 500);
            map.put("msg", exception.getMessage());
        }
        //返回结果
        return map;
    }

JWT包含有权限的接口

这里的代码没有进行优化,只是用最简单直白的方式介绍了JWT对接口的保护

@GetMapping("/list")
    public Map<String, Object> userList(String token) {

        //初始化返回值
        Map<String, Object> map = new HashMap<>(3);

        List<User> result = null;

        String errorMsg = "";

        //校验token
        log.info("当前token为:" + token);

        try {
            //验证令牌
            DecodedJWT verify = JWTUtil.verify(token);
            //如果令牌校验成功
            result = userService.userList();
            //返回查询结果
            map.put("code", 200);
            map.put("msg", "查询成功");
            map.put("result", result);
            return map;
        } catch (JWTDecodeException e) {
            //其实是用户修改了header或者payload,但是不用告诉用户错误的细节
            e.printStackTrace();
            errorMsg = "token无效!";
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            //其实是修改了签名,但是不用告诉用户错误的细节
            errorMsg = "token无效!";
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            errorMsg = "token已过期!";
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            //其实是修改了算法,但是不用告诉用户错误的细节
            errorMsg = "token无效!";
        } catch (Exception e) {
            e.printStackTrace();
            errorMsg = "token无效!";
        }
        //返回错误信息
        map.put("code", 500);
        map.put("msg", "token校验失败");
        map.put("result", errorMsg);
        return map;
    }

使用拦截器优化Token校验

在前面的案例中,如果每个接口都进行拦截器校验,冗余的代码会非常的多,程序的可读性也非常低。

在单体应用中,可以使用拦截器来校验token

分布式项目中,可以在网关内校验token

token拦截器

在上面的案例中,token在body中作为数据传递的,但是这样是不安全的,比较推荐的做法是加在请求头内,通过请求头携带.

package cn.rayfoo.modules.base.interceptor;

import cn.rayfoo.common.util.JWTUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * <p></p>
 * @date 2020/8/11 15:58
 */
public class JWTInterceptor implements HandlerInterceptor {

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

        //从请求头内获取token
        String token = request.getHeader("authorization");

        //验证令牌  如果令牌不正确会出现异常 被全局异常处理
        JWTUtil.verify(token);

        return true;
    }

}

拦截器注册

这里我对所有的请求进行了拦截,放行了登录接口,真实的场景下,我们一般会放行所有/user/**的请求,另外,这个拦截器中没有注入其他属性,所以可以通过此种方式创建,如果拦截器内注入了属性,需要使用@Bean+方法的形式注册拦截器。详细的内容,可以参考我博客中关于拦截器的介绍。

package cn.rayfoo.common.config;

import cn.rayfoo.modules.base.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * <p></p>
 * @date 2020/8/11 16:13
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new JWTInterceptor())
        .addPathPatterns("/**")
        .excludePathPatterns("/user/login");

    }

}

封装统一返回结果类

接下来的代码我会使用统一结果集来封装返回值,下面是统一结果集的代码:

package cn.rayfoo.common.response;

import lombok.Data;


/**
 * @author rayfoo@qq.com
 * @date 2020年8月6日
 */
@Data
public class Result<T> {

    /**
     * 状态码
     */
    private Integer code;

    /**
     * 提示信息
     */
    private String  msg;

    /**
     * 数据记录
     */
    private T data;

    public Result() {
    }

    public Result(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Result(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

}

全局异常处理

在拦截器中处理异常也是非常不好的习惯,我们可以将异常交由统一异常处理来管理

package cn.rayfoo.common.exception;

import cn.rayfoo.common.response.Result;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;


/**
 * @author rayfoo@qq.com
 * @version 1.0
 * <p>全局异常处理</p>
 * @date 2020/8/11 16:36
 */
@ControllerAdvice@Slf4j
public class ServiceExceptionHandler {

    /**
     * 默认异常的状态码
     */
    private static final Integer DEFAULT_EXCEPTION = 500;

    /**
     * token超时异常状态码
     */
    private static final Integer TOKEN_ERROR_EXCEPTION = 505;

    /**
     * token无效状态码
     */
    private static final Integer TOKEN_EXPIRED_EXCEPTION = 506;


    /**
     * 处理token异常
     */
    @ResponseBody
    @ExceptionHandler({SignatureVerificationException.class, AlgorithmMismatchException.class, JWTDecodeException.class})
    public Result<String> tokenErrorException() {
        Result<String> result = new Result<>();
        result.setCode(TOKEN_ERROR_EXCEPTION);
        result.setMsg("无效的token!");
        log.error("无效的token");
        return result;
    }

    /**
     * 处理token异常
     */
    @ResponseBody
    @ExceptionHandler({TokenExpiredException.class})
    public Result<String> tokenExpiredException() {
        Result<String> result = new Result<>();
        result.setCode(TOKEN_EXPIRED_EXCEPTION);
        result.setMsg("token超时!");
        log.error("用户token超时");
        return result;
    }

    /**
     * 处理所有RuntimeException异常
     */
    @ResponseBody
    @ExceptionHandler({RuntimeException.class})
    public Result<String> allException(RuntimeException e) {
        Result<String> result = new Result<>();
        result.setCode(DEFAULT_EXCEPTION);
        result.setMsg( e.getMessage());
        log.error(e.getMessage());
        e.printStackTrace();
        return result;
    }

    /**
     * 处理所有Exception异常
     */
    @ResponseBody
    @ExceptionHandler({Exception.class})
    public Result<String> allException(Exception e) {
        Result<String> result = new Result<>();
        result.setCode(DEFAULT_EXCEPTION);
        result.setMsg( e.getMessage());
        log.error(e.getMessage());
        e.printStackTrace();
        return result;
    }

}

封装BaseController

这里介绍一个Controller的小技巧,可以通过通用Controller来封装一些公共的属性

package cn.rayfoo.modules.base.controller;

import cn.rayfoo.modules.base.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * @date 2020/8/5 14:34
 * @description 基础controller
 */
public class BaseController {

    /**
     * 注入全部service
     */
    @Autowired
    protected UserService userService;


    /**
     * 创建session、Request、Response等对象
     */
    protected HttpServletRequest request;
    protected HttpServletResponse response;
    protected HttpSession session;


    /**
     * 在每个子类方法调用之前先调用
     * 设置request,response,session这三个对象
     *
     * @param request
     * @param response
     */
    @ModelAttribute
    public void setReqAndRes(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;
        this.session = request.getSession(true);
        //可以在此处拿到当前登录的用户
    }

}

Controller代码优化

如果需要用到token中的数据,可以使用request对象中获取token进行相关的处理。下面是优化后的Controller代码,是不是焕然一新呢?

package cn.rayfoo.modules.base.controller;

import cn.rayfoo.common.response.HttpStatus;
import cn.rayfoo.common.response.Result;
import cn.rayfoo.common.util.JWTUtil;
import cn.rayfoo.modules.base.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author rayfoo@qq.com
 * @version 1.0
 * <p></p>
 * @date 2020/8/11 14:12
 */
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController extends BaseController {


    @PostMapping("/login")
    public Result<String> login(@RequestBody User user) throws Exception {

        //初始化返回值
        Result<String> result = new Result<>();

        //用户登录校验
        User loginUser = userService.login(user);

        //没有抛出异常表示正常
        result.setCode(HttpStatus.OK.value());
        result.setMsg("认证成功!");

        //声明payload
        Map<String, String> payload = new HashMap<>(2);

        //初始化payload
        payload.put("id", loginUser.getId().toString());
        payload.put("username", loginUser.getUsername());

        //获取令牌
        String token = JWTUtil.getToken(payload, 20);

        //在响应结果中添加token
        result.setData(token);

        //返回结果
        return result;
    }

    @GetMapping("/list")
    public Result<List<User>> userList() throws Exception {

        //初始化返回值
        Result<List<User>> result = new Result<>();
        //如果成功,设置状态码和查询到的结果
        result.setCode(HttpStatus.OK.value());
        result.setMsg("查询成功!");
        List<User> users = userService.userList();
        result.setData(users);
        //返回结果
        return result;
    }

}

github:https://github.com/18338862369/SpringBoot-JWT

gitee:https://gitee.com/rayfoo/SpringBoot-JWT

如果代码对你有帮助 青帮我点个star哦~

参考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

原文地址:https://www.cnblogs.com/zhangruifeng/p/13477048.html