2
\$\begingroup\$

I developed a Spring RESTful service that uses a JWT for authorization. To the validity of this JWT, i used two different implementations.

  1. Create a filter to intercept every request and validate request, if invalid then reject it.
  2. Create a annotation to be used in controller methods, this approach is explained below.

The JWT contains user id which is used to identify authenticated user, but if i used a filter, i have to decode this JWT again in controller method, because of this problem i used second implementation.

The question is about annotation approach.

  • Is this a good implementation to JWT validation ?
  • How can i improve this code ?

Here's the code,

Annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Authorize {
    String[] value();
}

Implementation of the annotation

public class MethodAccessResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.getMethodAnnotation(Authorize.class) != null) && (parameter.getParameterName().equals("jwt"));
    }

    @Override
    public Object resolveArgument(
        MethodParameter parameter,
        ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest,
        WebDataBinderFactory binderFactory
    ) throws Exception {
        String[] allowedUsers = parameter.getMethodAnnotation(Authorize.class).value();
        JwtProperties jwt = JwtUtil.validate(webRequest.getHeader("Authorization"));
        String currentUser = jwt.getScope().get(0);

        int no = 0;
        for (String allowedUser : allowedUsers) {
            if (!allowedUser.equals(currentUser)) {
                no++;
            }
        }

        if (no == allowedUsers.length) {
            throw new UnauthorizedAccessException("Un-authorized access", 0xd1561);
        } else {
            return jwt;
        }
    }
}

Controller method - where the annotation is used

@GetMapping(
        path = "/api/event/post",
        produces = MediaType.APPLICATION_JSON_VALUE
)
@Authorize(value = {UserSupport.USER, UserSupport.ADMIN, UserSupport.END_USER})
public ResponseEntity<?> getLiveLocations(
        HttpServletRequest request,
        JwtProperties jwt
) throws
        MongoException,
        ParseException,
        JwtException,
        UnsupportedEncodingException,
        ResourceNotFoundException,
        UnauthorizedAccessException {
    //do something
}

An Exception is thrown if token is invalid or the user role contains in token is not allowed to access particular URL.

If token is validated then user details will be injected to method parameter JwtProperties jwt.

\$\endgroup\$
1
\$\begingroup\$
  • I'd prefer the filter, just because then you don't have to worry about a missed annotation leaving your API wide open. I never found decoding JWTs to be expensive, and I suspect you're performing premature optimization.

  • value isn't especially descriptive. Perhaps @Authorize(users= would be better.

  • Your code can be simplified by returning early if a match is found, rather than counting failures and comparing them:

    final JwtProperties jwt = JwtUtil.validate(webRequest.getHeader("Authorization"));
    final String currentUser = jwt.getScope().get(0);
    
    for (final String allowedUser : parameter.getMethodAnnotation(Authorize.class).value()) {
        if (allowedUser.equals(currentUser)) {
            return jwt;
        }
    }
    
    throw new UnauthorizedAccessException("Un-authorized access", 0xd1561);
    
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ appreciate your recommendations sir. \$\endgroup\$ – benjamin c Jul 6 '18 at 16:26
  • \$\begingroup\$ I use JWT with JWE, that's why i thought decoding could be an expensive task and has to process a lot of requests. \$\endgroup\$ – benjamin c Jul 6 '18 at 16:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.