JWT是什么?

JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于 Json 的开放标准。JWT 的声明一般被用来在身份提供者服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。

比如用在用户登录上时,基本思路就是用户提供用户名和密码给认证服务器,服务器验证用户提交信息的合法性;如果验证成功,会产生并返回一个 Token,用户可以使用这个 Token 访问服务器上受保护的资源。

JWT的组成

JWT 由三部分构成,第一部分称为头部(Header),第二部分称为消息体(Payload),第三部分是签名(Signature)。一个 JWT 生成的 Token 格式为:

token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

注意需要使用Base64对称加密,否则鉴权中心无法解密。

头部的信息通常由两部分内容组成,令牌的类型和使用的签名算法,比如下面的代码:

{
  "alg": "HS256", 
  "typ": "JWT"
}

消息体中可以携带一些你需要的信息,比如用户 ID。因为你得知道这个 Token 是哪个用户的,比如下面的代码:

{
  "id": "666", 
  "name": "biandan", 
  "blog": “”
}

签名是用来判断消息在传递的路上是否被篡改,从而保证数据的安全性,格式如下:

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

实现需求:用户登录成功后,使用 JWT 生成 token 返回给前端,前端拿到 token 后,如果需要调用后台接口,需要把 token 放在头部的 Authorization,否则鉴权不通过。

OK,我们创建一个 SpringBoot 项目。为了方便起见,直接把用户名和密码写到配置里。实际项目应该是从数据库或者 Redis 里读取。并且把 sessionId 也放在内存里,实际项目应该是放在 Redis 缓存里。

pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http:///POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http:///POM/4.0.0 http:///xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.study</groupId>
    <artifactId>study-jwt</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <dependencies>
        <!--Spring boot 集成包-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.15.0</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

配置文件 application.yml

server:
  port: 80

# 将SpringBoot项目作为单实例部署调试时,不需要注册到注册中心
eureka:
  client:
    fetch-registry: false
    register-with-eureka: false

# JWT 加解密的秘钥
myJwt:
  password: 666666

# 用户信息,暂时从配置文件读取,理论上应该从数据库、Redis里读取
userInfo:
  userName: biandan
  password: 123456
  userSign: 让天下没有难写的代码!

启动类:

package com.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author biandan
 * @description
 * @signature 让天下没有难写的代码
 * @create 2021-06-25 下午 7:20
 */
@SpringBootApplication
public class JwtApp {

    public static void main(String[] args) {
        SpringApplication.run(JwtApp.class, args);
    }
}

控制层 :

package com.study.controller;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author biandan
 * @description
 * @signature 让天下没有难写的代码
 * @create 2021-06-25 下午 7:21
 */
@RestController
public class LoginController {

    //从配置文件获取用户信息,理论上应该从数据库或者Redis,为了方便学习
    @Value("${userInfo.userName}")
    private String userName;

    @Value("${userInfo.password}")
    private String password;

    @Value("${userInfo.userSign}")
    private String userSign;

    //JWT 加解密的秘钥
    @Value("${myJwt.password}")
    private String jwtPassword;

    //保存 sessionId-sign,理论上应该保存到 Redis
    private static Map<String, String> userInfoMap = Collections.synchronizedMap(new HashMap<>());

    //登录接口,登录成功后返回 token
    @PostMapping("/login")
    public Map<String, Object> login(HttpServletRequest request) {
        Map<String, Object> result = new HashMap<>();
        String userName = request.getParameter("userName");
        String password = request.getParameter("password");
        if (this.userName.equals(userName) && this.password.equals(password)) {
            String sessionId = request.getSession().getId();
            //使用 JWT 生成 token
            String token = getToken(sessionId, userName);
            result.put("data", token);
            result.put("code", HttpStatus.OK.value());
            result.put("msg", "登录成功");
        } else {
            result.put("code", HttpStatus.FORBIDDEN.value());
            result.put("msg", "账号或密码不对,登录失败!");
        }
        return result;
    }

    //使用 JWT 生成token
    private String getToken(String sessionId, String userName) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("alg", "HS256");
        map.put("typ", "JWT");
        String token = JWT.create()
                .withHeader(map)
                .withClaim("sessionId", sessionId) //存入 sessionId
                .withClaim("userName", userName)//存入 userName
                .withIssuedAt(new Date())
                .sign(Algorithm.HMAC256(jwtPassword));
        System.out.println("登录 sessionId=" + sessionId);
        System.out.println("登录 token=" + token);
        userInfoMap.put(sessionId, userSign);//存放session-用户的个性签名
        return token;
    }


    //鉴权,鉴权成功我们返回用户信息
    @PostMapping("/getUserInfo")
    public Map<String, Object> getUserInfo(HttpServletRequest request) {
        Map<String, Object> result = new HashMap<>();
        String token = request.getHeader("Authorization");
        if (!StringUtils.isEmpty(token)) {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(jwtPassword)).build();
            Map<String, Claim> map = verifier.verify(token).getClaims();
            if (null != map) {
                String sessionId = map.get("sessionId").asString();//取出 sessionId
                String userSign = userInfoMap.get(sessionId);
                String userName = map.get("userName").asString();//取出 userName
                System.out.println("对应的userName=" + userName);
                result.put("data", userSign);
                result.put("code", HttpStatus.OK.value());
                result.put("msg", "SUCCESS");
                return result;
            }
        }
        result.put("code", HttpStatus.UNAUTHORIZED.value());
        result.put("msg", "账号未登录,请重新登录!");
        return result;
    }

}

启动,使用 postman 测试:

1、测试登录接口:

springboot使用jwt生成token jwt认证springboot_java

2、继续测试 getUserInfo 接口,把刚才后台返回的 data 数据(就是 token )复制出来,在 Headers 里的 key 选择 Authorization,value 就是 token 值。

springboot使用jwt生成token jwt认证springboot_java_02

OK,测试通过。