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

spring boot 2 集成JWT实现api接口认证

程序员文章站 2022-06-05 17:29:37
JSON Web Token(JWT)是目前流行的跨域身份验证解决方案。 官网:https://jwt.io/。 本文使用spring boot 2 集成JWT实现api接口验证。 ......

json web token(jwt)是目前流行的跨域身份验证解决方案。
官网:
本文使用spring boot 2 集成jwt实现api接口验证。

一、jwt的数据结构

jwt由header(头信息)、payload(有效载荷)和signature(签名)三部分组成的,用“.”连接起来的字符串。
jwt的计算逻辑如下:
(1)signature = hmacsha256(base64urlencode(header) + "." + base64urlencode(payload), secret)
其中私钥secret保存于服务器端,不能泄露出去。
(2)jwt = base64urlencode(header) + "." + base64urlencode(payload) + signature

下面截图以官网的例子,简单说明

spring boot 2 集成JWT实现api接口认证

二、jwt工作机制

客户端使用其凭据成功登录时,服务器生成jwt并返回给客户端。
当客户端访问受保护的资源时,用户代理使用bearer模式发送jwt,通常在authorization header中,如下所示:
authorization: bearer <token>
服务器检查authorization header中的有效jwt ,如果有效,则允许用户访问受保护资源。jwt包含必要的数据,还可以减少查询数据库或缓存信息。

三、spring boot集成jwt实现api接口验证

开发环境:
intellij idea 2019.2.2
jdk1.8
spring boot 2.1.11

1、创建一个springboot项目,pom.xml引用的依赖包如下

        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>

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

        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupid>com.alibaba</groupid>
            <artifactid>fastjson</artifactid>
            <version>1.2.62</version>
        </dependency>

2、定义一个接口的返回类

package com.example.jwtdemo.entity;


import lombok.data;
import lombok.noargsconstructor;
import lombok.tostring;

import java.io.serializable;

@data
@noargsconstructor
@tostring
public class responsedata<t> implements serializable {
    /**
     * 状态码:0-成功,1-失败
     * */
    private int code;

    /**
     * 错误消息,如果成功可为空或success
     * */
    private string msg;

    /**
     * 返回结果数据
     * */
    private t data;

    public static responsedata success() {
        return success(null);
    }

    public static responsedata success(object data) {
        responsedata result = new responsedata();
        result.setcode(0);
        result.setmsg("success");
        result.setdata(data);
        return result;
    }

    public static responsedata fail(string msg) {
        return fail(msg,null);
    }

    public static responsedata fail(string msg, object data) {
        responsedata result = new responsedata();
        result.setcode(1);
        result.setmsg(msg);
        result.setdata(data);
        return result;
    }
}

3、统一拦截接口返回数据

package com.example.jwtdemo.config;


import com.alibaba.fastjson.json;
import com.example.jwtdemo.entity.responsedata;
import org.springframework.core.methodparameter;
import org.springframework.http.mediatype;
import org.springframework.http.converter.httpmessageconverter;
import org.springframework.http.server.serverhttprequest;
import org.springframework.http.server.serverhttpresponse;
import org.springframework.web.bind.annotation.restcontrolleradvice;
import org.springframework.web.servlet.mvc.method.annotation.responsebodyadvice;

/**
 * 实现responsebodyadvice接口,可以对返回值在输出之前进行修改
 */
@restcontrolleradvice
public class globalresponsehandler implements responsebodyadvice<object> {

    //判断支持的类型
    @override
    public boolean supports(methodparameter methodparameter, class<? extends httpmessageconverter<?>> aclass) {
        return true;
    }

    @override
    public object beforebodywrite(object o, methodparameter methodparameter, mediatype mediatype, class<? extends httpmessageconverter<?>> aclass, serverhttprequest serverhttprequest, serverhttpresponse serverhttpresponse) {
        // 判断为null构建responsedata对象进行返回
        if (o == null) {
            return responsedata.success();
        }
        // 判断是responsedata子类或其本身就返回object o本身,因为有可能是接口返回时创建了responsedata,这里避免再次封装
        if (o instanceof responsedata) {
            return (responsedata<object>) o;
        }
        // string特殊处理,否则会抛异常
        if (o instanceof string) {
            return json.tojson(responsedata.success(o)).tostring();
        }
        return responsedata.success(o);
    }
}

4、统一异常处理

package com.example.jwtdemo.exception;

import com.example.jwtdemo.entity.responsedata;
import org.springframework.web.bind.annotation.exceptionhandler;
import org.springframework.web.bind.annotation.restcontrolleradvice;

@restcontrolleradvice
public class globalexceptionhandler {
    @exceptionhandler(exception.class)
    public responsedata exceptionhandler(exception e) {
        e.printstacktrace();
        return responsedata.fail(e.getmessage());
    }
}

5、创建一个jwt工具类

package com.example.jwtdemo.common;

import com.auth0.jwt.jwtverifier;
import com.auth0.jwt.algorithms.algorithm;
import com.auth0.jwt.interfaces.decodedjwt;
import com.auth0.jwt.jwt;

import java.util.date;

public class jwtutils {
    public static final string token_header = "authorization";
    public static final string token_prefix = "bearer ";
    // 过期时间,这里设为5分钟
    private static final long expire_time = 5 * 60 * 1000;
    // 密钥
    private static final string secret = "jwtsecretdemo";

    /**
     * 生成签名,5分钟后过期
     *
     * @param name 名称
     * @param secret 密码
     * @return 加密后的token
     */
    public static string sign(string name) {
        date date = new date(system.currenttimemillis() + expire_time);
        algorithm algorithm = algorithm.hmac256(secret); //使用hs256算法
        string token = jwt.create() //创建令牌实例
                .withclaim("name", name) //指定自定义声明,保存一些信息
                //.withsubject(name) //信息直接放在这里也行
                .withexpiresat(date) //过期时间
                .sign(algorithm); //签名
        return token;
    }

    /**
     * 校验token是否正确
     *
     * @param token 令牌
     * @param secret 密钥
     * @return 是否正确
     */
    public static boolean verify(string token) {
        try{
            string name = getname(token);
            algorithm algorithm = algorithm.hmac256(secret);
            jwtverifier verifier = jwt.require(algorithm)
                    .withclaim("name", name)
                    //.withsubject(name)
                    .build();
            decodedjwt jwt = verifier.verify(token);
            return true;
        } catch (exception e){
            return false;
        }
    }

    /**
     * 获得token中的信息
     *
     * @return token中包含的名称
     */
    public static string getname(string token) {
        try {
            decodedjwt jwt = jwt.decode(token);
            return jwt.getclaim("name").asstring();
        }catch(exception e){
            return null;
        }
    }
}

6、新建两个自定义注解:一个需要认证、另一个不需要认证

package com.example.jwtdemo.config;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
public @interface logintoken {
    boolean required() default true;
}
package com.example.jwtdemo.config;

import java.lang.annotation.elementtype;
import java.lang.annotation.retention;
import java.lang.annotation.retentionpolicy;
import java.lang.annotation.target;

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

7、新建拦截器并验证token

package com.example.jwtdemo.config;

import com.example.jwtdemo.common.jwtutils;
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;

public class authenticationinterceptor implements handlerinterceptor {

    @override
    public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception {
        // 如果不是映射到方法直接通过
        if(!(handler instanceof handlermethod)){
            return true;
        }
        handlermethod handlermethod=(handlermethod)handler;
        method method=handlermethod.getmethod();
        //检查是否有passtoken注释,有则跳过认证
        if (method.isannotationpresent(passtoken.class)) {
            passtoken passtoken = method.getannotation(passtoken.class);
            if (passtoken.required()) {
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isannotationpresent(logintoken.class)) {
            logintoken logintoken = method.getannotation(logintoken.class);
            if (logintoken.required()) {
                // 执行认证
                string tokenheader = request.getheader(jwtutils.token_header);// 从 http 请求头中取出 token
                if(tokenheader == null){
                    throw new runtimeexception("没有token");
                }
                string token = tokenheader.replace(jwtutils.token_prefix, "");
                if (token == null) {
                    throw new runtimeexception("没有token");
                }
                boolean b = jwtutils.verify(token);
                if (b == false) {
                    throw new runtimeexception("token不存在或已失效,请重新获取token");
                }
                return true;
            }
        }
        return false;
    }

    @override
    public void posthandle(httpservletrequest request, httpservletresponse response, object handler, modelandview modelandview) throws exception {

    }

    @override
    public void aftercompletion(httpservletrequest request, httpservletresponse response, object handler, exception ex) throws exception {

    }
}

8、配置拦截器

package com.example.jwtdemo.config;

import org.springframework.context.annotation.bean;
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 interceptorconfig implements webmvcconfigurer {
    @override
    public void addinterceptors(interceptorregistry registry) {
        registry.addinterceptor(authenticationinterceptor())
                .addpathpatterns("/**");
    }
    @bean
    public authenticationinterceptor authenticationinterceptor() {
        return new authenticationinterceptor();
    }
}

9、新建一个测试的控制器

package com.example.jwtdemo.controller;

import com.example.jwtdemo.common.jwtutils;
import com.example.jwtdemo.config.logintoken;
import com.example.jwtdemo.config.passtoken;
import org.springframework.web.bind.annotation.*;

@restcontroller
public class democontroller {
    @passtoken
    @postmapping("gettoken")
    public string gettoken(@requestparam string username, @requestparam string password){
        if(username.equals("admin") && password.equals("123456")){
            string token = jwtutils.sign("admin");
            return token;
        }
        return "用户名或密码错误";
    }

    @logintoken
    @getmapping("getdata")
    public string getdata() {
        return "获取数据...";
    }

}

10、postman测试

(1)get请求:http://localhost:8080/getdata,返回如下

spring boot 2 集成JWT实现api接口认证

 (2)get请求:http://localhost:8080/getdata,在token中随便输入字符串,返回如下

spring boot 2 集成JWT实现api接口认证

 (3)post请求:http://localhost:8080/gettoken,并设置用户名和密码参数,返回如下

spring boot 2 集成JWT实现api接口认证

 (4)get请求:http://localhost:8080/getdata,在token中输入上面token字符串,返回如下

spring boot 2 集成JWT实现api接口认证