欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot认证:整合Jwt

程序员文章站 2022-06-14 08:17:13
本文主要讲解Spring Boot 整合Jwt 认证的示例,详细内容,详见文末源码。 ......

背景

jwt全称是:json web token。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。

优点

  1. 简洁: 可以通过urlpost参数或者在http header发送,因为数据量小,传输速度也很快;
  2. 自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库;
  3. 因为token是以json加密的形式保存在客户端的,所以jwt是跨语言的,原则上任何web形式都支持;
  4. 不需要在服务端保存会话信息,特别适用于分布式微服务。

缺点

  1. 无法作废已颁布的令牌;
  2. 不易应对数据过期。

一、jwt消息构成

1.1 组成

一个token分3部分,按顺序为

  1. 头部(header)
  2. 载荷(payload)
  3. 签证(signature)

三部分之间用.号做分隔。例如:

eyj0exaioijkv1qilcjhbgcioijiuzi1nij9.eyjhdwqioiixyzdiy2izms02odflltrlzgytymu3yy0wotlkodazm2vky2uilcjlehaioje1njk3mjc4otf9.wwemzyb3tsqk34jmez36mmc5xpuh15ni3vov_sgczj8

jwt的头部承载两部分信息:

  1. 声明类型,这里是jwt
  2. 声明加密的算法 通常直接使用 hmac sha256

jwt里验证和签名使用的算法列表如下:

jws 算法名称
hs256 hmac256
hs384 hmac384
hs512 hmac512
rs256 rsa256
rs384 rsa384
rs512 rsa512
es256 ecdsa256
es384 ecdsa384
es512 ecdsa512

1.3 playload

载荷就是存放有效信息的地方。基本上填2种类型数据

  1. 标准中注册的声明的数据;
  2. 自定义数据。

由这2部分内部做base64加密。

  • 标准中注册的声明 (建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 自定义数据:存放我们想放在token中存放的key-value

1.4 signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成
base64加密后的headerbase64加密后的payload连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

二、spring bootjwt集成示例

示例代码采用:

<dependency>
    <groupid>com.auth0</groupid>
    <artifactid>java-jwt</artifactid>
    <version>3.8.1</version>
</dependency>

2.1 项目依赖

<dependencies>
    <dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-web</artifactid>
    </dependency>
    <dependency>
        <groupid>io.jsonwebtoken</groupid>
        <artifactid>jjwt</artifactid>
        <version>0.9.1</version>
    </dependency>
    <dependency>
        <groupid>com.auth0</groupid>
        <artifactid>java-jwt</artifactid>
        <version>3.8.1</version>
    </dependency>
    <!-- redis -->
    <dependency>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-data-redis</artifactid>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupid>org.projectlombok</groupid>
        <artifactid>lombok</artifactid>
        <scope>1.8.4</scope>
    </dependency>
    <dependency>
        <groupid>com.alibaba</groupid>
        <artifactid>fastjson</artifactid>
        <version>1.2.47</version>
    </dependency>
</dependencies>

2.2 自定义注解@jwttoken

加上该注解的接口需要登录才能访问

@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
public @interface jwttoken {
    boolean required() default true;
}

2.3 jwt 认证工具类jwtutil.java

主要用来生成签名、校验签名和通过签名获取信息

public class jwtutil {

    /**
     * 过期时间5分钟
     */
    private static final long expire_time = 5 * 60 * 1000;
    /**
     * jwt 密钥
     */
    private static final string secret = "jwt_secret";

    /**
     * 生成签名,五分钟后过期
     * @param userid
     * @return
     */
    public static string sign(string userid) {
        try {
            date date = new date(system.currenttimemillis() + expire_time);
            algorithm algorithm = algorithm.hmac256(secret);
            return jwt.create()
                    // 将 user id 保存到 token 里面
                    .withaudience(userid)
                    // 五分钟后token过期
                    .withexpiresat(date)
                    // token 的密钥
                    .sign(algorithm);
        } catch (exception e) {
            return null;
        }
    }

    /**
     * 根据token获取userid
     * @param token
     * @return
     */
    public static string getuserid(string token) {
        try {
            string userid = jwt.decode(token).getaudience().get(0);
            return userid;
        } catch (jwtdecodeexception e) {
            return null;
        }
    }

    /**
     * 校验token
     * @param token
     * @return
     */
    public static boolean checksign(string token) {
        try {
            algorithm algorithm = algorithm.hmac256(secret);
            jwtverifier verifier = jwt.require(algorithm)
                    // .withclaim("username", username)
                    .build();
            decodedjwt jwt = verifier.verify(token);
            return true;
        } catch (jwtverificationexception exception) {
            throw new runtimeexception("token 无效,请重新获取");
        }
    }
}

2.4 拦截器拦截带有注解的接口

  • jwtinterceptor.java
public class jwtinterceptor implements handlerinterceptor {

    @override
    public boolean prehandle(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object object) {
        // 从 http 请求头中取出 token
        string token = httpservletrequest.getheader("token");
        // 如果不是映射到方法直接通过
        if(!(object instanceof handlermethod)){
            return true;
        }
        handlermethod handlermethod=(handlermethod)object;
        method method=handlermethod.getmethod();
        //检查有没有需要用户权限的注解
        if (method.isannotationpresent(jwttoken.class)) {
            jwttoken jwttoken = method.getannotation(jwttoken.class);
            if (jwttoken.required()) {
                // 执行认证
                if (token == null) {
                    throw new runtimeexception("无token,请重新登录");
                }
                // 获取 token 中的 userid
                string userid = jwtutil.getuserid(token);
                system.out.println("用户id:" + userid);

                // 验证 token
                jwtutil.checksign(token);
            }
        }
        return true;
    }

    @override
    public void posthandle(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object o, modelandview modelandview) throws exception {

    }
    @override
    public void aftercompletion(httpservletrequest httpservletrequest, httpservletresponse httpservletresponse, object o, exception e) throws exception {

    }
}
  • 注册拦截器:webconfig.java
@configuration
public class webconfig implements webmvcconfigurer {

    /**
     * 添加jwt拦截器
     * @param registry
     */
    @override
    public void addinterceptors(interceptorregistry registry) {
        registry.addinterceptor(jwtinterceptor())
                // 拦截所有请求,通过判断是否有 @jwttoken 注解 决定是否需要登录
                .addpathpatterns("/**");
    }

    /**
     * jwt拦截器
     * @return
     */
    @bean
    public jwtinterceptor jwtinterceptor() {
        return new jwtinterceptor();
    }
}

2.5 全局异常捕获

@restcontrolleradvice
public class globalexceptionhandler {
    @responsebody
    @exceptionhandler(exception.class)
    public object handleexception(exception e) {
        string msg = e.getmessage();
        if (msg == null || msg.equals("")) {
            msg = "服务器出错";
        }
        jsonobject jsonobject = new jsonobject();
        jsonobject.put("message", msg);
        return jsonobject;
    }
}

2.6 接口

  • jwtcontroller.java
@restcontroller
@requestmapping("/jwt")
public class jwtcontroller {

    /**
     * 登录并获取token
     * @param username
     * @param password
     * @return
     */
    @postmapping("/login")
    public object login( string username, string password){
        jsonobject jsonobject=new jsonobject();
        // 检验用户是否存在(为了简单,这里假设用户存在,并制造一个uuid假设为用户id)
        string userid = uuid.randomuuid().tostring();
        // 生成签名
        string token= jwtutil.sign(userid);
        map<string, string> userinfo = new hashmap<>();
        userinfo.put("userid", userid);
        userinfo.put("username", username);
        userinfo.put("password", password);
        jsonobject.put("token", token);
        jsonobject.put("user", userinfo);
        return jsonobject;
    }

    /**
     * 该接口需要带签名才能访问
     * @return
     */
    @jwttoken
    @getmapping("/getmessage")
    public string getmessage(){
        return "你已通过验证";
    }
}

2.7 postman测试接口

2.7.1 在没token的情况下访问jwt/getmessage接口

{
    "message": "无token,请重新登录"
}

2.7.2 先登录,在访问jwt/getmessage接口

  • 登录请求及结果,详见下图:

Spring Boot认证:整合Jwt

登录后得到签名如箭头处

  • 在请求头加上签名,然后再请求jwt/getmessage接口

Spring Boot认证:整合Jwt

请求通过,测试成功!

2.7.3 过期后再次访问

我们设置的签名过期时间是五分钟,五分钟后再次访问jwt/getmessage接口,结果如下:

Spring Boot认证:整合Jwt

通过结果,我们发现时间到了,签名失效,说明该方案通过。

三、总结

3.1 示例源码

github 源码

3.2 技术交流

  1. 风尘博客-csdn