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

目录
  • 一、最原始直接
  • 二、AOP
  • 三、拦截器+方法参数解析器
    • 3.1 自定义权限拦截器
    • 3.2 自定义参数注解
    • 3.3 自定义方法参数解析器
    • 3.4 配置MVC
  • 总结
  • 环境: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,以及今天的主角HandlerMethodArgumentResolver

HandlerMethodArgumentResolver是用来处理方法参数的解析器,包含以下2个方法:

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

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

  • 自定义权限拦截器AuthenticationInterceptor拦截所有request请求,并将token解析为currentUser,最终放到request中;
  • 自定义参数注解@CurrentUser,添加至controller的方法参数user之上;
  • 自定义方法参数解析器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);
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • SpringMVC实现controller中获取session的实例代码

    平时使用springMVC,在方法中访问session中经常很自然地调用Servlet API.用起来非常直观方便,一直没有多考虑什么. 比如这样: @RequestMapping(value = "/logout") public String logout(HttpSession session) { session.removeAttribute("user"); return "/login"; } 但毕竟这样对Servlet API产生

  • SpringBoot之HandlerInterceptor拦截器的使用详解

    前言 平常项目开发过程中,会遇到登录拦截,权限校验,参数处理,防重复提交等问题,那拦截器就能帮我们统一处理这些问题. 一.实现方式 1.1 自定义拦截器 自定义拦截器,即拦截器的实现类,一般有两种自定义方式: 定义一个类,实现org.springframework.web.servlet.HandlerInterceptor接口. 定义一个类,继承已实现了HandlerInterceptor接口的类,例如org.springframework.web.servlet.handler.Handle

  • 详解如何在SpringBoot中自定义参数解析器

    目录 前言 1.自定义参数解析器 2.PrincipalMethodArgumentResolver 3.RequestParamMapMethodArgumentResolver 4.小结 前言 在一个 Web 请求中,参数我们无非就是放在地址栏或者请求体中,个别请求可能放在请求头中. 放在地址栏中,我们可以通过如下方式获取参数: String javaboy = request.getParameter("name "); 放在请求体中,如果是 key/value 形式,我们可以通

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

    目录 一.最原始直接 二.AOP 三.拦截器+方法参数解析器 3.1 自定义权限拦截器 3.2 自定义参数注解 3.3 自定义方法参数解析器 3.4 配置MVC 总结 环境:SpringBoot 2.0.4.RELEASE 需求:很多Controller方法,刚进来要先获取当前登录用户的信息,以便做后续的用户相关操作. 准备工作:前端每次请求都传token,后端封装一方法tokenUtils.getUserByToken(token),根据token解析得到currentUserInfo. 这是

  • Springboot+Shiro记录用户登录信息并获取当前登录用户信息的实现代码

    由于最近做项目需要,在用户登陆后有一个功能是需要用户的信息,进行写入数据库的操作.但是目前还用不到Shiro的高级权限,只为了简单获取用户信息,自己整合了一个只记录用户,获取用户信息的功能. 导入Shiro依赖 <!-- Shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version

  • 使用spring oauth2框架获取当前登录用户信息的实现代码

    使用spring oauth2框架做授权鉴定.想获取当前用户信息怎么办? 我们知道spring oauth2是基于spring security的实现的. spring security可以通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()获取到当前用户信息. 而spring oauth2通过SecurityContextHolder.getContext().getAuthentication().ge

  • 详解.NET 6如何实现获取当前登录用户信息

    目录 需求 目标 原理和思路 实现 创建当前用户获取接口 实现接口功能 使用功能 验证 总结 需求 在前面的文章里使用.NET 6开发TodoList应用之领域实体创建原理和思路,我们留了一个坑还没有填上,就是在数据库保存的时候,CreateUser和ModifiedUser我们当时填的都是Anonymous,完成认证的功能后,现在我们需要实现在保存数据库的时候填入当前登陆进行操作的用户名. 目标 实现当前登陆用户信息获取. 原理和思路 原理很简单,在认证时拿到的Token里,payload中是

  • 详解Spring Security中获取当前登录用户的详细信息的几种方法

    目录 在Bean中获取用户信息 在Controller中获取用户信息 通过 Interface 获取用户信息 在JSP页面中获取用户信息 在Bean中获取用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (!(authentication instanceof AnonymousAuthenticationToken)) { String currentU

  • 微信小程序获取手机号授权用户登录功能

    小程序中有很多地方都会用到注册用户信息的地方,用户需要填写手机号等,有了这个组件可以快速获取微信绑定手机号码,无须用户填写. 1.getPhoneNumber这个组件通过button来实现(别的标签无效).将button中的open-type="getPhoneNumber",并且绑定bindgetphonenumber事件获取回调. <span style="font-size:14px;"><button open-type="get

  • sql server 2008 数据库管理系统使用SQL语句创建登录用户步骤详解

    废话不多说了,直接给大家贴代码了,具体代码如下所示: --服务器角色: --固定服务器角色具有一组固定的权限,并且适用于整个服务器范围. 它们专门用于管理 SQL Server,且不能更改分配给它们的权限. --可以在数据库中不存在用户帐户的情况下向固定服务器角色分配登录. --按照从最低级别的角色(bulkadmin)到最高级别的角色(sysadmin)的顺序进行描述: --1.Bulkadmin:这个服务器角色的成员可以运行BULKINSERT语句.这条语句允许从文本文件中将数据导入到SQL

  • 详解springmvc控制登录用户session失效后跳转登录页面

    springmvc控制登录用户session失效后跳转登录页面,废话不多少了,具体如下: 第一步,配置 web.xml <session-config> <session-timeout>15</session-timeout> </session-config> 第二步,配置spring-mvc.xml <!-- Session失效拦截 --> <mvc:interceptors> <!-- 定义拦截器 --> <

  • application作用域实现用户登录挤掉之前登录用户代码

    一.实现思想 1.application(ServletContext)是保存在服务器端的作用域,我们在application中保存两种形式的键值对:1:<userId, sessionId>,2:<sessionId, session> 2.每当一个用户登录时(将生成一个新的session),首先根据userId在application中查询sessionId: 如果没有查询到sessionId,说明还没有用户登录此账号,那么将<userId, sessionId>和

  • 微信小程序如何获取openid及用户信息

    微信小程序获取openid及用户信息的方法 1. 获取openid 1.1 获取code 调用接口获取登录凭证(code)进而换取用户登录态信息,包括用户的唯一标识(openid) 及本次登录的会话密钥(session_key).用户数据的加解密通讯需要依赖会话密钥完成. wx.login({ //获取code success: function(res) { code = res.code //返回code } }) 1.2 获取openid 拿到上一步获取的code,结合小程序 appid

随机推荐