大数跨境
0
0

SpringBoot中Controller接口参数这样处理太优雅了

SpringBoot中Controller接口参数这样处理太优雅了 Spring全家桶实战案例
2024-06-29
1
导读:SpringBoot中Controller接口参数这样处理太优雅了

环境:SpringBoot3.2.5



1. 简介

在前后端分离的应用中,JWT token作为用户身份验证的关键手段,通常需要在后端解析以获取用户信息。虽然使用过滤器或拦截器结合ThreadLocal可以方便地保存和获取用户信息,但在某些场景下,我们可能希望直接在Controller的参数中直接获取这些信息,以减少代码的冗余和复杂性。

为此,我们可以利用Spring框架提供的自定义HandlerMethodArgumentResolver功能。通过实现这一接口,在控制器方法执行前自动从请求中解析JWT token,并将提取的用户信息作为参数直接传递给控制器方法。这种方式不仅简化了代码,还提高了代码的可读性和可维护性。接下来我将详细的介绍自定义HandlerMethodArguemntResolver的使用。

2. 实战案例

2.1 准备环境

public class Users {  private String id ;  /**用户名*/  private String username ;  /**密码*/  private String password ;  /**身份证*/  private String idNo ;  // getters, setters}

一个简单的用户实体对象,接下来写Service,该Service提供简单的登录和查询功能

@Servicepublic class UsersService {
  // 内存用户 private static final List<Users> USERS = List.of(new Users("1", "admin", "123123", "111111"),      new Users("2", "guest", "123456", "222222"));  // 密钥 private static final String SECRET = "aaaabbbbccccdddd" ;  // 登录 public String login(String username, String password) { Optional<Users> optionalUser = USERS.stream().filter(user -> user.getUsername().equals(username) && user.getPassword().equals(password)) .findFirst() ; if (optionalUser.isPresent()) { Users user = optionalUser.get() ; Map<String, Object> claims = new HashMap<>(); claims.put("id", user.getId()) ; String token = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, SECRET).compact() ; return token ; } return null ; }  // 从当前的请求中获取token,接着查找对应的用户信息 public Users getUser() { HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest() ; String token = request.getHeader(HttpHeaders.AUTHORIZATION) ; token = token.replace("Bearer ", "") ; Claims body = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody() ; String id = (String) body.get("id") ; return USERS.stream().filter(user -> id.equals(user.getId())).findFirst().orElseGet(() -> null) ; }
}

上面的Service非常的简单,验证用户 生成token / 根据token查询用户

2.2 定义Controller接口

@RestController@RequestMapping("/users")public class UsersController {
private final UsersService usersService ; public UsersController(UsersService usersService) { this.usersService = usersService ; }
@GetMapping("/login") public String login(String username, String password) { return this.usersService.login(username, password) ; }}

该Controller目前只有一个登录方法,先验证能正确登录&返回token。

接口正常返回了Token信息。接下来就是重点了,如何根据每次请求中携带的该token获取对应的用户信息!

这里我期望的是在Controller接口参数上能够通过一个注解就能读取到当前登录的用户信息,而如果只是想获取某个字段值比如id,idNo,那么通过某种表达式能自动的从当前用户中解析获取,如下接口形式:

// 获取当前用户的完整信息@GetMapping("get")public Users get(@TokenPrincipal Users user) {  return user ;}// 获取当前用户的username信息@GetMapping("username")public String username(@TokenPrincipal(expression = "username") String username) {  return username ;}

要实现上面的方式,我们就只能通过自定义HandlerMethodArgumentResolver来实现参数的解析。

2.3 自定义注解

@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface TokenPrincipal {
String expression() default "";
}

该注解用于方法参数或者注解类上,同时定义了expression属性,该属性用来设置SpEL表达式。在Spring中通过SpEL表达式能非常方法的进行属性,方法,Bean对象的访问。如需要深入学习SpEL表达式,请查看下面这篇文章

玩转Spring表达式语言SpEL:在项目实践中与AOP的巧妙结合

2.4 自定义参数解析器

public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
private ExpressionParser parser = new SpelExpressionParser();
private final UsersService usersService ; public TokenArgumentResolver(UsersService usersService) { this.usersService = usersService ;  } @Override public boolean supportsParameter(MethodParameter parameter) {    // 参数上有TokenPrincipal注解的才会被该解析器处理 return findMethodAnnotation(TokenPrincipal.class, parameter) != null;  }  // 参数解析 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 1.获取用户 Object principal = this.usersService.getUser() ; if (principal == null) { return null ; } TokenPrincipal annotation = findMethodAnnotation(TokenPrincipal.class, parameter) ; String expressionToParse = annotation.expression() ; // 2.如果设置了表达式则进行SpEL配置 if (StringUtils.hasLength(expressionToParse)) {      StandardEvaluationContext context = new StandardEvaluationContext();      context.setRootObject(principal) ; Expression expression = this.parser.parseExpression(expressionToParse) ; principal = expression.getValue(context) ; } // 3.判断类型是否相同 if (principal != null && !ClassUtils.isAssignable(parameter.getParameterType(), principal.getClass())) { return null ; } return principal ; }
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {    // 这里就是查找当前参数上是否有TokenPrincipal注解  }}

该参数解析器还是比较简单的,只处理那些参数上添加了@TokenPrincipal注解的

接下来就要将该解析器注册到参数解析器集合中

@Componentpublic class TokenWebMvcConfig implements WebMvcConfigurer {
private final UsersService usersService ; public TokenWebMvcConfig(UsersService usersService) { this.usersService = usersService ;  } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new TokenArgumentResolver(this.usersService)) ;  }}

以上就完成了所有需要的步骤及类,接下来进行测试

2.5 测试

获取用户完整信息

获取用户名username

通过自定义HandlerMethodArgumentResolver确实能够简化在Controller方法中获取用户信息的操作。然而,对于需要在多个组件或服务中共享用户信息的情况,结合使用ThreadLocal来保存当前用户信息仍然是最有效的策略。你可以通过过滤器或拦截器解析出用户信息存入ThreadLocal,而自定义的HandlerMethodArgumentResolver从ThreadLocal中获取用户。

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

必须掌握的SpringBoot开发技能

强大!基于SpringBoot实现AOP的动态热插拔功能

还记得Spring这2个强大的配置类吗?

Java高级开发必须掌握JMH进行性能测试优化

Java开发人员必须掌握的11种干净代码最佳实践

OpenFeign新功能,终于支持了这2项功能

这才是SpringBoot统一响应和异常处理的最佳实践

请一定掌握SpringBoot这4个高级开发技巧

紧急!Spring Boot安全漏洞

SpringBoot非常实用的一个功能,你肯定不知道

玩转Redis!非常强大的Redisson分布式集合,少写60%代码

SpringBoot多租户3种架构实现方案详解

必须掌握SpringBoot强大的自定义属性配置

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读195
粉丝0
内容832