0

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());
    }
}
Toshimichi
  • 46
  • 1
  • 7
  • try keeping a session cookies/ tokens (save user info there) and when u visit users/current read the session get the userid and preview the relevant user profile – namila007 Dec 31 '21 at 21:55
  • can you please provide me an example? – Toshimichi Jan 01 '22 at 08:10
  • 1
    There are no `RESTful URLs`! URIs/URLs are just a sequence of characters that as a whole act as a pointer to a resource. The URI itself is i.e. not enough to determine whether a remote service adheres to RESTful principles and a single URI can't state whether its path segments form a parent-child relationship or not. URIs/URLs shouldn't be used to convey meaning from server to clients which the latter ones attempt to extract as the server is free to change its URIs/URLs anytime it sees fits. If so, link relation names should be used to convey the intention of the URI – Roman Vottner Jan 01 '22 at 08:31
  • @Toshimichi check about JWT tokens or session cookies. all you have to do is create a token or cookie and save userID or some unique user variable. once you visit the users/current read the cookie/token extract id and retrieve the data from ur apis – namila007 Jan 02 '22 at 10:09
  • Sorry but I think you are misunderstanding the question. My question is, how I can implement "/users/current" endpoint. Also, this is not Rest API. Please check this question: https://stackoverflow.com/questions/36520372/designing-uri-for-current-logged-in-user-in-rest-applications – Toshimichi Jan 02 '22 at 14:42

1 Answers1

0

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

Toshimichi
  • 46
  • 1
  • 7