How to correctly and conveniently organize authentication in Spring REST Api through JWT? I do not want to use the session because it is REST and JWT. Verification of the token should take place with every request that requires authentication. The token is either stored in cookies or transmitted in headers (this is not so important).

The question is how to make authentication as easy as possible to use, what to write, say, a filter, and forget about it. What would be inside each method where authentication is needed, do not pull something like getUser (). I just want to write and forget, but that this user would already be inside the method and could work with him.

Before writing Api on SparkJava, everything was very simple there. Instead of the standard Route, where it was necessary, I returned the SecuredRoute samopisny, which in turn was implemented from the standard Route, and redefined its standard handle method, in which the authorization check logic was written, and if the user is authorized, the handleSecured was passed on, in which parameters already was the user himself.

The code for clarity that was on spark

public interface SecuredRoute extends Route { @Override default Object handle(Request request, Response response) { // Логика проверки... if (юзер не прошел провеку) { response.removeCookie("/", "accessToken"); return new ErrorResponse(response).errorResponseUnauthenticated(); } return handleSecured(request, response, user); } Object handleSecured(Request request, Response response, User user); } 

Is it possible to do something like this on Spring?

It seems to me that, ideally, it would be to create some kind of annotation that would be put in front of the method in which authorization is needed, and how that injection of this user into the method

    1 answer 1

    So, after a long search, a couple of articles were found on the Internet, on the basis of which we managed to do what we wanted. It turned out to add a little "magic" in Spring.

    I didn’t want to use Spring Security, because it seemed to me that it’s not very convenient to hide all URLs that only authorized users have access to for, for example, “/ sicret / **”, so I had to write a small piece of this Security myself.

    Just leave it here, suddenly someone will have the same need.

    Authentication occurs behind the scenes using annotation, in which jwt from cookies or from headers gets. There is a parsing of this jwt, the user gets and "prokakyvaetsya" inside the method in which you need an authorized user. If everything is OK, that is, the token has been parsed and the user is received, then it is "thrown" into the method, but if something went wrong, then we throw out our exception, which we will then process in the global exit handler, and return the JSON response instead of the standard response in the form of an html page.

    Now the code itself

    Annotation. No parameters will be passed to it.

     @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AuthUser { } 

    "Handler" annotations. I will leave only the main code, since the logic of processing the token and getting the user is not so important here

     public class AuthUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterAnnotation(AuthUser.class) != null && methodParameter.getParameterType().equals(User.class); } @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { if (this.supportsParameter(methodParameter)) { // Получаем токен из куки или хедера, парсим его if (с юзером все ок) { return user; } else { throw new FailedAuthenticatedException(); } } return WebArgumentResolver.UNRESOLVED; } } 

    Handler registration

     @Configuration @EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new AuthUserHandlerMethodArgumentResolver()); } } 

    Controller

     @RequestMapping(value = EndPoints.AUTH_CHECK_AUTHORIZED_WITH_COOKIE, method = RequestMethod.GET) public Response getUserInfo(@AuthUser User user) { // здесь уже можно работать с юзером как с аутентифицированным } 

    Global Exception Handler

     @ControllerAdvice public class GlobalHandlerExceptionControllerAdvice { @ExceptionHandler({Exception.class}) public Response databaseError(Exception exception, HttpServletResponse response) { if (exception instanceof FailedAuthenticatedException) { // сюда попадаем в том случае, если ловим наш эксепшен, то есть, когда юзер не аутентифицирован. И возвращаем наш response в виде json в качестве ошибки return new ErrorResponse(ErrorResponse.Error.AUTHENTICATION_ERROR); } // в остальных случаях возвращаем сообщение об ошибке return new ErrorResponse(ErrorResponse.Error.INTERNAL_ERROR); } }