Курс "Объектно-ориентированное программирование"
  • Главная
  • 1 семестр (осень 2021)
    • Видео
    • Конспект лекций
      • Лекция 1
      • Лекция 2
      • Лекция 3
      • Лекция 4
      • Лекция 5
      • Лекция 6
      • Лекция 7
      • Лекция 8
      • Лекция 9
      • Лекция 10
      • Лекция 11
      • Лекция 12
      • Лекция 13
      • Лекция 14
      • Лекция 15
    • Лабораторные работы
      • Лабораторная работа 1
        • 1. Установка программного обеспечения, создание и запуск проекта
        • 2. Основы синатксиса Java
        • Задание на лабораторную работу
      • Лабораторная работа 2
        • Синтаксис создания классов
        • Задание на лабораторную работу
      • Лабораторная работа 3
        • Инкапсуляция
        • Язык моделирования UML
        • Задание на лабораторную работу
      • Лабораторная работа 4
        • Наследование и композиция
        • Наследование и композиция в UML
        • Задание на лабораторную работу
      • Лабораторная работа 5
        • Задание на лабораторную работу
      • Лабораторная работа 6
        • Абстрактные классы и интерфейсы в UML
        • Задание на лабораторную работу
  • 2 семестр (весна 2022)
    • Конспект лекций
      • Лекция 1-2
      • Лекция 3-4
      • Лекция 5
      • Лекция 6
      • Лекция 7-8
      • Лекция 9
      • Лекция 10
      • Лекция 11-12
      • Лекция 13-14
      • Лекция 15
  • Архив
    • Лекция 2
    • Архив
      • Заметки по абстракции
      • Конспект лекций
        • 1. Принцип Separation of Concerns, контроллер и представление
        • 2. Паттерн MVC
        • 3. Клиент-серверная архитектура. Создание простой RESTful веб-службы с помощью Spring Boot.
        • 4. Внедрение зависимостей (Dependency Injection)
        • 5. Интеграция приложения с СУБД
        • 6. Фреймворк Spring MVC
        • 7. Работа со Spring Security, часть 1
        • 8. Работа со Spring Security, часть 2
        • 9. Развертывание приложения в Heroku
      • Заочники (осень 2022)
      • Архив
        • Лекции
          • Draft
            • 5. Абстрактные классы и интерфейсы. Механизм обратного вызова
            • 6. Анонимные классы
          • Блок лекций 1
            • Дополнительные задания и литература
            • 1. Базовые сведения об ООП
            • 1. Обобщенные типы. Автоупаковка и автораспаковка.
            • 2. Знакомство с языком Java
          • Блок лекций 2
            • 5. Абстрактные классы и интерфейсы. Механизм обратного вызова.
            • 6. Анонимные объекты, классы, методы. Лямбда-выражения.
            • 7. Аргументы переменной длины. Принцип абстракции. Дополнительные принципы ООП.
          • Блок лекций 3
            • 2. Коллекции объектов.
            • 3. Паттерн Итератор. Компараторы. Потоки в Java (Streams API)
          • Блок лекций 4
            • 1. Исключения в Java. Обработка исключений.
          • Блок лекций 5
            • Статические поля и методы
            • Вложенные и внутренние классы
            • Перечисления
          • Блок лекций 6
            • 2. Интеграция приложения с СУБД
        • Лабораторные работы
          • 2. Основы синтаксиса
            • Python
            • C#
          • Лабораторная работа 3
            • 1. Инкапсуляция
            • 2. Перегрузка методов
            • Задание на лабораторную работу
            • Теоретические вопросы
          • Лабораторная работа 4
            • Задание на лабораторную работу
            • Теоретические вопросы
          • Лабораторная работа 6
            • Задание на лабораторную работу
            • Теоретические вопросы
          • Лабораторная работа 7
            • Задание на лабораторную работу
            • Теоретические вопросы
          • Лабораторная работа 8
          • Лабораторная работа 9
          • Лабораторная работа 10
            • Задание на лабораторную работу
          • Лабораторная работа 11
            • Задание на лабораторную работу
          • Лабораторная работа 12
            • Пример выполнения лабораторной работы
            • Задание на лабораторную работу
        • Практические работы
          • Практическая работа 1
          • Практическая работа 2
        • Материалы по Java Spring
          • 1. Клиент-серверная архитектура. Создание простой RESTful веб-службы с помощью Spring Boot.
          • 4. Работа со Spring MVC, часть 1
          • 5. Работа со Spring MVC, часть 2
      • Конспект лекций
        • 1. История развития языков программирования
        • 2. Базовые термины ООП
        • 3. Понятие класса и объекта
        • 4. Создание объектов. Конструктор.
        • 5. Основные принципы ООП. Инкапсуляция.
        • 6. Перегрузка методов
        • 7. Повторное использование кода. Композиция и наследование
        • 8. Полиморфизм
        • 9. Абстрактные классы и интерфейсы
        • 10. Анонимные классы, интерфейсы, методы. Лямбда-выражения
        • 11. Принцип абстракции. Дополнительные принципы ООП
        • 12. Обобщенные типы. Автоупаковка и автораспаковка
        • 13. Коллекции
        • 14. Паттерн "Итератор". Компараторы. Фреймворк Streams API.
        • 15. Исключения в Java. Обработка исключений
        • 16. Принципы разработки графического интерфейса. Фреймворк JavaFX
        • 17. События в JavaFX
        • 20. Перечисления
      • Лабораторные работы
        • Лабораторная работа 2
          • 1. Синтаксис создания классов
          • 2. Язык моделирования UML
          • Задание на лабораторную работу
          • Теоретические вопросы
        • Лабораторная работа 3
          • 1. Инкапсуляция
          • 2. Перегрузка методов
          • Задание на лабораторную работу
        • Лабораторная работа 4
          • Наследование и композиция
          • Наследование и композиция в диаграмме классов UML
          • Задание на лабораторную работу
        • Лабораторная работа 5
          • Задание на лабораторную работу
          • Теоретические вопросы
        • Лабораторная работа 6
          • Задание (старое)
          • Теоретические вопросы
        • Лабораторная работа 8
          • Задание на лабораторную работу
        • Лабораторная работа 9
          • Задание на лабораторную работу
      • Видеолекции
      • Видеолекции
Powered by GitBook
On this page
  • Тема: Понятие сессии. Cookies. JWT-токены. Генерация токенов. Security Filter Chain. Разработка приложения с использованием jwt-токенов.
  • Аутентификация с помощью jwt

Was this helpful?

Export as PDF
  1. 2 семестр (весна 2022)
  2. Конспект лекций

Лекция 13-14

Тема: Понятие сессии. Cookies. JWT-токены. Генерация токенов. Security Filter Chain. Разработка приложения с использованием jwt-токенов.

Материал по теме находится в стадии оформления

  1. Добавляем в pom-файл поддержку jwt и jaxb

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

Создадим класс JwtUtils, добавим приватный ключ и методы для создания токена

@Service
public class JwiUtil {

    private String PRIVATE_KEY = "670d4918c206e1bfec75bcaf637dc5b8";

    private String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
                .signWith(SignatureAlgorithm.HS512, PRIVATE_KEY).compact();
    }
}

Добавим метод для валидациия токена и сопутствующие ему методы

@Service
public class JwiUtil {

    private String PRIVATE_KEY = "670d4918c206e1bfec75bcaf637dc5b8";

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 8))
                .signWith(SignatureAlgorithm.HS512, PRIVATE_KEY).compact();
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    private String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(PRIVATE_KEY).parseClaimsJws(token).getBody();
    }
}

Создадим классы-обертки для входящего логина и пароля и для исходящего jwt

@Data
public class AuthRequest {
    private String username;
    private String password;
}
@Data
public class AuthResponse {
    private final String jwt;
}

Создадим класс REST-контроллера, добавим метод для создания токена. Логика работы метода следующая:

  1. Производится аутентификация по пришедшему логину и паролю;

  2. Если аутентификация прошла успешно, то получаем UserDetails из БД по username;

  3. Генерируем токен с помощью данных UserDetails;

  4. Возвращаем токен как объект AuthResponse.

@org.springframework.web.bind.annotation.RestController
public class RestController {

    @Autowired
    private AuthenticationManager authManager;

    @Autowired
    private JwtUtil jwiUtil;

    @Autowired
    MyUserDetailsService userDetailsService;

    @PostMapping(value = "/auth")
    public ResponseEntity<?> createAuthToken(@RequestBody AuthRequest request) throws BadCredentialsException {
        try {
            authManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
            );
        } catch (BadCredentialsException ex) {
            ex.printStackTrace();
        }

        final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername());
        final String jwt = jwiUtil.generateToken(userDetails);

        return ResponseEntity.ok(new AuthResponse(jwt));
    }
}

Настраиваем конфигурационный класс SecurityConfig. Добавляем bean для AuthenticationManager, а также доступ к URL. Для того, чтобы сервер не генерировал новую сессию, устанавливаем sessionCreationPolicy.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    MyUserDetailsService userDetailsService;

    @Autowired
    JwtRequestFilter filter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    // Авторизация
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "static/css", "static/js").permitAll()
                .antMatchers("/auth").permitAll()
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

В качестве клиента используем Postman.

Для начала попробуем сделать запрос с некорректным логином и паролем

На стороне сервера было выброшено исключение

Теперь попробуем ввести логин и пароль существующего пользователя. В ответ мы получим jwt, который будем использовать для последующих запросов.

Аутентификация с помощью jwt

Добавим в класс-контроллер endpoint, для доступа к которому требуется аутентификация. Входной аргумент типа Principal хранит информацию об аутентифицированном пользователе. Получаем username пользователя и извлекаем из БД информацию о нем.

@GetMapping(value = "/helloworld")
public String helloWorldRequest(Principal principal) {
    final UserDetails userDetails = userDetailsService.loadUserByUsername(principal.getName());
    return "<h1>Hello " + userDetails.getAuthorities().toString() + " </h1>";
}

Редактируем конфигурационный класс SecurityConfig, добавляем требование аутентификации для endpoint /helloworld.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/", "static/css", "static/js").permitAll()
            .antMatchers("/auth").permitAll()
            .antMatchers("/helloworld").authenticated()
            .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .antMatchers("/admin/**").hasRole("ADMIN")
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

Для того чтобы провести аутентификацию с помощью jwt, создадим отдельный фильтр, который потом встроим в filter chain, которая используется в Spring Security.

Логика работы нашего фильтра следующая:

  1. Считываем заголовок GET-запроса с ключом "Authorization";

  2. Проверяем, есть ли в начале заголовка слово "Bearer ";

  3. Извлекаем из jwt значение username;

  4. Извлекаем из БД пользователя по полученному username и осуществляем валидацию токена;

  5. Если jwt провел валидацию, то аутентифицируем пользователя;

  6. Передаем управление дальше по цепочке фильтров.

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    MyUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

        final String authHeader = httpServletRequest.getHeader("Authorization");

        String jwt = null;
        String username = null;

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            jwt = authHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(token);
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

Отредактируем конфигурационный класс SecurityConfig, добавим использование фильтра в цепочке фильтров (мы указываем, что наш фильтр должен быть встроен в цепочку ДО фильтра UsernamePasswordAuthenticationFilter, который является стандартным фильтром для аутентификации).

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // Авторизация
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "static/css", "static/js").permitAll()
                .antMatchers("/auth").permitAll()
                .antMatchers("/helloworld","/me").authenticated()
                .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
    }
}
PreviousЛекция 11-12NextЛекция 15

Last updated 3 years ago

Was this helpful?