前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot REST 风格 API 接口 JWT Token 认证

Spring Boot REST 风格 API 接口 JWT Token 认证

作者头像
Java宝典
发布2021-01-14 14:49:45
1.6K0
发布2021-01-14 14:49:45
举报

Spring Boot REST 风格 API 接口 JWT Token 认证

需求分析

接口认证需求:

1 能够有选择地过滤没有权限(Token)的请求 2 Token 具有时效性 3 如果用户连续操作,Token 能够自动刷新(自动延长有效期)

核心依赖

代码语言:javascript
复制
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>3.8.3</version>
</dependency>

本文使用目前最新版本的依赖

核心代码

JwtUtil 生成和解密Token

代码语言:javascript
复制
package com.example.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;

/**
 * @Author: lty
 * @Date: 2019/12/17 09:33
 */
public class JwtUtil {
    private final static String DEFAULT_SECRET = "TASDASDF9823K4JH29S8D2H349SDFH14";

    //region 加密区
    public static String encode(String k, String v) {

        return encode(k, v, 0);
    }

    private static String encode(String k, String v, long expireTime) {
        return encode(null, k, v, expireTime);
    }


    private static String encode(String secret, String k, String v, long expireTime) {
        if (secret == null || secret.length() < 1) {
            secret = DEFAULT_SECRET;
        }
        Date expDate = null;
        if (expireTime > 1) {
            expDate = new Date(System.currentTimeMillis() + expireTime);
        }

        //创建加密的token
        Algorithm algorithm = Algorithm.HMAC256(secret);
        String token =JWT.create().withIssuer("lty").withClaim(k,v)
          .withExpiresAt(expDate).sign(algorithm);
        return token;
    }
    //endregion


    //region 解密区
    public static String decode(String key, String encryptedToken) {
        return decode(null, key, encryptedToken);
    }


    public static String decode(String secret, String key, String encryptedToken) {
        if ("".equals(secret) || null == secret) {
            secret = DEFAULT_SECRET;
        }
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("lty")
                .build();
        String s = verifier.verify(encryptedToken).getClaim(key).asString();
        return s;
    }


    //endregion
    public static void main(String[] args) {
        String encode = JwtUtil.encode("TokenKey", "843328437@1576551621592");
        System.out.println(encode);

        String lty = JwtUtil.decode("TokenKey", encode);
        System.out.println(lty);
    }
}

Token 拦截器

拦截器配置

代码语言:javascript
复制
package com.example.interceptor;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;


@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {

    /**
     * 多个拦截器组成一个拦截器链
     * addPathPatterns 用于添加拦截规则
     * excludePathPatterns 用户排除拦截
     * @param registry
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {

        //拦截
        registry.addInterceptor(new TokenInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/autologon/authorizationLogin");
        super.addInterceptors(registry);
    }

    /**
     * 配置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        ///将所有/static/** 访问都映射到classpath:/static/ 目录下
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        super.addResourceHandlers(registry);
    }

}
代码语言:javascript
复制
package com.example.interceptor;

import com.example.pojo.Result;
import com.example.util.JwtUtil;
import com.example.util.ResultUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Objects;


public class TokenInterceptor implements HandlerInterceptor {

    private final Logger logger = LoggerFactory.getLogger(TokenInterceptor.class);

    private final String TOKEN_HEADERS_FIELD = "authCheckCode";
    private final String TOKEN_KEY = "TokenKey";
    public static final long TOKEN_REFRESH_TIME_MILLIS = 1000 * 60 * 60 * 2L;
    public static final long TOKEN_EXPIRE_TIME_MILLIS = 1000 * 60 * 60 * 24 * 30L;

    /**
     * controller 执行之前调用
     * @param httpRequest
     * @param httpResponse
     * @param o
     * @return
     * @throws IOException
     */
    @Override
    public boolean preHandle(HttpServletRequest httpRequest, HttpServletResponse httpResponse, Object o) throws Exception {
        httpResponse.addHeader("Access-Control-Allow-Origin", "*");
        httpResponse.addHeader("Access-Control-Allow-Headers","*");
        // 允许跨域的Http方法
        httpResponse.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE");
        // 允许浏览器访问 Token 认证响应头
        httpResponse.addHeader("Access-Control-Expose-Headers",  TOKEN_HEADERS_FIELD);
        // 默认返回原 Token
        httpResponse.setHeader(TOKEN_HEADERS_FIELD, httpRequest.getHeader(TOKEN_HEADERS_FIELD));
        // 应对探针模式请求(OPTIONS)
        String methodOptions = "OPTIONS";
        if (httpRequest.getMethod().equals(methodOptions)) {
            httpResponse.setStatus(HttpServletResponse.SC_ACCEPTED);
            return true;
        }

        Result result = checkToken(httpRequest, httpResponse);
        if (!"00".equals(result.getCode())) {
            logger.warn("{}",result.toString());
            httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            httpResponse.setContentType("application/json; charset=utf-8");
            httpResponse.setCharacterEncoding("utf-8");
            PrintWriter writer = httpResponse.getWriter();
            writer.write(new ObjectMapper().writeValueAsString(result.toString()));
            return false;
        }

        return true;
    }

    private Result checkToken(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            String token = request.getHeader(TOKEN_HEADERS_FIELD);
            if (token == null || token.length() < 1) {
                return ResultUtil.requestFaild("无效请求头");
            }
            String tokenValue = JwtUtil.decode(TOKEN_KEY, token);
            long time = Long.parseLong(tokenValue.substring(tokenValue.indexOf("@") + 1));
            String user = tokenValue.substring(0, tokenValue.indexOf("@"));
            logger.info("{}, date: {}, user: {}", tokenValue, new Date(time), user);
            // 校验 Token 有效性
            long subResult = System.currentTimeMillis() - time;
            if (subResult >= TOKEN_EXPIRE_TIME_MILLIS) {
                return ResultUtil.requestFaild("Token过期");
            }
            if (subResult > TOKEN_REFRESH_TIME_MILLIS) {
                // 刷新 Token
                String newToken = JwtUtil.encode(TOKEN_KEY,user + "@" + System.currentTimeMillis());
                System.out.println("生成新token "+ newToken+ "刷新时间 "+System.currentTimeMillis());
                response.setHeader(TOKEN_HEADERS_FIELD, newToken);
                return ResultUtil.requestSuccess(null);
            }

        } catch (Exception e) {
            logger.warn("Token 校验失败,{}:{}", e.getClass().getName(), e.getMessage());
            return ResultUtil.requestFaild("Token验证失败");
        }
        return ResultUtil.requestSuccess(null);
    }


    /**
     * controller 执行之后,且页面渲染之前调用
     */
    @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 {

    }


}

测试

写一个HTTP接口 测试Token验证

代码语言:javascript
复制
/**
 * 功能描述   test
 *
 * @param
 * @return com.example.pojo.Result
 * @author lty
 * @date 2019/11/28
 */
@RequestMapping(value = "/t")
public Result t() throws Exception {
    return testService.testList();
}

控制台输出日志

代码语言:javascript
复制
2019-12-17 13:43:46,095  INFO TokenInterceptor:78 - 843328437@1576551621592, date: Tue Dec 17 11:00:21 CST 2019, user: 843328437
生成新token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJsdHkiLCJUb2tlbktleSI6Ijg0MzMyODQzN0AxNTc2NTYxNDI2MDk4In0.X-qsFfMAP9ebYIHjkpaD3rgJqqPp3QnTuek7aRKvEVE刷新时间 1576561426111
2019-12-17 13:43:46,113  INFO HttpAspect:50 - url=http://localhost:8080/example/test/t
2019-12-17 13:43:46,113  INFO HttpAspect:52 - method=POST
2019-12-17 13:43:46,114  INFO HttpAspect:54 - ip=0:0:0:0:0:0:0:1
2019-12-17 13:43:46,114  INFO HttpAspect:56 - class_method=com.example.controller.TestController.t
2019-12-17 13:43:46,114  INFO HttpAspect:58 - args={}
2019-12-17 13:43:46,114  INFO HttpAspect:59 - >>>>>>>>>>>>>>

Gtihub 地址: github.com/liangtengyu

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java宝典 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Boot REST 风格 API 接口 JWT Token 认证
    • 需求分析
      • 核心依赖
        • 核心代码
          • Token 拦截器
            • 测试
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档