SpringBoot集成JWT

JSON Web Tokens(JWT),是目前最流行的跨域身份验证解决方案。

为什么要采用JWT?

传统的Session校验,需要将用户登录信息保存在服务端,导致扩展性不强。当遇到分布式服务时,Session数据往往需要共享,就需要通过手段将Session数据持久化,达到这一目采用的最多的方式就是用Redis来实现Session共享。

有没有方法不需要服务端来保存用户登录信息,把用户信息放在客户端,服务端只要根据客户端带入的参数校验客户端有效性就可以了?JWT就是这样的。

JWT的数据结构

JWT分成三段,为Header(头部)、Payload(负载)、Signature(签名)

三段数据以.隔开

Header.Payload.Signature

请看下面一段JWT数据

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJhZG1pbiJ9.Jwb2uY2Sb0SPsQavnpC3irfmNUCt6OjdmLZSRiD_8Bs 

JWT Header是元数据,描述了签名算法和token类型,payload就是客户端要保存的数据、Signature是数据签名,用来防止保存在客户端的数据被篡改。

创建SpringBoot项目

打开https://start.spring.io/,创建一个spring boot的maven项目,并导入到开发工具中。

创建接口

创建两个接口为/login/hello-jwt/hello-jwt接口必须要登录后才能访问。

导入web依赖

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

创建/hello-jwt接口

package com.javafm.jwt.helljwt;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloJwtController {

    @RequestMapping("/hello-jwt")
    public String helloJwt() {
        return "hello jwt";
    }
}

导入jwt依赖

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

创建/login接口

package com.javafm.jwt.helljwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class LoginController {

    private String createToken(String username, String password) {
        return JWT.create().withAudience(username).sign(Algorithm.HMAC256(password));
    }


    @RequestMapping("/login")
    public Map<String, String> login(@RequestParam("username") String username, @RequestParam("password") String password) {
        if ("admin".equals(username) && "admin".equals(password)) {
            // 登录成功后创建jwt
            Map<String, String> resMap = new HashMap<>();
            resMap.put("token", createToken(username, password));
            resMap.put("code", "00000");
            resMap.put("msg", "success");
            return resMap;
        }
        Map<String, String> resMap = new HashMap<>();
        resMap.put("code", "A0200");
        resMap.put("msg", "用户登录异常");
        return resMap;
    }
}

创建JWT校验拦截器

package com.javafm.jwt.helljwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AuthInterceptor implements HandlerInterceptor {

    public void error(HttpServletResponse response) {
        response.setHeader("Content-Type", "text/plain;charset=utf-8");
        try {
            response.getWriter().write("未授权的请求");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();

        // 登录接口 `/login` 不需要校验
        if (uri.indexOf("/login") == 0) {
            return true;
        }

        // 获取header中的token
        String token = request.getHeader("Authorization");
        if (token == null) {
            error(response);
            return false;
        }

        // 从token中提取出username,这个是在登录创建token时放进去的
        String username;
        try {
            username = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException e) {
            error(response);
            return false;
        }
        // 假设这里根据 username 去数据库查询出用户信息,在这里我们写死是password
        String password = "admin"; // 假设这里是根据用户名从数据库查询出来的用户信息

        // 验证token
        // Algorithm.HMAC256 参数放的不一定是密码,这个的看你创建token时,放的是什么内容,这里就传入什么内容
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build();

        try {
            jwtVerifier.verify(token);
            return true;
        }catch (JWTVerificationException e) {
            error(response);
            return false;
        }
    }
}

注册拦截器

package com.javafm.jwt.helljwt;

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 WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
    }
}

在未登录前直接请求/hell-jwt接口,被服务端拦截器拦截,直接返回未授权的请求

先登录获取token,再将token放到header中请求/hello-jwt

源代码地址:https://gitee.com/dev-tang/hell-jwt.git

本博客采用 知识共享署名-禁止演绎 4.0 国际许可协议 进行许可

本文标题:SpringBoot集成JWT

本文地址:https://dev-tang.com/post/2020/05/springboot-jwt.html

推荐阅读