Как грамотно и удобно организовать аутентификацию в Spring REST Api через JWT? Сессию не хочу использовать, потому что это REST и JWT. Проверка токена должна проходить при каждом запросе, который требует аутентификации. Токен хранится или в куки или передается в хедерах(это не столь важно).
Вопрос в том, как можно сделать аутентификацию, максимально простой в использовании, что бы написать, допустим, фильтр, и забыть про это. Что бы внутри каждого метода где нужна аутентификация, не дергать что то типо getUser(). Хочется просто написать и забыть, но что бы этот юзер уже был внутри метода и с ним можно было работать.
До этого писать Api на SparkJava, там все было очень просто. Вместо стандартного Route, там где надо было, возвращал самописный SecuredRoute, который в свою очередь имплементился от стандартного Route, и переопределял его стандартный метод handle, в котором была написана логика проверки авторизации, и если юзер авторизован, то дальше передавался handleSecured, в параметрах которого уже был сам юзер.
Код для наглядности, который был на 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);
}
Можно ли что-либо подобное сделать на Spring?
Мне кажется что в идеале было бы создать какую то свою аннотацию, которая ставилась бы перед методом, в котором нужна авторизация, и как то инжектила этого юзера в метод
Итак, после продолжительных поисков, в интернете было найдено пара статей, на основе которых удалось сделать то что хотелось. Получилось добавить немного "магии" в Spring.
Я не хотел использовать Spring Security, потому что мне показалось не очень удобных прятать все урлы, доступ к которым есть только у авторизованных пользователей, за общий, например "/sicret/**" поэтому пришлось написать небольшой кусок этого Security самому.
Просто оставлю это здесь, вдруг у кого то будет такая же потребность.
Аутентификация происходит "за кулисами" с помощью аннотации, в которой достается jwt из куки или из хедеров. Происходит парсинг этого jwt, достается юзер и "прокидывается" внутрь метода, в котором нужен авторизованный пользователь. В случае если все ок, то есть токен был распаршен и юзер получен, то он "прокидывается" в метод, если же что-то пошло не так, то выбрасываем свое исключение, которое потом будем обрабатывать в глобальном обработчике эксепшенов, и возвращать JSON ответ вместо стандартного респонса в виде html страницы.
Теперь сам код
Аннотация. Никакие параметры в нее передаваться не будут
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthUser {
}
"Обработчик" аннотации. Оставлю только основной код, так как логика обработки токена и получения юзера здесь не столь важна
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;
}
}
Регистрация обработчика
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthUserHandlerMethodArgumentResolver());
}
}
Контроллер
@RequestMapping(value = EndPoints.AUTH_CHECK_AUTHORIZED_WITH_COOKIE, method = RequestMethod.GET)
public Response getUserInfo(@AuthUser User user) {
// здесь уже можно работать с юзером как с аутентифицированным
}
Глобальный обработчик эксепшенов
@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);
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Я создаю приложение, которое распознает текст из фото, У меня почти всё готово, но приложение распознает весь участок изображения, Мне необходимо...
Строка лишь один раз возвращает отрицательное числоЧего не хватает чтобы возвращала при каждом вводе отрицательно числа в if (ln < 0 );
Пытаюсь реализовать Ajax Long polling в своем проекте вот скрин структуры: