Spring Security MultiHttpSecurity Конфигурация, чтобы я мог выполнять два типа аутентификации. JWT токен и сессионный файл cookie - PullRequest
3 голосов
/ 04 октября 2019

У меня уже есть механизм Spring Security Cookie для моего приложения, теперь только для API, мне нужно добавить механизм аутентификации на основе токенов JWT. Я использую MultiHttpSecurityConfiguration от Spring Security с двумя вложенными классами.

Должен ли механизм сессий и токена JWT включаться вместе в одно приложение или нет, это совсем другой вопрос, мне нужно достичь двух вещей.

  1. Сессионная аутентификация Spring Security с cookie будет работать так же, как и раньше.
  2. Необходимо добавить заголовок аутентификации для API
package com.leadwinner.sms.config;

import java.util.Collections;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import com.leadwinner.sms.CustomAuthenticationSuccessHandler;
import com.leadwinner.sms.CustomLogoutSuccessHandler;
import com.leadwinner.sms.config.jwt.JwtAuthenticationProvider;
import com.leadwinner.sms.config.jwt.JwtAuthenticationTokenFilter;
import com.leadwinner.sms.config.jwt.JwtSuccessHandler;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@ComponentScan(basePackages = "com.leadwinner.sms")
public class MultiHttpSecurityConfig {

    @Autowired
    @Qualifier("userServiceImpl")
    private UserDetailsService userServiceImpl;

    @Autowired
    private JwtAuthenticationProvider authenticationProvider;

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

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

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Collections.singletonList(authenticationProvider));
    }

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

         @Autowired
         private JwtAuthenticationTokenFilter jwtauthFilter;

        @Override
        public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .antMatcher("/web/umgmt/**").authorizeRequests()
            .antMatchers("/web/umgmt/**").authenticated()
            .and()
            .addFilterBefore(jwtauthFilter, UsernamePasswordAuthenticationFilter.class);
         http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }

    @Configuration
    @Order(2)
    public static class SecurityConfig extends WebSecurityConfigurerAdapter {
        private  final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

        @Bean
        public CustomAuthenticationEntryPoint getBasicAuthEntryPoint() {
            return new CustomAuthenticationEntryPoint();
        }

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

            logger.info("http configure");
            http
            .antMatcher("/**").authorizeRequests()          
            .antMatchers("/login/authenticate").permitAll()
                    .antMatchers("/resources/js/**").permitAll()
                    .antMatchers("/resources/css/**").permitAll()
                    .antMatchers("/resources/images/**").permitAll()
                    .antMatchers("/web/initial/setup/**").permitAll()
                    .antMatchers("/dsinput/**").permitAll().antMatchers("/dsoutput/**").permitAll()                 

                    .and()
                .formLogin()
                    .loginPage("/login").usernameParameter("employeeId").passwordParameter("password")
                    .successForwardUrl("/dashboard")
                    .defaultSuccessUrl("/dashboard", true)
                    .successHandler(customAuthenticationSuccessHandler())
                    .failureForwardUrl("/logout")
                    .loginProcessingUrl("/j_spring_security_check")
                    .and().logout()
                    .logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
                    .logoutSuccessHandler(customLogoutSuccessHandler())
                    .permitAll()
                    .invalidateHttpSession(true)
                    .deleteCookies("JSESSIONID")
                    .and().sessionManagement()
                    .sessionFixation().none()
                    .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                    .invalidSessionUrl("/logout")
                    .and().exceptionHandling().accessDeniedPage("/logout").and().csrf().disable();
            http.authorizeRequests().anyRequest().authenticated();


        }

        @Bean
        public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
            return new CustomAuthenticationSuccessHandler();
        }

        @Bean
        public LogoutSuccessHandler customLogoutSuccessHandler() {
            return new CustomLogoutSuccessHandler();
        }
    }
}

JwtAuthenticationTokenFilter.java

package com.leadwinner.sms.config.jwt;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            String authToken = header.substring(7);
            System.out.println(authToken);

            try {
                String username = jwtTokenUtil.getUsernameFromToken(authToken);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    if (jwtTokenUtil.validateToken(authToken, username)) {
                        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                                username, null, null);
                        usernamePasswordAuthenticationToken
                                .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                    }
                }
            } catch (Exception e) {
                System.out.println("Unable to get JWT Token, possibly expired");
            }
        }

        chain.doFilter(request, response);
    }
}

JwtTokenUtil.java

package com.leadwinner.sms.config.jwt;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

@Component
public class JwtTokenUtil implements Serializable {
    private static final long serialVersionUID = 8544329907338151549L;
    public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
    private String secret = "my-secret";

    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

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

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, username);
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        return "Bearer "
                + Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                        .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                        .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    public Boolean validateToken(String token, String usernameFromToken) {
        final String username = getUsernameFromToken(token);
        return (username.equals(usernameFromToken) && !isTokenExpired(token));
    }
}

Похоже, что теперь фильтр JwtSecurityConfig не применяется для пути, который я упомянул. Будем благодарны за любую помощь.

Я уже читал этот вопрос. Я следовал тому же.

Spring Security с Spring Boot: смешать базовую аутентификацию с аутентификацией токена JWT

Редактировать: добавлен JwtAuthenticationTokenFilter, JwtTokenUtil

1 Ответ

1 голос
/ 11 октября 2019

Я получил ваше требование.

  1. В заголовке запроса (для каждого запроса) необходимо предоставить API-интерфейсы, к которым должен обращаться токен JWT.
  2. А также веб-приложение должно быть защищено с помощью аутентификации на основе форммеханизм, который должен работать на основе http сессии.

Этого можно добиться с помощью двух фильтров аутентификации.

Фильтр - 1 : для API остальных (JwtAuthTokenFilter)который должен быть без состояния и идентифицирован токеном авторизации, отправляемым в запросе каждый раз.
Filter - 2 : вам нужен другой фильтр (UsernamePasswordAuthenticationFilter). По умолчанию spring-security обеспечивает это, если вы настраиваете его с помощью http.formLogin(),Здесь каждый запрос идентифицируется связанным сеансом (JSESSIONID cookie). Если запрос не содержит допустимого сеанса, он будет перенаправлен на точку входа аутентификации (скажем, login-page).

Рекомендуемый шаблон URL
api-url-pattern    = "/api/**" [strictly for @order(1)]
webApp-url-pattern = "/**" [ wild card "/**" always used for higer order otherwise next order configuration becomes dead configuration]

Подход

  • Определить основной класс конфигурации с помощью @EnableWebSecurity

  • Создать два внутренних статических класса, которые должны расширяться WebSecurityConfigurerAdapter и аннотироваться@ Конфигурация и @ Заказ. Здесь порядок для конфигурации остальных API должен быть 1, а для конфигурации веб-приложения порядок должен быть больше 1

  • См. мой ответ в этой ссылке для получения более подробной информации с подробным объяснением и необходимым кодом. Не стесняйтесь просить загружаемую ссылку из репозитория github, если требуется.

Ограничение
Здесь оба фильтра будут работать рядом (Parellally). Я имею в виду из веб-приложения, даже если пользователь проходит аутентификацию по сеансу, он не может получить доступ к API без токена JWT.

РЕДАКТИРОВАТЬ
Для требований OP, где он не делаетне хочу определять какую-либо роль, но доступ к API разрешен для аутентифицированного пользователя. По его требованию изменена ниже конфигурация.

http.csrf().disable()
.antMatcher("/web/umgmt/**").authorizeRequests()
.antMatcher("/web/umgmt/**").authenticated() // use this
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...