Skip to content

Create RESTful web service for current logged user in Spring Boot

I have URL for user dashboard(/users/{userId}).

I wanted to support /users/current for current logged user, and I searched for ways to implement that, and I did that by the following code.

However, I think it’s too overwhelming and I wonder if there are better/simpler ways to do that.

@Controller
@RequestMapping("/users/{target}")
public class UserController {

    @GetMapping
    public String get(@PathVariable User target) {
        return "dashboard";
    }

    @PostMapping
    public String put(@PathVariable User target, ...) {
        ...
    }

    @GetMapping("licenses")
    public String getLicenses(@PathVariable User target, ...) {
        ...
    }

    ...
}
@Configuration
@RequiredArgsConstructor
public class MethodHandlersConfig {

    private final RequestMappingHandlerAdapter adapter;

    @PostConstruct
    public void prioritizeCustomArgumentMethodHandlers() {
        List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(adapter.getArgumentResolvers());
        List<HandlerMethodArgumentResolver> customResolvers = adapter.getCustomArgumentResolvers();
        argumentResolvers.removeAll(customResolvers);
        argumentResolvers.addAll(0, customResolvers);
        adapter.setArgumentResolvers(argumentResolvers);
    }
}
@Component
@RequiredArgsConstructor
public class UserResolver extends PathVariableMethodArgumentResolver {

    private final UserService userService;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return User.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    @SuppressWarnings("rawtypes")
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        User user;
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
        Map path = (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String target = (String) path.get(name);
        if (target.equals("current")) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth == null) {
                throw new InsufficientAuthenticationException("User is not authenticated");
            }
            user = (User) auth.getPrincipal();
        } else {
            user = userService.loadUserByUsername(target);
        }
        request.setAttribute("username", target, RequestAttributes.SCOPE_REQUEST);
        request.setAttribute("user", user, RequestAttributes.SCOPE_REQUEST);
        return user;
    }
}

P. S. I think I made a much better solution with custom annotation (@UserPath)

@Component
@RequiredArgsConstructor
public class UserResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor {

    private final UserService userService;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(UserPath.class);
    }

    @Override
    protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
        UserPath annotation = parameter.getParameterAnnotation(UserPath.class);
        return new NamedValueInfo(annotation.name(), true, ValueConstants.DEFAULT_NONE);
    }

    @SuppressWarnings("unchecked")
    protected String getValue(String name, WebRequest request) {
        Map<String, String> vars = (Map<String, String>) request.getAttribute(
                HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        return vars.get(name);
    }

    @Override
    @Nullable
    protected User resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        UserPath annotation = parameter.getParameterAnnotation(UserPath.class);

        String userObj = getValue(name, request);
        User current = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (userObj.equals("current")) {
            return current;
        } else {
            User found = userService.loadUserByUsername(userObj);
            if (annotation.admin() &&
                    !current.getId().equals(found.getId()) &&
                    !current.getAuthorities().contains(UserAuthority.ROLE_ADMIN)) {
                throw new InsufficientAuthenticationException("Admin permission is required");
            }
            return found;
        }
    }

    @Override
    protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
        User user = (User) arg;
        String userObj = getValue(name, webRequest);
        mavContainer.addAttribute("user", user);
        mavContainer.addAttribute("username", userObj);
    }

    @Override
    public void contributeMethodArgument(MethodParameter parameter, Object value,
                                         UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
        uriVariables.put(parameter.getParameterAnnotation(UserPath.class).value(), value.toString());
    }
}

Answer

maybe custom annotation with AbstractNamedValueMethodArgumentResolver would be the best solution. Read P.S of the question for more details.