Множественный адаптер безопасности - addFilter, поэтому не работает должным образом - PullRequest
2 голосов
/ 11 октября 2019

Я хотел бы иметь конфигурацию для API и MVC. Авторизация для API равна JWT, в то время как авторизация для MVC равна x509 client certificate (отдельные конфигурации работают хорошо).

Ожидаемое поведение:

  1. request / v2 / api/ ** будет отфильтрован с помощью фильтра, указанного в addFilterBefore, где jwt проверен и создан контекст
  2. Все остальные запросы будут использовать аутентификацию сертификата клиента ssl.

Как это работает сейчас -любой запрос запускается addFilterBefore и отклоняется из-за отсутствия токена jwt.

мой класс конфигурации:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter
{

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

        @Autowired
        private JwtRequestFilter jwtRequestFilter;

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

    @Configuration
    @Order(4)
    public class x509Authenticator extends X509AuthenticationServer
    {

        @Override
        protected void configure(HttpSecurity http) throws Exception
        {
            http.authorizeRequests().anyRequest().authenticated()
                .and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
                .userDetailsService(userDetailsService())
                .and().exceptionHandling().accessDeniedPage("/forbidden");

        }

        @Bean
        public UserDetailsService userDetailsService() 
        {
            return new UserDetailsService() 
            {
                @Override
                public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
                {
                    return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
                }
            }
        }
    }
}

1 Ответ

0 голосов
/ 15 октября 2019

Обратите внимание: предоставленная вами информация / код недостаточны. Поскольку вы не предоставили коды X509AuthenticationServer и JwtRequestFilter, я не могу сказать, что именно не так.

Предполагая, что это ваше срочное требование, пытающееся объяснить все возможные варианты.

Как это работает сейчас - любой запрос запускает addFilterBefore и отклоняется по причине отсутствия токена jwt.

Да, он пропустит бросок фильтра для любого запроса. Единственная вещь, которую вы должны позаботиться в фильтре, это проверить заголовок, если он есть, затем перейти к аутентификации JWT, иначе пропустите аутентификацию JWT ( Проверьте код класса моего фильтра, если блок ).

Поскольку ваша весенняя защита настроена на x509, объект аутентификации будет установлен субъектом сертификата с использованием регулярного выражения .x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")

  • Для X509 субъект запроса будет от субъекта сертификата клиента

  • Для запроса JWT, поскольку он состоит из сертификата сервера, принципал будет установлен из субъекта сертификата сервера. Теперь переопределите принципал, получив имя пользователя из токена JWT и предоставив права доступа, необходимые для этого пользователя.

Ниже заданного субъекта сертификата сервера и принципала, полученного от регулярного выражения, является "Правин"
Subject: EMAILADDRESS=nlpraveennl@gmail.com, CN=Praveen, OU=Answer, O=StackOverflow, L=BENGALURU, ST=KA, C=IN
Ниже приведены данные сертификата клиента и посмотрите на предмет. Принципал, полученный от регулярных выражений: "Веданта"
[
  Version: V1
  Subject: EMAILADDRESS=vedanta@gmail.com, CN=vedanta, OU=some unit, O=some comapany, L=San diego, ST=CA, C=US
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun RSA public key, 4096 bits
  modulus: 817322829240490927539679977649457347113079769252522527800627995161015683461174014845370356721299781132807751160825513223084246773438383264091041820195243162665509891042158368380019167357193944418022840037166629645037683573001749034046239097004884878919482801656531508586406926436161114752390080130151471441072696492233900694526232243678291240183028778626646828176141484812179759739175161094296327915519918417719601837669497535100237474334129104633049344560273440876841464270970566837970617275659841740418106346304917775719024288908219661239022501256531355020518204348890248534705892780384737192506755315365883201062844995260628679776392945804218936346471987181753817158168697446990117525268883019172878686864407803654159029932574084328051385395658876802073285425506794524283532870369321962543974623256690683454729681498079311854836252232196330777070325603711372859419866719120909302184446204160932841096818392866335724975192880990513954025684813917171551040959488266330875554906980729293764667616531866907648957912796417658137011975409928985274876102141544954375822680116494691313951508398067207453068155470604238471192479465455519868221517071480577973522583550201343549400093003081375335896091143428006322327934212827941149315676683
  public exponent: 65537
  Validity: [From: Tue Oct 15 09:04:58 IST 2019,
               To: Fri Oct 09 09:04:58 IST 2020]
  Issuer: EMAILADDRESS=nlpraveennl@gmail.com, CN=Praveen, OU=Answer, O=StackOverflow, L=BENGALURU, ST=KA, C=IN
  SerialNumber: [    baafa655 dd56be52]

]



Что еще может быть не так

1. Для API версии 2 (JWT) : Вы не отправляете сертификат сервера в своем запросе. Я согласен, ему не нужен клиентский сертификат, но сервер принимает связь только через SSL-соединение.

  • Вы не можете проверить его с помощью curl cmd. Это работает, только если ваш SSL подписан CA.

  • Вы не можете проверить это в Google Chrome, так как Google Chrome также нуждается в SSL, подписанном CA.

Взломать для тестирования:
Использовать сертификат сервера и ключ безопасности вместе с вашим токеном JWT, как указано ниже

curl -ik --cert server.crt --key serverPrivateKey.pem -H "Authorization:Bearer jwtToken" "https://localhost:8443/v2/api/yourpath"

Это должно работать.

2. для API, отличных от v2 (без JWT) : Вы не включаете сертификат клиента в запрос. Правильный способ проверить это.

curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1/hello"
curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1.5/hello"

Я проверил с моей стороны, он работает для меня без каких-либо проблем. Единственное, что у меня есть

К вашему сведению, добавив сюда код.

конфигурация
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig 
{

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

        @Autowired
        private JwtAuthenticationTokenFilter jwtRequestFilter;

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

    @Configuration
    @Order(4)
    public static class X509Configuration extends WebSecurityConfigurerAdapter
    {

        @Override
        protected void configure(HttpSecurity http) throws Exception
        {
            http.authorizeRequests().anyRequest().authenticated()
                .and().x509().subjectPrincipalRegex("CN=(.*?)(?:,|$)")
                .userDetailsService(userDetailsService())
                .and().exceptionHandling().accessDeniedPage("/forbidden");

        }

        @Bean
        public UserDetailsService userDetailsService() 
        {
            return new UserDetailsService() 
            {
                @Override
                public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
                {
                    System.out.println("[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]");
                    System.out.println(username);
                    System.out.println("[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]");
                    return new User(username, "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
                }
            };
        }
    }
}
JWT-фильтр токенов аутентификации
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException
    {
        System.out.println("(((((((((((((((())))))))))))))");
        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)
                {
                    // here username should be validated with database and get authorities from database if valid
                    // Say just to hard code sending same username received
                    if (jwtTokenUtil.validateToken(authToken, username))
                    {
                        List<GrantedAuthority> authList = new ArrayList<>();
                        authList.add(new SimpleGrantedAuthority("ROLE_APIUSER"));

                        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, null, authList);
                        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);
    }
}
Класс JwtTokenUtility
@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));
    }

    //To generate token for testing
    public static void main(String[] args)
    {
        JwtTokenUtil tu = new JwtTokenUtil();
        String s1 = tu.generateToken("hello");
        System.out.println(s1);
        String user = tu.getUsernameFromToken(s1);
        System.out.println(user);
    }
}
Контроллер / API
@RestController
public class HelloController
{
    //Client certificate
    @RequestMapping(path = "/v1/hello")
    public String helloV1()
    {

        return "HELLO Version 1 - "+((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
    }

    //Client certificate
    @RequestMapping(path = "/v1.5/hello")
    public String helloV1Dot5()
    {
        return "HELLO Version 1.5 - "+((User)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
    }

    //Jwt
    @RequestMapping(path = "/v2/hello")
    public String helloV2()
    {
        return "HELLO Version 2 - "+SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}
...