2
votes

I have an API which needs to be secured in two different ways:

1) Using JWT for all request URL's other than 1 which needs to be secured with Basic Auth

2) Basic Auth for one url.

I have setup security configurations for both JWT and Basic Auth. My problem is that when I make a request to the Basic Authenticated URL using a valid username and password, It successfully authenticates me and does it's job of storing data within in cassandra.

I then expect to have to generate a token for ALL other request URL's via /api/login and add it to the Authorization: Bearer {Token} header..

However, if ive been authenticated via Basic Auth, I can then access the other URL's (protected by JWT auth) without even having a token in the request.

When I access the JWT protected URL's without authenticating with Basic Auth, I have to send the token in the header and it works as expected..

Should I expect this? As I believe even though ive authenticated via basic auth for one endpoint, I should still have to send tokens in the request for all other protected JWT endpoints..

I have found this answer: SpringBoot multiple authentication adapter

and also this article: https://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/#multiple-httpsecurity

and tried implementing the solutions, but the problem as explained still occurs.

The security config class is as follows:

@Configuration
@EnableWebSecurity
public class SecurityHttpConfig extends WebSecurityConfigurerAdapter {

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

        @Value("${basic.auth.user}")
        private String basicAuthUsername;

        @Value("${basic.auth.password}")
        private String basicAuthPassword;

        @Value("${crashboxx.consume.endpoint}")
        private String crashBoxxConsumeEndpoint;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
                    .httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);// We don't need sessions to be created.
        }

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

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
            auth.inMemoryAuthentication().withUser(basicAuthUsername).password(encoder.encode(basicAuthPassword))
                    .roles("ADMIN");
        }

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

    @Configuration
    @Order(2)
    public static class JwtWebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        private JwtAuthenticationEntryPoint unauthorizedHandler;

        @Autowired
        private JwtAuthenticationProvider jwtAuthenticationProvider;

        // Any endpoints that require no authorization should be added here..
        @Value("${api.login.endpoint}")
        private String loginEndpoint;

        @Autowired
        public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
            authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
        }

        @Bean
        public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
            return new JwtAuthenticationTokenFilter();
        }

        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests().antMatchers("/api/login").permitAll().anyRequest().authenticated();

            httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
            httpSecurity.headers().cacheControl();
        }
    }

With the BasicAuthEntryPoint class:

public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    private static final Gson gson = new Gson();

    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response,
            final AuthenticationException authException) throws IOException, ServletException {
        // Authentication failed, send error response.
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        writer.println(gson.toJson("HTTP Status 401 : " + authException.getMessage()));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        setRealmName("Realm");
        super.afterPropertiesSet();
    }

Also the JWT impl:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

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

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestHeader = request.getHeader(tokenHeader);
        // Ensure Auth Header contains 'Bearer'
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            String authToken = requestHeader.substring(7);
            JwtAuthentication authentication = new JwtAuthentication(authToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }

I hope this has made sense.. If there are any more questions please let me know, but cant seem to get round this one.

I have added the 'special case' first which is the one url for basic auth, but still does not make any differences.

Thanks

2
Please mention your loginEndpoint URL and crashBoxxConsumeEndpoint URLPraveenKumar Lalasangi
@PraveenKumarLalasangi I have updated the code with the url's hardcodedAlexander Dunn
What is Your all request URL's pattern? /v1/crash/**?PraveenKumar Lalasangi
Let me know URL's protected by JWT authPraveenKumar Lalasangi

2 Answers

3
votes

Code you posted in Security configuration of @Order(1)

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
            .httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

If this is the exact code you are using then your @Order(2) configuration will not be consulted. It will be dead configuration.
Let me explain!
http.authorizeRequests() == http.antMatcher("/**").authorizeRequests()

In your first configuration you are using a wildcard and your configuration result is

  • /v1/crash/consumeCrashBoxxEvent accessed if user is authenticated and has role ADMIN
  • Rest of URL's accessed if user is authenticated

Let me guess what is happening!
1. You are hitting URL /v1/crash/consumeCrashBoxxEvent or any URL you will be prompted for basic authentication.
2. After successful authentication you can access any URL because you are a authenticated user.

However, if ive been authenticated via Basic Auth, I can then access the other URL's (protected by JWT auth) without even having a token in the request.

Because as i have told you can access any URL because you are a authenticated user

When I access the JWT protected URL's without authenticating with Basic Auth, I have to send the token in the header and it works as expected

Check without token you can access or not. Because once if you login by Basic Authentication there is no logout from server side(even if you restart the server). You can achieve logout only if you close browser. So you test it by closing and starting browser again. And test it by not sending JWT token.
Also ensure your request reaching JwtAuthenticationTokenFilter, Put debug logs to verify.

As in your question there are lot of abstractions, it is very hard to predict exactly what is happening unless you post your complete code.

Let me know in comments if my predictions deviated from actual.

1
votes

This was solved by using the information provided in the answer above from Praveen Kumar Lalasangi.

A small change to the configure method did the trick.. The updates are:

@Override
    protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
      .antMatcher(crashBoxxConsumeEndpoint).authorizeRequests().anyRequest()
      .hasRole("ADMIN")
      .and().httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint())
      .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }