HandlerMethodArgumentResolver用于统一获取当前登录用户


  • 环境:SpringBoot 2.0.4.RELEASE
  • 需求:很多Controller方法,刚进来要先获取当前登录用户的信息,以便做后续的用户相关操作。
  • 准备工作:前端每次请求都传token,后端封装一方法tokenUtils.getUserByToken(token),根据token解析得到currentUserInfo。

这是一个常见的业务需求,为实现这个需求,有以下几种解决方案:

一、最原始直接

即,每个Controller开始,先调用tokenUtils.getUserByToken(token),不够优雅。

二、AOP

AOP可以解决很多切面类问题,思路同Spring AOP来自定义注解实现审计或日志记录(完整代码),将currentUser放到request里;比起拦截器稍重。

三、拦截器+方法参数解析器

使用mvc拦截器HandlerInterceptor+方法参数解析器HandlerMethodArgumentResolver最合适。

SpringMVC提供了mvc拦截器HandlerInterceptor,包含以下3个方法:

  • preHandle
  • postHandle
  • afterCompletion

HandlerInterceptor经常被用来解决拦截事件,如用户鉴权等。另外,Spring也向我们提供了多种解析器Resolver,如用来统一处理异常的HandlerExceptionResolver,以及今天的主角HandlerMethodArgumentResolverHandlerMethodArgumentResolver是用来处理方法参数的解析器,包含以下2个方法:

  • supportsParameter(满足某种要求,返回true,方可进入resolveArgument做参数处理)
  • resolveArgument

 知识储备已到位,接下来着手实现,主要分为三步走:

  1. 自定义权限拦截器AuthenticationInterceptor拦截所有request请求,并将token解析为currentUser,最终放到request中;
  2. 自定义参数注解@CurrentUser,添加至controller的方法参数user之上;
  3. 自定义方法参数解析器CurrentUserMethodArgumentResolver,取出request中的user,并赋值给添加了@CurrentUser注解的参数user。

 3.1 自定义权限拦截器

自定义权限拦截器AuthenticationInterceptor,需实现HandlerInterceptor。在preHandle中调用tokenUtils.getUserByToken(token),获取到当前用户,最后塞进request中,如下:

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

import edp.core.utils.TokenUtils;

import edp.core.consts.Consts;

import edp.davinci.model.User;

public class AuthenticationInterceptor implements HandlerInterceptor {

@Autowired

private TokenUtils tokenUtils;

@Override

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

String token = request.getHeader("Authorization");

User user = tokenUtils.getUserByToken(token);

request.setAttribute(Consts.CURRENT_USER, user);

return true;

}

@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 {

}

}

 3.2 自定义参数注解

自定义方法参数上使用的注解@CurrentUser,代表被它注解过的参数的值都需要由方法参数解析器CurrentUserMethodArgumentResolver来“注入”,如下:

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 自定义 当前用户 注解

* 注解 参数

* 此注解在验证token通过后,获取当前token包含用户

*/

@Target({ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

public @interface CurrentUser {

}

给各Controller类中RequestMapping方法的参数添加此注解,如下:

import org.springframework.http.MediaType;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.*;

import edp.davinci.core.common.Constants;

import edp.core.annotation.CurrentUser;

import javax.servlet.http.HttpServletRequest;

@RestController

@RequestMapping(value = Constants.BASE_API_PATH + "/download")

public class DownloadController {

@GetMapping(value = "/page", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

public ResponseEntity getDownloadRecordPage(@CurrentUser User user, HttpServletRequest request) {

...

}

}

 3.3 自定义方法参数解析器

自定义方法参数解析器CurrentUserMethodArgumentResolver,需实现HandlerMethodArgumentResolver

import org.springframework.core.MethodParameter;

import org.springframework.web.bind.support.WebDataBinderFactory;

import org.springframework.web.context.request.NativeWebRequest;

import org.springframework.web.context.request.RequestAttributes;

import org.springframework.web.method.support.HandlerMethodArgumentResolver;

import org.springframework.web.method.support.ModelAndViewContainer;

import edp.core.annotation.CurrentUser;

import edp.core.consts.Consts;

import edp.davinci.model.User;

/**

* @CurrentUser 注解 解析器

*/

public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {

@Override

public boolean supportsParameter(MethodParameter parameter) {

    return parameter.getParameterType().isAssignableFrom(User.class)

&& parameter.hasParameterAnnotation(CurrentUser.class);

}

@Override

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

    return (User) webRequest.getAttribute(Consts.CURRENT_USER,     RequestAttributes.SCOPE_REQUEST);

}

}

As we all know,拦截器定义好以后,在SpringMVC项目中,需要去SpringMVC的配置文件springmvc.xml添加该拦截器;但是在SpringBoot中,省去了很多配置文件,取而代之的是被注解@Configuration标识的配置类,SpringMVC配置文件对应的配置类需继承WebMvcConfigurationSupport。同理,解析器定义好以后,也需被添加到SpringMVC的配置文件或配置类中。最后,额外的一步,配置mvc。

 3.4 配置MVC

定义MVC配置类,需继承WebMvcConfigurationSupport。分别在addInterceptors和addArgumentResolvers方法中,添加自定义的拦截器和参数解析器,如下:

import static edp.core.consts.Consts.EMPTY;

import java.math.BigInteger;

import java.util.ArrayList;

import java.util.List;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.MediaType;

import org.springframework.http.converter.HttpMessageConverter;

import org.springframework.web.method.support.HandlerMethodArgumentResolver;

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 com.alibaba.fastjson.serializer.SerializerFeature;

import com.alibaba.fastjson.serializer.ValueFilter;

import com.alibaba.fastjson.support.config.FastJsonConfig;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;

import edp.davinci.core.common.Constants;

import edp.davinci.core.inteceptor.AuthenticationInterceptor;

import edp.davinci.core.inteceptor.CurrentUserMethodArgumentResolver;


@Configuration

public class WebMvcConfig extends WebMvcConfigurationSupport {

    @Value("${file.userfiles-path}")

    private String filePath;

/**

* 登录校验拦截器

*

* @return

*/

@Bean

public AuthenticationInterceptor loginRequiredInterceptor() {

    return new AuthenticationInterceptor();

}

/**

* CurrentUser 注解参数解析器

*

* @return

*/

@Bean

public CurrentUserMethodArgumentResolver currentUserMethodArgumentResolver() {

    return new CurrentUserMethodArgumentResolver();

}

/**

* 参数解析器

*

* @param argumentResolvers

*/

@Override

protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {

    argumentResolvers.add(currentUserMethodArgumentResolver());

    super.addArgumentResolvers(argumentResolvers);

}

@Override

protected void addInterceptors(InterceptorRegistry registry) {

    registry.addInterceptor(loginRequiredInterceptor())

    .addPathPatterns(Constants.BASE_API_PATH + "/**")

    .excludePathPatterns(Constants.BASE_API_PATH + "/login");

    super.addInterceptors(registry);

}

@Override

public void addResourceHandlers(ResourceHandlerRegistry registry) {

    registry.addResourceHandler("/**")

    .addResourceLocations("classpath:/META-INF/resources/")

    .addResourceLocations("classpath:/static/page/")

    .addResourceLocations("classpath:/static/templates/")

    .addResourceLocations("file:" + filePath);

}

@Override

protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

    FastJsonConfig fastJsonConfig = new FastJsonConfig();

    fastJsonConfig.setSerializerFeatures(SerializerFeature.QuoteFieldNames,

    SerializerFeature.WriteEnumUsingToString,

    SerializerFeature.WriteMapNullValue,

    SerializerFeature.WriteDateUseDateFormat,

    SerializerFeature.DisableCircularReferenceDetect);

    fastJsonConfig.setSerializeFilters((ValueFilter) (o, s, source) -> {

    if (null != source && (source instanceof Long || source instanceof BigInteger) &&         source.toString().length() > 15) {

        return source.toString();

    } else {

        return null == source ? EMPTY : source;

    }

});

//处理中文乱码问题

List<MediaType> fastMediaTypes = new ArrayList<>();

    fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);

    fastConverter.setSupportedMediaTypes(fastMediaTypes);

    fastConverter.setFastJsonConfig(fastJsonConfig);

    converters.add(fastConverter);

    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值