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

JSON Web Token(JWT)是目前流行的跨域身份验证解决方案。
官网:https://jwt.io/
本文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

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

二、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,返回如下

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

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

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

原文地址:https://www.cnblogs.com/gdjlc/p/12081701.html

时间: 2024-07-28 16:42:36

spring boot 2 集成JWT实现api接口认证的相关文章

Spring Boot中使用Swagger2构建API文档

程序员都很希望别人能写技术文档,自己却很不愿意写文档.因为接口数量繁多,并且充满业务细节,写文档需要花大量的时间去处理格式排版,代码修改后还需要同步修改文档,经常因为项目时间紧等原因导致文档滞后于代码,接口调用方的抱怨声不绝于耳.而程序员是最擅长"偷懒"的职业了,自然会有多种多样的自动生成文档的插件.今天要介绍的就是Swagger. 接下来我们在Spring Boot中使用Swagger2构建API文档 Swagger是一个简单但功能强大的API表达工具.它具有地球上最大的API工具生

java web项目(spring项目)中集成webservice ,实现对外开放接口

什么是WebService?webService小示例 点此了解 下面进入正题: Javaweb项目(spring项目)中集成webservice ,实现对外开放接口步骤: 准备: 采用与spring兼容性较好的cxf来实现 cxf 的  jar下载地址: http://cxf.apache.org/download.html 选择zip格式下载,解压后的lib目录下的jar 需要最少的jar如下: cxf-2.3.3.jargeronimo-annotation_1.0_spec-1.1.1.

Spring Boot Admin 集成自定义监控告警

Spring Boot Admin 集成自定义监控告警 前言 Spring Boot Admin 是一个社区项目,可以用来监控和管理 Spring Boot 应用并且提供 UI,详细可以参考 官方文档. Spring Boot Admin 本身提供监控告警功能,但是默认只提供了 Hipchat.Slack 等国外流行的通讯软件的集成,虽然也有邮件通知,不过考虑到使用体检决定二次开发增加 钉钉 通知. 本文基于 Spring Boot Admin 目前最新版 1.5.7. 准备工作 Spring

spring boot admin 集成的简单配置随笔

和我并肩作战的同事也要相继离职了,心里还是有很多不舍得,现在业务也陆陆续续落在了肩头,上午项目经理让我把spring boot admin集成到现在的项目中,已遍后续的监控. 哇!我哪里搞过这个!心里好慌,好在我面向对象虽然不是很精通,但是面向百度我倒是很拿手,于是开启了,面向百度编程,现在已经成功过了~写个博客继续一下,方便以后使用以及分享. 注:此写法适用于 2.0以下版本 高于2.0请直接官方文档走起:http://codecentric.github.io/spring-boot-adm

最安全的api接口认证

最安全的api接口认证 实现步骤: 1.客户端与服务器都存放着用于验证的Token字段,客户端在本地把自己的 用户名+时间戳+Token 组合进行MD5加密后生成一段新的md5-token. 2.客户端访问的时候携带:用户名.时间戳.md5-token. 3.服务端收到请求后,先判断用户名.时间戳是否合法.假设先判断发送过来的时间戳和现在的时间戳不能大于2分钟. 4.如果是在2分钟之内,到redis里查看有没有该用户为key对应的md5-token,并判断它和发送过来的md5-token是否相同

spring boot Rabbitmq集成,延时消息队列实现

本篇主要记录Spring boot 集成Rabbitmq,分为两部分, 第一部分为创建普通消息队列, 第二部分为延时消息队列实现: spring boot提供对mq消息队列支持amqp相关包,引入即可: [html] view plain copy <!-- rabbit mq --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-

Kafka的安装及与Spring Boot的集成

安装JDK 下载jdk-8u202-ea-bin-b03-linux-x64-07_nov_2018.tar.gz 解压 配置 $ vi /etc/profile,在最后加入下面两行 export JAVA_HOME=/usr/local/bigdata/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH 重新登录执行 java,验证JDK配置成功 安装Kafka 下载kafka_2.11-1.0.2.tgz,这里主要1.0.2这个Kafka Server

java Spring Boot中使用Swagger2构建API文档

1.添加Swagger2的依赖 在pom.xml中加入Swagger2的依赖 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>io.spr

记录初学Spring boot中使用GraphQL编写API的几种方式

Spring boot+graphql 一.使用graphql-java-tools方式 <dependency> <groupId>com.graphql-java-kickstart</groupId> <artifactId>graphql-java-tools</artifactId> <version>5.6.0</version> </dependency> <dependency> &