arrow-left

All pages
gitbookPowered by GitBook
1 of 1

Loading...

Лекция 13-14

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

triangle-exclamation

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Возвращаем токен как объект AuthResponse.
Извлекаем из БД пользователя по полученному username и осуществляем валидацию токена;
  • Если jwt провел валидацию, то аутентифицируем пользователя;

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

  • <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>
    @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();
        }
    }
    @Data
    public class AuthRequest {
        private String username;
        private String password;
    }
    @Data
    public class AuthResponse {
        private final String jwt;
    }
    @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));
        }
    }
    @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();
        }
    }
    @GetMapping(value = "/helloworld")
    public String helloWorldRequest(Principal principal) {
        final UserDetails userDetails = userDetailsService.loadUserByUsername(principal.getName());
        return "<h1>Hello " + userDetails.getAuthorities().toString() + " </h1>";
    }
    @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);
    }
    @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);
        }
    }
    @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);
        }
    }