Как настроить авторизацию в Spring Security

Продолжим тему аутентификации и авторизации в Spring Security.

Аутентификация – это проверка пользователя на то, является ли он тем, кем себя выдает. Приложение спрашивает “кто ты”, а пользователь, например,  вводит имя и пароль. Приложение проверяет, что такому имени действительно соответствует такой пароль и отвечает ок, проверка пройдена.

Авторизация – это выдача прав (либо отказ).  Происходит уже после того, как пользователь подтвердил  свою идентичность.  Допустим, пользователь прошел аутентификацию и хочет попасть на url:

/admin

Приложение проверяет, какие стоят права у данного пользователя, и либо впускает его, либо нет.

Например, user может зайти на url  /user, а admin и на /user, и еще на другие url.

Подготовка

В прошлом примере мы настроили аутентификацию типа In-Memory, при которой пользователи хранятся в памяти приложения. Пример простой и подходящий для демонстрации настройки авторизации.

Контроллер

Но для того, чтобы настроить права (для двух пользователей), дополним контроллер еще парой методов. Итак, пусть будут две роли USER и ADMIN, а также три URL:

/               для всех пользователей (в том числе не аутентифицированных)
/user           для пользователей с ролью USER и ADMIN
/admin          для пользователей с ролью ADMIN

Контроллер теперь имеет три URL:

@RestController
public class HelloController {

    @GetMapping("/")
    public String hello() {
        return "Hello";
    }

    @GetMapping("/user")
    public String user() {
        return "User";
    }

    @GetMapping("/admin")
    public String admin() {
        return "Admin";
    }
}

Пользователи (In-Memory athentication)

А настройка аутентификации (см. предыдущий пример) выглядит теперь так:

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("user")
                   .password("user")
                   .authorities("ROLE_USER")
                .and()
                .withUser("admin")
                   .password("admin")
                   .authorities("ROLE_ADMIN");
    }

Теперь мы ввели два разрешения ROLE_USER и ROLE_ADMIN, а в прошлом примере было одно.

Вот теперь разрешения нам пригодятся – для настройки авторизации.

Настройка авторизации

Чтобы настроить авторизацию, надо точно так же, как мы делали при настройке аутентификации, переопределить метод configure(), только теперь с другим аргументом HttpSecurity:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //другие методы

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
              .antMatchers("/admin/**").hasRole( "ADMIN")
              .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
              .antMatchers("/**").permitAll()
      .and().formLogin();
    }
}

Именно этот объект HttpSecurity и нужно настраивать. Создавать его как бин не надо, его создает Spring Security, а мы получаем к нему доступ из метода configure(HttpSecurity http).

Как мы видели в прошлой статье, по умолчанию Spring Security дает доступ к любому url любому аутентифицированному пользователю. Иначе говоря, если хочешь попасть на url, перенаправляешься на форму ввода пароля, и только после этого попадаешь на url.

Переопределив метод configure(HttpSecurity http), мы немедленно отменили поведение по умолчанию. Теперь внутри переопределенного метода все требуется задать заново вручную (с небольшими нововведениями).

Мы по очереди перечисляем возможные url и задаем права доступа к ним (точнее, в нашем примере – роли).

Перечисление url

Итак:

http.authorizeRequests()

Это строкой мы говорим предоставить разрешения для следующих url.

Далее мы перечисляем не сами url (поскольку их может быть слишком много), а шаблоны. Шаблоны url задаются с  помощью класса AntPathRequestMatcher .

Перечислять шаблоны надо в порядке от самых узкоохватывающих до широкоохватывающих:

    .antMatchers("/admin/**").hasRole("ADMIN")
    .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
    .antMatchers("/**").permitAll()

Здесь шаблон:

/** означает любой url

/admin/** означает любые, в том числе вложенные url, начинающиеся с /admin

Например, следующие три url подпадают под  шаблон /admin/**:

/admin
/admin/page1
/admin/aaa/bbb

Если в коде начать перечисление с всеобъемлющего шаблона  /**, то перебор на нем остановится (так как любой url, в том числе /admin) соответствует шаблону /** , а значит всем будет разрешен доступ. Именно поэтому начинать нужно с узкоохватывающих шаблонов.

Настройка доступа (роли, разрешения)

Наконец, к главному. После шаблона в каждой строке указывается кому разрешен доступ: всем пользователям (метод permitAll() разрешает доступ всем, в том числе неаутентифицированным пользователям) или пользователям с определенной ролью – метод hasRole(“ADMIN”) (либо ролями).

Обратите внимание, что в настройках аутентификации в начале статьи мы задавали пользователям разрешение с префиксом ROLE. А в настройках авторизации доступ определяем через роль. Роль идет без префикса ROLE, таково соглашение.

Можно было задать доступ с помощью разрешений, результат был бы аналогичный:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
                .antMatchers("/user/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
                .antMatchers("/**").permitAll()
                .and().formLogin();
    }

Еще такой есть полезный метод:

.authenticated() - разрешает доступ всем аутентифицированным пользователям

Страница ввода пароля

Наконец, страница логина теперь генерироваться не будет, чтобы ее вернуть, допишем строку:

.and().formLogin();

Если пользователь не аутентифицирован (в данном случае так будет, если в запросе либо отсутствует кукис JSESSIONID, либо он неправильный), то выполняется редирект на страницу ввода логина и пароля.

Ввод логина и пароля в форму считается аутентификцией типа Form-Based, что означает, что имя и пароль приходят в  POST-запросе в параметрах username и password (такие имена параметров по умолчанию используются в Spring Security). То есть когда пользователь попадет на страницу логина и введен туда данные, на сервер пойдет новый запрос, в котором данные будут передаваться в этом самом POST.

Чтобы задать аутентификацию типа Http Basic, строка в коде должна быть такая:

.and().httpBasic();

В этом случае браузеру придет ответ с требованием показать нативную браузерную форму, куда пользователь так же вводит данные. Но эти данные в случае Http Basic –аутентификации передаются уже в другом виде – в заголовке:

Authorization: Basic base64(usename:password)

Это устаревший и небезопасный способ.

Но суть в том, что обе эти строки указывают Spring Security, как именно он должен брать из запроса имя пользователя и пароль.

Не стоит путать аутентификацию In-Memory (рассмотренную в предыдущей статье) с аутентификациями типа Form-Based  и Http Basic.  Последние две безотносительно Spring Security определяют, как именно передается в запросе имя/пароль с клиента на сервер, то есть в какой части запроса хранятся эти данные – в заголовке, в теле запроса и т.д. А In-Memory – это тип аутентификации в Spring Security (один из), который задает, как пользователи хранятся на стороне сервера (в базе, настройках, коде… ), как и откуда их достать, чтобы потом сравнить с переданными в запросе именем и паролем.

Итоги

Пример доступен на GitHub.

Как настроить авторизацию в Spring Security: 5 комментариев

  1. Здравствуйте.
    Очень хорошо объясняете.
    Не могли бы Вы подсказать, я выполнил реализацию собственной аутентификации:
    .authorizeRequests()
    .antMatchers(“/admin/**”).hasRole(“ADMIN”)
    .antMatchers(“/ok/**”).hasRole(“USER”)
    .antMatchers(“/*”).permitAll()
    .and()
    .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
    .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
    .and()
    .exceptionHandling().authenticationEntryPoint(new CustomHttp403ForbiddenEntryPoint())
    контроллеры также как вашем примере (концептуально).
    При попытке зайти в закрытую часть (../ok или ../ok/) все время перебрасывает на accessDeniedHandler().

    Сам accessDeniedHandler() такой:
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    String url = request.getContextPath() + “/accessdenied”;
    response.sendRedirect(url);
    }

        1. Ох, это какой-то gist – разрозненные сниппеты, которые надо по кусочкам в цельный проект собирать, если правильно понимаю.

          Имелась в виду ссылка на обычный репо с проектом, вот такой например https://github.com/vokmar/vokmar – только здесь сейчас один файл readme.md лежит.
          (П.С. Как отправить проект с локального компьютера в GitHub-репозиторий у меня есть в первой статье про Git, если что) https://sysout.ru/rabota-s-github/)

  2. Здравствуйте, подскажите, пожалуйста, как со всеми аналогичными настройками авторизоваться через постман? json с передачей username и password в формате json (raw) и через form-data не сработали. Возвращается всегда 403

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *