SpringBoot拦截器获取token用户对象优雅地传递到Controller层

本文介绍了如何在SpringBoot应用中通过拦截器和ThreadLocal存储用户信息,以避免每个接口重复解析Token,提高代码效率。同时提供了使用自定义拦截器和Aspect切面两种方式实现的示例。
摘要由CSDN通过智能技术生成

项目场景:

SpringBoot拦截器获取token用户对象优雅地传递到Controller层


问题描述

后端有许多接口都需要请求中携带有正确的Token,这时采用拦截器来验证token,但是每个接口都还是需要解析一遍token,浪费资源,不免显得代码繁琐,臃肿。


解决方案:

我们可以把用户信息存储起来,方便其它地方获取,两种方式:

  1. 使用ThreadLocal线程存储(推荐)
  2. 存储到上下文中request.setAttribute

1、编写类TokenUserInfo、UserThreadLocal类

 用户实体类

package com.test.entity;

import lombok.Data;

/**
 * 用户信息
 *
 */
@Data
public class TokenUserInfo {

    /**
     * 用户id
     */
    private String userId;
    /**
     * 登录帐号
     */
    private String loginAccount;
    /**
     * 用户名
     */
    private String loginUserName;
}

 封装ThreadLocal便于代码的维护

package com.test.aop;

import com.test.entity.TokenUserInfo;

/**
 * ThreadLocal为每个线程单独提供一份存储空间
 * 将ThreadLocal进行封装,便于代码的维护和迭代
 */
public class UserThreadLocal {
    /**
     * 存储用户信息
     */
    private static ThreadLocal<TokenUserInfo> userThread = new ThreadLocal<>();

    public static void set(TokenUserInfo tokenUserInfo) {
        userThread.set(tokenUserInfo);
    }

    public static TokenUserInfo getUser() {
        return userThread.get();
    }

    public static void remove() {
        userThread.remove();
    }
}

2、自定义拦截器Interceptor

抛出的异常需要自己捕捉,返回

package com.test.aop;

import com.test.constant.CommonConstant;
import com.test.entity.TokenUserInfo;
import com.test.exception.InvalidTokenException;
import com.test.exception.MyException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Token拦截器
 * 拦截器与UserInfoAspect切面的方式选择一种
 */
@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

		// 从header中获取token
		String token = request.getHeader(CommonConstant.ACCESS_TOKEN);

		// 如果参数中不存在token,则报错
		if (StringUtils.isBlank(token)) {
			throw new MyException("请求头缺少"+CommonConstant.ACCESS_TOKEN+"参数");
		}

		try {
            // TODO 根据token获取用户信息
            // ......
        }catch (Exception e){
            log.error("获取用户信息失败:", e);
            throw new MyException("获取用户信息失败");
        }


        //模拟已经获取到了用户信息
        TokenUserInfo tokenUserInfo = new TokenUserInfo();
        tokenUserInfo.setUserId("1227086153ef415896da5819d4fb4c2f");
        tokenUserInfo.setLoginAccount("test");
        tokenUserInfo.setLoginUserName("测试");

        //token失效
        if(tokenUserInfo == null){
            throw new InvalidTokenException(token);
        }

        /*
         * 存储用户信息方便其它地方获取,两种方式
         * 1.使用ThreadLocal线程存储(推荐)
         * 2.存储到上下文中request.setAttribute
         */

        // 放入线程域
        UserThreadLocal.set(tokenUserInfo);

        //2.存储到上下文中request.setAttribute
//        request.setAttribute(CommonConstant.USER_INFO,tokenUserInfo);

        //放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //程序运行结束之后,删除线程,防止内存泄漏
        UserThreadLocal.remove();
    }
}

3、配置拦截器

排除不需要拦截的路径

package com.test.config;

import com.test.aop.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * WebMvc配置
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
	/**
	 * 解决跨域问题
	 */
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**").allowedOrigins("*").allowCredentials(true)
				.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").maxAge(3600);
	}
	
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        registry.addResourceHandler("/swagger/**").addResourceLocations("classpath:/static/swagger/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
    }

	@Resource
	private TokenInterceptor tokenInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		//不拦截的地址
		List<String> excludedList = new ArrayList<>();
		//swagger地址
		excludedList.add("/swagger-ui.html");
		excludedList.add("/swagger-ui.html/**");
		excludedList.add("/webjars/**");
		excludedList.add("/swagger/**");
		excludedList.add("/doc.html");
		excludedList.add("/doc.html/**");
		excludedList.add("/swagger-resources/**");
		excludedList.add("/v2/**");
		excludedList.add("/favicon.ico");


		registry.addInterceptor(tokenInterceptor)
				.addPathPatterns("/**")//拦截所有请求
				.excludePathPatterns(excludedList);//排除的请求
		super.addInterceptors(registry);
	}
}

 4、Controller层获取

package com.test.controller;

import com.test.aop.UserThreadLocal;
import com.test.entity.TokenUserInfo;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@RestController
@Api(tags = "测试接口")
@RequestMapping("/test")
public class TestController {

	@RequestMapping(value = "/get", method = RequestMethod.GET)
	public String get(HttpServletRequest request) {
		/*
		 * 优雅的获取用户信息,两种方式
		 * 1.使用ThreadLocal线程存储(推荐)
		 * 2.存储到上下文中request.setAttribute
		 */

		TokenUserInfo tokenUserInfo = UserThreadLocal.getUser();
//		Object userInfo = request.getAttribute(CommonConstant.USER_INFO);
//		TokenUserInfo tokenUserInfo = userInfo != null ? (TokenUserInfo) userInfo : null;

		if(tokenUserInfo == null){
			throw new InvalidTokenException("request未获取到"+CommonConstant.USER_INFO);
		}

		return "success";
	}
}

拓展

拓展说一下使用Aspect切面的方式获取token,注意Aspect切面的顺序,@Order(较小的数):优先级高

package com.test.aop;

import com.test.constant.CommonConstant;
import com.test.entity.TokenUserInfo;
import com.test.exception.InvalidTokenException;
import com.test.exception.MyException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 用户信息切面
 * 切面与TokenInterceptor拦截器的方式选择一种
 *
 */
@Aspect
@Component
@Slf4j
public class UserInfoAspect {

    /**
     * 所有controller方法,排除getList接口。
     */
    @Pointcut("execution(* com.test..controller..*(..)) && !execution(* com.test..controller.TestController.getList(..))")
    public void userInfoPointCut() {
        // 空注释,避免sonar警告
    }

    @Around("userInfoPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    	//获取request
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = requestAttributes.getRequest();

        // 从header中获取token
        String token = request.getHeader(CommonConstant.ACCESS_TOKEN);

        // 如果参数中不存在token,则报错
        if (StringUtils.isBlank(token)) {
            throw new MyException("请求头缺少"+ CommonConstant.ACCESS_TOKEN+"参数");
        }

        try {
            // TODO 根据token获取用户信息
            // ......
        }catch (Exception e){
            log.error("获取用户信息失败:", e);
            throw new MyException("获取用户信息失败");
        }


        //模拟已经获取到了用户信息
        TokenUserInfo tokenUserInfo = new TokenUserInfo();
        tokenUserInfo.setUserId("1227086153ef415896da5819d4fb4c2f");
        tokenUserInfo.setLoginAccount("test");
        tokenUserInfo.setLoginUserName("测试");

        //token失效
        if(tokenUserInfo == null){
            throw new InvalidTokenException(token);
        }

        try {
            /*
             * 存储用户信息方便其它地方获取,两种方式
             * 1.使用ThreadLocal线程存储(推荐)
             * 2.存储到上下文中request.setAttribute
             */

            // 放入线程域
            UserThreadLocal.set(tokenUserInfo);
            //2.存储到上下文中request.setAttribute
//            request.setAttribute(CommonConstant.USER_INFO,tokenUserInfo);

            //执行方法
            return joinPoint.proceed();
//        } catch (Exception e){
//            //这里可以用catch捕捉异常,因为我配置了全局异常,所以这里就注释掉了
//            e.printStackTrace();
        } finally {
            //程序运行结束之后,删除线程,防止内存泄漏
            UserThreadLocal.remove();
        }
    }
}

 使用HandlerInterceptor拦截器与Aspect切面都能达到获取token的效果,但是Aspect切面如果要排除某个接口不是很方便,所以还是推荐HandlerInterceptor拦截器的方式

 

参考源码:https://download.csdn.net/download/u011974797/85849196

Spring Boot中,我们可以使用拦截器来验证token拦截器是一种在请求处理之前或之后拦截请求的机制。我们可以在拦截器中编写代码来验证token,并在需要时拒绝请求。为了实现这个目标,我们需要编写一个自定义的拦截器类,并将其配置到Spring Boot应用程序中。 具体步骤如下: 1. 创建一个自定义的拦截器类,例如TokenInterceptor.java。 2. 在拦截器类中编写代码来验证token。可以使用第三方库,例如JWT,来验证token。 3. 创建一个拦截器配置类,例如InterceptorConfig.java。 4. 在拦截器配置类中注册自定义拦截器,并配置拦截器的拦截路径和排除路径。 5. 在Spring Boot应用程序中启用拦截器配置。 下面是一个简单的示例代码,演示如何在Spring Boot中使用拦截器验证tokenTokenInterceptor.java: ``` public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 验证token的代码 // 如果token验证失败,可以使用response对象返回错误信息 return true; } } ``` InterceptorConfig.java: ``` @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Autowired private TokenInterceptor tokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/login"); // 排除登录请求 } } ``` 在上面的示例代码中,我们创建了一个名为TokenInterceptor的自定义拦截器类,并实现了HandlerInterceptor接口。在preHandle方法中,我们可以编写代码来验证token。如果token验证失败,可以使用response对象返回错误信息。 然后,我们创建了一个名为InterceptorConfig的拦截器配置类,并实现了WebMvcConfigurer接口。在addInterceptors方法中,我们注册了TokenInterceptor拦截器,并配置了拦截路径和排除路径。在这个例子中,我们拦截了所有请求,但排除了/login请求。 最后,在Spring Boot应用程序中启用拦截器配置。这可以通过在应用程序类上添加@EnableWebMvc注解来实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛哥是个大帅比

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值