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 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");
    }
}

 方式二  放行路径单独设置

编写拦截器

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

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

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

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

        String url = request.getRequestURI().substring(request.getContextPath().length());
        System.out.println(url);
        // 登录和注册等请求不需要令牌
        if (SAFE_URL_LIST.contains(url)) {
            return true;
        }

        // 从请求头里面读取token
        String token = request.getHeader(HEADER_AUTH);
        if (token == null) {
            throw new RuntimeException("请求失败,令牌为空");
        }

        // 解析令牌
        Map map = JwtUtil.resolveToken(token);
        Long userId = Long.parseLong(map.get("userId").toString());
        ContextHolder.setUserId(userId);
        return true;
    }

}
添加拦截器配置

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Bean
    public LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册TestInterceptor拦截器
        registry.addInterceptor(loginInterceptor());		//添加需要拦截的路径(由于设置了请求前缀,所以必须将请求前缀加上)
    }

}

封装JWT工具

针对第二种方式封装JWT工具

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 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 resolveToken(String token) {
        System.out.println(token);
        if (token == null) {
            throw new RuntimeException("令牌为空");
        }
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replaceFirst(TOKEN_PREFIX + "_", ""))
                    .getBody();
        } catch (ExpiredJwtException e) {
            throw new RuntimeException("令牌已过期");
        } catch (Exception e) {
            throw new RuntimeException("令牌解析异常");
        }
    }

}

保存用户ID,方便以后使用 

public abstract class ContextHolder {

    public static ThreadLocal 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();
    }

}

你可能感兴趣的:(JWT工具,spring,boot,java,后端)