zoukankan      html  css  js  c++  java
  • 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)GET请求:http://localhost:8080/getData,在token中随便输入字符串,返回如下

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

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

  • 相关阅读:
    CIA泄露资料分析(黑客工具&技术)—Windows篇
    包学会之浅入浅出Vue.js:结业篇
    包学会之浅入浅出Vue.js:升学篇
    包学会之浅入浅出Vue.js:开学篇
    Manacher算法详解
    CSP-S 2019 游记
    洛谷 P3373 【模板】线段树 2
    AHOI 2009 维护序列
    洛谷 P4017 最大食物链计数
    洛谷 SP14932 LCA
  • 原文地址:https://www.cnblogs.com/gdjlc/p/12081701.html
Copyright © 2011-2022 走看看