- After adding custom filters, permitAll() does not work #4368
- Comments
- redbrickss commented Jun 4, 2017
- Summary
- Actual Behavior
- Expected Behavior
- Configuration
- Version
- Sample
- mmouterde commented Mar 12, 2018 •
- siddhiparkar151992 commented Sep 14, 2018
- siddhiparkar151992 commented Sep 14, 2018 •
- rwinch commented Sep 14, 2018
- rwinch commented Sep 14, 2018
- KevinWingi commented Jun 8, 2019 •
- davi004 commented Sep 5, 2019 •
- Kevin2iBi commented Sep 5, 2019 •
- jzheaux commented Sep 6, 2019
- Why Spring Security permitAll() is not working with OAuth2.0?
- 2 Answers 2
- Spring Security за 15 минут
- Что вы сделаете?
- Что вам понадобится?
- Как пользоваться этой статьей
- Первичная настройка Spring-Security
- WebSecurityConfigurerAdapter
- Создадим login-страницу
- Как это устроено?
- Как настроить авторизацию в Spring Security
- Подготовка
- Контроллер
- Пользователи (In-Memory athentication)
- Настройка авторизации
- Перечисление url
- Настройка доступа (роли, разрешения)
- Страница ввода пароля
- Итоги
- Как настроить авторизацию в Spring Security: 13 комментариев
After adding custom filters, permitAll() does not work #4368
Comments
redbrickss commented Jun 4, 2017
Summary
After adding custom JWT filter for authentication, all paths with permitAll() are still passing through the custom JWT filter.
So, for example if I have these URI’s
/api/users/
and
/api/users/** (other multiple ant matchers URI which are authenticated())
So, If I have a custom JWT filter with pattern matching URI as (/api/users/**) then why is this filter enabled for permitAll (/api/users/
Do I have to keep authenticated URI’s with /a/api/user/** and use the pattern for the filter?
and /api/users/** with permitAll()? Looks like a bad design to me.
Actual Behavior
permitAll() URI’s are passing through custom filter.
Expected Behavior
permitAll() URI’s shouldn’t pass through custom filter.
Configuration
Version
Sample
The text was updated successfully, but these errors were encountered:
mmouterde commented Mar 12, 2018 •
@redbrickss I had a similar issue with :
I fixed it with :
Not sure why the context is not refreshed between requests.
siddhiparkar151992 commented Sep 14, 2018
I am facing the same issue 😐 tried all possible combinations but it seems like after custom filter it doesnt work at all.
siddhiparkar151992 commented Sep 14, 2018 •
#Update:
Solved it using following config
added restricted path to filter others will be permitted
rwinch commented Sep 14, 2018
@siddhiparkar151992 I’m glad you were able to solve this. As an FYI, there is no need for .antMatchers(«/**») as this is the default. So you can replace what you have with the following:
PS: You can use fenced code blocks for formatting code. For example:
rwinch commented Sep 14, 2018
To anyone still having an issue, it is difficult to reproduce the issue if you have custom code (i.e. a custom authentication filter) without posting it. Please provide a complete sample to reproduce the issue.
KevinWingi commented Jun 8, 2019 •
Another way to solve the problem is ignoring the URL and if possible with the respective HTTP method overriding the method void configure (WebSecurity web) <> as bellow
@Override public void configure(WebSecurity web) throws Exception < web.ignoring().antMatchers(HttpMethod.POST, "/agents/login"); >
davi004 commented Sep 5, 2019 •
I am facing similar issues where i have custom filter for JWT validation and also have configured the web.ignoring in place but still the request goes into filter
Kevin2iBi commented Sep 5, 2019 •
@davi004 i think you need to remove the authorizeRequests().antMatchers(AUTH_WHITELIST).permitAll() code and just let the web.ignoring configuration for you AUTH_WHITELIST.
Another think you need to add your JWTRequestFiler filter before the UsernamePasswordAuthenticationFilter and not after like this addFilterBefore(new JWTRequestFilter(), UsernamePasswordAuthenticationFilter.class)
jzheaux commented Sep 6, 2019
permitAll() has no effect on authentication filters. Spring Security processes authentication first and then authorization, and permitAll() is an authorization matter.
Things essentially happen in this order:
- Write Secure Headers, like X-XSS-Protection
- Create an Authentication statement (that’s what the authentication filters are for)
- Decide if that Authentication is enough to allow the request ( permitAll() always says «yes»)
Step 2 is where a JWT filter would go since that’s how you’d figure out what privileges ( scopes for example) that token has. That question has to be asked before an authorization decision ( permitAll() ) can be made.
Note that just because a request is public, that doesn’t mean the response won’t be rendered differently if there is a user in context. Thus, the separation of these two concerns is important.
It’s already been stated that WebSecurity is how to completely ignore certain endpoints. Note, though, that this means that Spring Security won’t secure that request in any way (no Step 1, 2, or 3).
Источник
Why Spring Security permitAll() is not working with OAuth2.0?
I have a REST API secured with OAuth2.0 I am able to get the access-token using http://localhost:8085/auth/token?grant_type=password&username=22@gmail.com&password=mypass(along with username pass basic auth).
But when I am trying to access http://localhost:8085/api/v1/signup , API returns a 401 unauthorized error.
Though I have used antMatchers(«/signup»).permitAll() , why API is expecting a access-token to access this resource? Passing access-token along with this request would signup a user.
This is my resource server configuration
Update: As suggested by this thread, I ignored /signup at «, but that also didn’t worked.
2 Answers 2
I got the issue. It was the context path which was causing the issue. I am having a dispatcher servlet defined with a mapping URL /api/v1/* and as one can see my signup request, it contains a context path i.e http://localhost:8085/api/v1/signup
For OAuth2 configuration in Spring, we need to take extra care of context path. First, it should be defined in the AuthorizationServer
Then, the context must be added to the permitAll() path like this
Up till now, the signup request is still expected to pass a access token with it. For removing the OAuth security from signup, we need to remove security at the WebSecurity , which can be done using WebSecurityConfigurerAdapter
Note, there is no use to add context path to the WebSecurityConfigurerAdapter configuration.
Источник
Spring Security за 15 минут
Что вы сделаете?
Вы интегрируете web-app на Spring Boot с библиотекой Spring Security. Мы возьмем незащищенное приложение, которое мы разбирали в прошлой статье и защитим его с помощью Spring Security:
Что вам понадобится?
- Около 15-50 минут
- Ваш любимый текстовый редактор или IDE
- JDK 1.8 или выше
- Maven 3.0+
Как пользоваться этой статьей
- Внимательно прочитайте статью
- Скачайте исходный код с github
- Запустите и проверьте работу
- Удостоверьтесь что код вам понятен
- Если что то осталось неясно, смело задавайте вопросы мне или google
Первичная настройка Spring-Security
Давайте представим, что нам хочется предотвратить неавторизованный доступ к страничке http://localhost:8080/hello. Сейчас если пользователь нажмет ссылку на базовой странице http://localhost:8080/base ничто не остановит его. Вы должны добавить какой то барьер который бы заставлял пользователя авторизоваться перед тем как увидеть эту страничку. Вы сделаете это сконфигурировав Spring Security. Если Spring Security находится в classpath Spring Boot автоматически защищает все HTTP-endpoints базовой(basic) авторизацией
В дальнейшем вы сможете более тонко настроить security. Первое что вам надо сделать это добавить Spring Security в classpath:
WebSecurityConfigurerAdapter
- @EnableWebSecurity включает поддержку web security и обеспечивает интеграцию со Spring MVC
- сonfigure(HttpSecurity) определяет какой URL нужно защищать, а какой не надо(по умолчанию все защищено)
- .antMatchers(“/”, “/base”).permitAll() данные urls защищать не надо(.permitAll())
- .loginPage(“/login”) определяется страница для совершения логина в приложение, которая доступна всем
- .logout().permitAll() определяет что разлогиниться могут все
- configureGlobal(AuthenticationManagerBuilder) устанавливает in-memory хранилище пользователей с одним пользователем
Создадим login-страницу
Добавим файл src/main/resources/templates/login.html с шаблоном thymeleaf в котором включим интеграцию со Spring-Security — thymeleaf-extras-springsecurity3
И не забудем добавить в MvcConfig для страницы логина натройку контроллера — registry.addViewController(“/login”).setViewName(“login”);
А также изменим страничку hello добавив туда ссылку для разлогинивания:
Мы показываем имя пользователя на страничке благодаря использованию интеграции со Spring Security HttpServletRequest#getRemoteUser() Ссылка “Sign Out” создаст POST запрос к “/logout” и далее переведет пользователя на “/login?logout”. Благодаря чему пользователь увидит это сообщение:
Как это устроено?
Эта простейшая авторизация работает благодаря HTTP_cookie. Рекомендую отследить момент до логина и после и посмотреть на состояние cookie с именем JSESSIONID. Вы увидете что после логина cookie не меняет значение. После разлогинивания это значение поменяется, что и будет означать что пользователь вышел из системы.
В chrome нажмите Ctrl-Shift-I -> Network tab и далее заголовки httml запроса
Тоесть как только пользователь залогинился Spring Security присваивает залогиненому пользователю отдельный JSESSIONID по которому он определяет его при последующих запросах. Это все похоже на работу сессий в сервлетах. Подробнее прочитайте вот эту ссылку how-does-spring-security-sessions-work
Источник
Как настроить авторизацию в Spring Security
Продолжим тему аутентификации и авторизации в Spring Security.
Аутентификация — это проверка пользователя на то, является ли он тем, кем себя выдает. Приложение спрашивает «кто ты», а пользователь, например, вводит имя и пароль. Приложение проверяет, что такому имени действительно соответствует такой пароль и отвечает ок, проверка пройдена.
Авторизация — это выдача прав (либо отказ). Происходит уже после того, как пользователь подтвердил свою идентичность. Допустим, пользователь прошел аутентификацию и хочет попасть на url:
Приложение проверяет, какие стоят права у данного пользователя, и либо впускает его, либо нет.
Например, user может зайти на url /user, а admin и на /user, и еще на другие url.
Подготовка
В прошлом примере мы настроили аутентификацию типа In-Memory, при которой пользователи хранятся в памяти приложения. Пример простой и подходящий для демонстрации настройки авторизации.
Контроллер
Но для того, чтобы настроить права (для двух пользователей), дополним контроллер еще парой методов. Итак, пусть будут две роли USER и ADMIN, а также три URL:
Контроллер теперь имеет три URL:
Пользователи (In-Memory athentication)
А настройка аутентификации (см. предыдущий пример) выглядит теперь так:
Теперь мы ввели два разрешения ROLE_USER и ROLE_ADMIN, а в прошлом примере было одно.
Вот теперь разрешения нам пригодятся — для настройки авторизации.
Настройка авторизации
Чтобы настроить авторизацию, надо точно так же, как мы делали при настройке аутентификации, переопределить метод configure(), только теперь с другим аргументом HttpSecurity:
Именно этот объект HttpSecurity и нужно настраивать. Создавать его как бин не надо, его создает Spring Security, а мы получаем к нему доступ из метода configure(HttpSecurity http).
Как мы видели в прошлой статье, по умолчанию Spring Security дает доступ к любому url любому аутентифицированному пользователю. Иначе говоря, если хочешь попасть на url, перенаправляешься на форму ввода пароля, и только после этого попадаешь на url.
Переопределив метод configure(HttpSecurity http), мы немедленно отменили поведение по умолчанию. Теперь внутри переопределенного метода все требуется задать заново вручную (с небольшими нововведениями).
Мы по очереди перечисляем возможные url и задаем права доступа к ним (точнее, в нашем примере — роли).
Перечисление url
Это строкой мы говорим предоставить разрешения для следующих url.
Далее мы перечисляем не сами url (поскольку их может быть слишком много), а шаблоны. Шаблоны url задаются с помощью класса AntPathRequestMatcher .
Перечислять шаблоны надо в порядке от самых узкоохватывающих до широкоохватывающих:
Например, следующие три url подпадают под шаблон /admin/**:
Если в коде начать перечисление с всеобъемлющего шаблона /**, то перебор на нем остановится (так как любой url, в том числе /admin) соответствует шаблону /** , а значит всем будет разрешен доступ. Именно поэтому начинать нужно с узкоохватывающих шаблонов.
Настройка доступа (роли, разрешения)
Наконец, к главному. После шаблона в каждой строке указывается кому разрешен доступ: всем пользователям (метод permitAll() разрешает доступ всем, в том числе неаутентифицированным пользователям) или пользователям с определенной ролью — метод hasRole(«ADMIN») (либо ролями).
Обратите внимание, что в настройках аутентификации в начале статьи мы задавали пользователям разрешение с префиксом ROLE. А в настройках авторизации доступ определяем через роль. Роль идет без префикса ROLE, таково соглашение.
Можно было задать доступ с помощью разрешений, результат был бы аналогичный:
Еще такой есть полезный метод:
Страница ввода пароля
Наконец, страница логина теперь генерироваться не будет, чтобы ее вернуть, допишем строку:
Если пользователь не аутентифицирован (в данном случае так будет, если в запросе либо отсутствует кукис JSESSIONID, либо он неправильный), то выполняется редирект на страницу ввода логина и пароля.
Ввод логина и пароля в форму считается аутентификцией типа Form-Based, что означает, что имя и пароль приходят в POST-запросе в параметрах username и password (такие имена параметров по умолчанию используются в Spring Security). То есть когда пользователь попадет на страницу логина и введен туда данные, на сервер пойдет новый запрос, в котором данные будут передаваться в этом самом POST.
Чтобы задать аутентификацию типа Http Basic, строка в коде должна быть такая:
В этом случае браузеру придет ответ с требованием показать нативную браузерную форму, куда пользователь так же вводит данные. Но эти данные в случае Http Basic —аутентификации передаются уже в другом виде — в заголовке:
Это устаревший и небезопасный способ.
Но суть в том, что обе эти строки указывают Spring Security, как именно он должен брать из запроса имя пользователя и пароль.
Итоги
Пример доступен на GitHub.
Также см. тут как защитить конкретные методы с помощью @Preauthorize.
Как настроить авторизацию в Spring Security: 13 комментариев
Здравствуйте, подскажите, пожалуйста, как со всеми аналогичными настройками авторизоваться через постман? json с передачей username и password в формате json (raw) и через form-data не сработали. Возвращается всегда 403
1) Надо отключить csrf в configure(HttpSecurity http) (так как из браузера он с формой передается, а из POSTMAN — нет):
http.csrf().disable();
2) ну и перeдавать в POST-запросе localhost:8080/login form-data, к примеру такие:
username: user
password: password
Спасибо! Помогло 🙂 Еще вопрос: мне нужно чуть усложнить приложение, чтобы пользователь имел доступ на редактирование только созданных им объектов. Соответственно, роль будет по-прежнему юзер, но ограничениями доступа к «чужим аккаунтам». Предполагаю, что это можно решить как-то средствами группового разграничения доступа, но пока не получилось найти адекватный источник реализации этого всего добра. Можете натолкнуть на мысль?
Можно сделать авторизацию на уровне методов, например, если пользователь приложения AppUser может редактировать только свой объект Thing, то аннотируем метод редактирования так:
@PreAuthorize(«authentication.principal.username.equals(#thing.appUser.username)»)
public Thing editThing(Thing thing) <
//.
>
(подразумевается, что Thing имеет ссылку на юзера)
Есть также Spring Security ACL — мощная вещь (но возможно избыточная, нужно создавать обязательные таблицы и т.д.)
Я так понимаю, в контроллере использовать @PreAuthorize не выйдет? Нужно вынести куда-то в отдельный класс? Потому что вот так у меня не сработало (хотя блог с юзером связаны) Запрос выполнился даже не связанный:
@PutMapping(«/blog/
@PreAuthorize(«authentication.principal.username.equals(#blog.user.username)»)
public ResponseEntity updateBlog(@PathVariable int id, @Valid @RequestBody Blog blog) throws EntityNotFoundException <
ModelMapper mapper = new ModelMapper();
if (!blogRepository.findById(id).isPresent())
throw new EntityNotFoundException(«The blog with + id + » doesn’t exist»);
Blog blogToUpdate = blogRepository.getOne(id);
blogToUpdate.setTitle(blog.getTitle());
blogToUpdate.setContent(blog.getContent());
blogRepository.save(blogToUpdate);
return ResponseEntity.ok().body(mapper.map(blogToUpdate, BlogDto.class));
>
1. Возможно не включили @EnableGlobalMethodSecurity(prePostEnabled = true) на главном классе.
2. @PreAuthorize рекомендуется ставить на методы сервисов
Вы очень понятно объясняете, спасибо большое за ваш труд!
2 связанных вопроса:
1. Откуда в конфиге вытаскивается информация о наличии у юзера определенной роли hadRole? Я использую jwt token, по вашему примеру делала фильтр, чтобы токен при запуске апп.проверялся, обновлялся (дата продлевается) и заново ложился в хедер реквеста перед возвратом в чейн — и дальше уже в контроллер
2. Допустим, в jwt токен есть роли в authorities. Что срабатывает раньше: наш конфиг со своими hadRole, или фильтр? А то выходит, фильтр мне обновит токен (дату, и вытащит в токен актуальные роли из бд с помощью userDetails), но секьюрити конфиг сработает раньше фильтра и не будет знать, что юзер уже админ, например
уточните, пожалуйста, что вы имеете в виду под «конфиг где вытаскивается информация о наличии у юзера определенной роли hadRole»?
В методе configure(AuthenticationManagerBuilder auth) указаны два In-Memory пользователя user и admin с ролями, но если у вас база, то этих пользователей в этом методе не должно быть. Они созданы как замена реальным для демонстрации примера.
P.S. их можно считать аналогом пользователей из базы, но только они пересоздаются каждый раз при перезапуске приложения в том же виде.
Подскажите, использую микросервисную архитектуру, и авторизация выполняется на другом сервере. У пользователя есть два токена. Токен досупа и refresh токен. Второй устаревает каждый 3 минуты. За его обновлением следит свой сервис.
Для доступа к основыным сервсам приложения пользователь в базовой аутентификации передает логин и пароль. Логи это токен доступа… пароль… refreshToken. Так как второй все время обновляется то компроментация ему не страшна.
Но есть пролблем. Спринг хеширует авторизацию. И при повторных запросах уже не проверяет ее если установлен куки JSESSIONID.
А так как токен устаревает, то проверять надо при каждом доступе к серверу.
Сейчас у меня такие настройки Spring Security
@Override
protected void configure(HttpSecurity http) throws Exception <
http
.csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().disable()
.logout().deleteCookies(«JSESSIONID»).permitAll();
http.addFilterAfter(
new CustomFilter(), BasicAuthenticationFilter.class);
Подскажите, может еще надо что то дописать, чтоб каждый запрос проходил процедуру авторизации?
В декомпилированном BasicAuthenticationFilter нашел вот такой метод private boolean authenticationIsRequired(String username) <
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if (existingAuth != null && existingAuth.isAuthenticated()) <
if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) <
return true;
> else <
return existingAuth instanceof AnonymousAuthenticationToken;
>
> else <
return true;
>
>
тут он как раз проверяет необходимость повторной авторизации. Но как это мне поможет не понимаю.
Что касается сессий: попробуйте их таким образом отключить:
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); (вместо .and().sessionManagement().disable())
Спасибо! Помогло!
У меня токен обновления живет 3 минуты. Может мне не отключать совсем сессию, а сделать у нее таймаут, пару минут. Чтоб уменьшить нагрузку на сервис авторизации?
Источник