I'm new to Spring development and I use Spring Security for JWT authentication in my application.
It is already configured and works fine, but the only messy thing is unpacking the Principal in each API request mapping. I only encode the user UUID in a JWT payload, but I need the entire User
entity fetched from database in each request mapping.
Currently my code looks like:
@GetMapping("/something")
public SomeResponse someMethod(Authentication authentication) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
MyUserEntity user = userService.findByUuid(userDetails.getUuid());
// ...
}
But I want to create some kind of a middleware so I'll be able to call findByUuid
before the controller receives the request and then pass the entity to Spring to inject it, so the mapping code will look like:
@GetMapping("/some")
public SomeResponse someMethod(MyUserEntity user) {
// ...
}
I've searched for the same problem and the only idea I found was creating a filter which performs the user lookup by their UUID and setting the request attribute:
@Component
public class UserFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute("user", new User("Jerry"));
filterChain.doFilter(request, response);
}
}
And then injecting the WebRequest
into each mapping:
@GetMapping("/current-user")
public String getCurrentUser(WebRequest request) {
var user = (User) request.getAttribute("user", WebRequest.SCOPE_REQUEST);
return user.getUsername();
}
But it still doesn't look like a good approach as it forces me to repeat the same line for each of my 50 API methods.
Is there a way to manipulate the arguments injected to a request mapping by Spring?
CodePudding user response:
Thanks to @M.Deinum, I was able to set up my own HandlerMethodArgumentsResolver
component which can provide the required argument:
@Component
@RequiredArgsConstructor
public class AuthenticatedUserArgumentResolver implements HandlerMethodArgumentResolver {
private final UserService userService;
@Override
public boolean supportsParameter(@NonNull MethodParameter parameter) {
return parameter.getParameterType().equals(MyUserEntity.class);
}
@Override
public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewContainer mavContainer, @NonNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal();
return userService.findByUuid(userDetails.getUuid());
}
}
And use it as expected:
@GetMapping("/some")
public SomeResponse someMethod(MyUserEntity user) {
// ...
}