Реализация аутентификации JWT в API-интерфейсах Spring Boot

У меня есть веб-приложение SpringBoot 2.0.2.RELEASE с этим файлом конфигурации:

@Override
protected void configure(HttpSecurity http) throws Exception {

    final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
    if (activeProfiles.contains("dev")) {
        http.csrf().disable();
        http.headers().frameOptions().disable();
    }

    http
        .authorizeRequests()
        .antMatchers(publicMatchers()).permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
        .failureUrl("/login?error").permitAll()
        .and()
        .logout().permitAll();
    }

Я хочу добавить настраиваемый фильтр безопасности на основе JWT ТОЛЬКО для остальных контроллеров, которые будут соответствовать /rest/**, поэтому я изменил конфигурацию этого файла, но теперь я не могу войти в приложение, потому что у меня есть Статус HTTP 401 - неавторизованный

@Override
protected void configure(HttpSecurity http) throws Exception {

    final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
    if (activeProfiles.contains("dev")) {
        http.csrf().disable();
        http.headers().frameOptions().disable();
    }

    http
       .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
       // don't create session
       .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
       .authorizeRequests()
       .antMatchers(publicMatchers()).permitAll()
       .anyRequest().authenticated()
       .and()
       .formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
                   .failureUrl("/login?error").permitAll()
       .and()
       .logout().permitAll();


       // Custom JWT based security filter
       JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
       http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

и фильтр (который простирается от OncePerRequestFilter)

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

    logger.info("processing authentication for '{}'", request.getRequestURL());


    if (request.getRequestURI().indexOf("/rest/")==-1) {
        chain.doFilter(request, response);
        return;
    }


    final String requestHeader = request.getHeader(this.tokenHeader);

    String username = null;
    String authToken = null;
    if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
        authToken = requestHeader.substring(7);
        try {
            username = jwtTokenUtil.getUsernameFromToken(authToken);
        } catch (IllegalArgumentException e) {
            logger.info("an error occured during getting username from token", e);
        } catch (ExpiredJwtException e) {
            logger.info("the token is expired and not valid anymore", e);
        }
    } else {
        logger.info("couldn't find bearer string, will ignore the header");
    }

    logger.info("checking authentication for user '{}'", username);
    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
       logger.info("security context was null, so authorizating user");

        // It is not compelling necessary to load the use details from the database. You could also store the information
        // in the token and read it from it. It's up to you ;)
        UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

        // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
        // the database compellingly. Again it's up to you ;)
        if (jwtTokenUtil.validateToken(authToken, userDetails)) {
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            logger.info("authorizated user '{}', setting security context", username);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(request, response);
    }

....

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    return request.getRequestURI().indexOf("/rest/")==-1;
}

в регистраторе я вижу

("couldn't find bearer string, will ignore the header"

Потому что я хочу применить фильтр JWT только в RestContollers не во всех из них, например LoginController

С помощью этого класса конфигурации я могу получить доступ к URL-адресу / rest / только при входе в приложение.

@Profile("web")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final Logger LOG = LoggerFactory.getLogger(WebSecurityConfig.class);

    @Autowired
    private UserSecurityService userSecurityService;


    @Value("${server.servlet.context-path}")
    private String serverContextPath;

    /** The encryption SALT. */
    private static final String SALT = "fd&lkj§sfs23#$1*(_)nof";

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
    }

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

    @Configuration
    @Order(1)
    public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Autowired
        private JwtAuthenticationEntryPoint unauthorizedHandler;

        @Autowired
        private JwtTokenUtil jwtTokenUtil;

        @Value("${jwt.header}")
        private String tokenHeader;

        @Value("${jwt.route.authentication.path}")
        private String authenticationPath;

        @Override
        protected void configure(HttpSecurity http) throws Exception {

            http
                    // we don't need CSRF because our token is invulnerable
                    .csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                    // don't create session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests()
                    .antMatchers(“/rest/**”).permitAll().anyRequest().authenticated()
                    .antMatchers(“**/rest/**”).permitAll().anyRequest().authenticated();

            // Custom JWT based security filter
            JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
            http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

            // disable page caching
            http.headers().frameOptions().sameOrigin() // required to set for H2 else H2 Console will be blank.
                    .cacheControl();

        }

    }

    @Configuration
    @Order(0)
    public static class OtherSecurityConfiguration extends WebSecurityConfigurerAdapter {

        @Value("${server.servlet.context-path}")
        private String serverContextPath;

        @Autowired
        private Environment env;

        @Override
        protected void configure(HttpSecurity http) throws Exception {

            final List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
            if (activeProfiles.contains("dev")) {
                http.csrf().disable();
                http.headers().frameOptions().disable();
            }

            http.authorizeRequests()
                .antMatchers(publicMatchers())
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin().loginPage("/login").defaultSuccessUrl("/bonanza/list")
                .failureUrl("/login?error").permitAll()
                .and()
                .logout()
                .permitAll();
        }

         private String[] publicMatchers() {

             /** Public URLs. */
            final String[] PUBLIC_MATCHERS = {
                    "/webjars/**",
                    serverContextPath + "/css/**",
                    serverContextPath + "/js/**",
                    serverContextPath + "/fonts/**",
                    serverContextPath + "/images/**",                
                    serverContextPath ,
                    "/",
                    "/error/**/*",
                    "/console/**",
                    ForgotMyPasswordController.FORGOT_PASSWORD_URL_MAPPING,
                    ForgotMyPasswordController.CHANGE_PASSWORD_PATH,
                    SignupController.SIGNUP_URL_MAPPING
            };

            return PUBLIC_MATCHERS;

        }

    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userSecurityService).passwordEncoder(passwordEncoder());
    }

}

Почему бы вам не использовать antMatcher («rest / **»). Authenticated ()

Mohale 19.05.2018 22:55

Я должен быть аутентифицирован во всех случаях, отдыхая / с JWT, а остальные с типичной springsecurity

Nunyet de Can Calçada 20.05.2018 06:54

предполагая, что ваш фильтр - OncePerRequestFilter, у него есть метод shouldNotFilter (), можете ли вы использовать его, чтобы игнорировать все, кроме ваших желаемых запросов?

Will M. 21.05.2018 20:05
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
3
1 668
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короче говоря, у вас есть два подпути (а именно /rest/** и другие) в одном приложении, и вы хотите применить разные схемы входа для каждого из них. Spring-security позволяет вам иметь несколько конфигураций, учитывая такой сценарий.

Я бы сделал что-то вроде этого:

@EnableWebSecurity
public class SecurityConfig {

    @Configuration
    @Order(1)
    public static class ApiSecurityConfiguration 
                  extends WebSecurityConfigurerAdapter {

        private final JwtAuthorizationTokenFilter jwtFilter = new ...
        private final AuthenticationEntryPoint unauthorizedHandler = new ...

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/rest/**").authorizeRequests()
                .and()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
                .and()
                .addFilter(jwtFilter);
        }
    }

    @Configuration
    public static class OtherSecurityConfiguration 
                  extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests().anyRequest().authenticated()
                    .and()
                    .formLogin()
                        .loginPage("/login").defaultSuccessUrl("/bonanza/list")
                        .failureUrl("/login?error").permitAll()
                    .and()
                    .logout().permitAll();
        }
    }
}

При такой конфигурации JwtAuthorizationTokenFilter следует активировать только для совпадающих путей. Таким образом, я думаю, вам не нужно проверять пути в JwtAuthorizationTokenFilter.

Другие вопросы по теме