SpringBoot+JWT实现拦截器的实现方式

踩过的坑

        由于是第一次接触拦截器,所以对拦截器中的配置也不是很懂,导致在这个过程中踩了很多坑,最让我印象深刻的是,在配置拦截器的时候,由于我在项目中使用到了请求前缀,而我的拦截器又是从其他博主的博客中直接拿来用的,导致只要我一打开@Configuration,就会报404错误(尽管放行了登录请求),其原因是在添加拦截请求时,使用的是  /**  ,其会让我的请求前缀也会纳入进去,导致一进入登录请求,就会被拦截到,导致请求误拦截。

拦截器实现的方式

        其实拦截器总的来说就是添加拦截请求和放行请求,但是在实现上也确实有不同的思路,以下是我总结的两种实现方式(本质上大差不差)

方式一  拦截路径和放行路径设置在一起

实现步骤

编写拦截器
import cn.hutool.jwt.Claims;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.example.demo.config.MyContext;
import com.example.demo.utils.JwtUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;


//认证拦截器
@Component //添加到容器
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtils jwtUtils;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String login = request.getRequestURI();
        if (login.contains("/login")) {
            return true;
        }
        // //获取请求头中的token
        final String token;
        final String authHeader = request.getHeader("Authorization");
        if (StringUtils.isNotBlank(authHeader) && authHeader.startsWith("Bearer ")) {
            //            截取token
            token = authHeader.substring(7);
        } else {
            if (request.getHeader("token") != null) {
                token = request.getHeader("token");
            } else {
                token = request.getParameter("token");
            }
        }
        Map<String, Object> mp = new HashMap<>();
        try {
            if (token == null) {
                System.out.println("token空");
            }
            jwtUtils.verify(token);//验证token
            return true;  //上一句无异常就 放行
        } catch (SignatureVerificationException e) {
//            e.printStackTrace();
            mp.put("msg", "无效签名");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            mp.put("msg", "token过期,请重新登录");
        } catch (AlgorithmMismatchException e) {
//            e.printStackTrace();
            mp.put("msg", "算法不匹配");
        } catch (NullPointerException e) {
//            e.printStackTrace();
            mp.put("msg", "token不能为空");
        } catch(RuntimeException e){
            e.printStackTrace();
            mp.put("msg", "token不正确");
        } catch (Exception e) {
//            e.printStackTrace();
            mp.put("msg", "其他异常");
        }
        mp.put("state", false);
        //map转换为json
        String json = new ObjectMapper().writeValueAsString(mp);

        response.setContentType("application/json; charset=UTF-8");

        //返回
        response.getWriter().println(json);
        return false;
    }
}
添加拦截器进配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;



@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    // 注入之前设置的拦截器(这里方式不止一种,之前听了一个实习生的描述,感觉他只知道Spring的这种注入。。。可别被限制了,这只是创建对象而已)
    @Autowired
    private LoginInterceptor loginInterceptor;

    //    注入拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationFilter)
                // 拦截所有请求(这里要十分注意,如果你在yml配置文件中添加了请求前缀,就设置为请求前缀下的路径。例如设置的请求前缀为 /admin ,则需要设置为 /admin/**)
               .addPathPatterns("/**")
                // 设置放行路径

                .addPathPatterns("/workPlan/**")

                .excludePathPatterns("/admin/login");
    }
}

 

方式二  放行路径单独(或多个)的JWT设置

编写拦截器

创建拦截器,在拦截器中添加放行路径以及登录校验

import com.xinxun.wxsecondheadmark.util.ContextHolder;
import com.xinxun.wxsecondheadmark.util.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Component
public class LoginInterceptor implements HandlerInterceptor {
    /**
     * 请求头
     */
    private static final String HEADER_AUTH = "Authorization";

    /**
     * 安全的url,不需要令牌
     */
    private static final List<String> SAFE_URL_LIST = Arrays.asList("/wx/admin/login", "/wx/admin/register");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        response.setContentType("application/json; charset=utf-8");

        String prop = request.getRequestURI().substring(request.getContextPath().length());
        String url = "/wx"+prop;
        // 登录和注册等请求不需要令牌
        // todo 由于上述集合中的安全URL是具体的,因为有新的需求,要求将以client开头的前缀给放行,因此使用了字符串的contains方法来模糊匹配
        if (prop.contains("/client/")||prop.contains("/order/")||prop.contains("/goods/")||SAFE_URL_LIST.contains(url)) {
            return true;
        }
        System.out.println("***********没有被放行的请求*************");
        System.out.println(url);
        // 从请求头里面读取token
        String token = request.getHeader(HEADER_AUTH);
        if (token == null) {
            throw new RuntimeException("请求失败,令牌为空");
        }
        System.out.println(token);
        // 解析令牌
        Map<String, Object> map = JwtUtil.resolveToken(token);
        Long userId = Long.parseLong(map.get("id").toString());
        ContextHolder.setUserId(userId);
        return true;
    }

}
添加拦截器配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Resource
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册LoginInterceptor拦截器
        registry.addInterceptor(loginInterceptor);
    }

}

 封装的JWT工具
import cn.hutool.core.date.DateUtil;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.Map;


@Slf4j
public class JwtUtil {
    /**
     * 令牌密码 不少于32位
     */
    private static final String SECRET = "token_secret";

    /**
     * 令牌前缀
     */
    private static final String TOKEN_PREFIX = "Bearer";

    /**
     * 令牌过期时间
     */
    private static final Integer EXPIRE_SECONDS = 60 * 60 * 24 * 7;


    /**
     * 生成令牌
     */
    public static String generateToken(Map<String, Object> map) {
        String jwt = Jwts.builder()
                .setSubject("user info").setClaims(map)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setExpiration(DateUtil.offsetSecond(new Date(), EXPIRE_SECONDS))
                .compact();
        return TOKEN_PREFIX + "_" + jwt;
    }

    /**
     * 验证令牌
     */
    public static Map<String, Object> resolveToken(String token) {
        if (token == null) {
            throw new RuntimeException("令牌为空");
        }
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replaceFirst(TOKEN_PREFIX + "_", ""))
                    .getBody();
        } catch (ExpiredJwtException e) {
            log.error("JWT过期:", e);
            throw new RuntimeException("JWT过期");
        } catch (UnsupportedJwtException e) {
            log.error("不支持的JWT:", e);
            throw new RuntimeException("不支持的JWT");
        } catch (MalformedJwtException e) {
            log.error("JWT格式错误:", e);
            throw new RuntimeException("JWT格式错误");
        } catch (SignatureException e) {
            log.error("签名异常:", e);
            throw new RuntimeException("签名异常");
        } catch (IllegalArgumentException e) {
            log.error("非法请求:", e);
            throw new RuntimeException("非法请求");
        } catch (Exception e) {
            log.error("解析异常:", e);
            throw new RuntimeException("解析异常");
        }
    }

}

保存用户ID,方便以后食用(线程安全) 

public abstract class ContextHolder {

    public static ThreadLocal<Long> context = new ThreadLocal<>();

    public static void setUserId(Long userId) {
        context.set(userId);
    }

    public static Long getUserId() {
        return context.get();
    }

    public static void shutdown() {
        context.remove();
    }

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值