-1

I'm using Spring Core, Spring MVC, Spring REST, JWT.

Hello everyone!

I'm learning Spring Security and I encountered a problem.

I would like to make an authentication based on JWT; I have a Rest controller with 3 simple methods:

The first method should be available for everyone, the second only for signed in users, and the third only for admins. And although that is working, I have another problem.

The problem is that my filter throws(not directly, because the null is returned by token = header.substring(7)) a NullPointerException each time after starting, even when it doesn't get any request yet(I can be wrong, I hope that code will clarify it). Is it possible to set a filter to process only chosen requests or maybe my approach is wrong?

Thanks for the help!

Error:

java.lang.NullPointerException
    at com.sample.config.JwtFilter.getAuthenticationByToken(JwtFilter.java:50)
    at com.sample.config.JwtFilter.doFilterInternal(JwtFilter.java:36)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:678)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.base/java.lang.Thread.run(Unknown Source)

My JwtFilter:

public class JwtFilter extends BasicAuthenticationFilter {

    public JwtFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);

    }

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

        SecurityContextHolder.getContext().setAuthentication(authResult);

        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthenticationByToken(String header) {
            String token = header.substring(7);
            Jws<Claims> claims = Jwts.parser().setSigningKey("fQx]n}YmL)WuHjL".getBytes()).parseClaimsJws(token);

            String username=claims.getBody().get("name").toString();
            String role = claims.getBody().get("role").toString();
    return new UsernamePasswordAuthenticationToken(username,null,Collections.singleton(new SimpleGrantedAuthority(role)));      
    }

}

My configuration:


@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return new AuthenticationManager() {

            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                return null;
            }
        };
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/api/test2").authenticated()      .antMatchers("/api/test3").hasRole("ADMIN")
        .and()
        .addFilter(new JwtFilter(authenticationManager())); }
}

public class SpringSecurityWebInitializer extends AbstractSecurityWebApplicationInitializer{

}

The controller:


@RestController
@RequestMapping("/api")
public class TestApi {

    @GetMapping("/test1")
    public String test1()
    {
        return "test1";//for everyone
    }

    @GetMapping("/test2")
    public String test2()
    {
        return "test2";//only for logged in
    }

    @GetMapping("/test3")
    public String test3()
    {
        return "test3";//only for admins 
    }   
}

NatFar
  • 2,090
  • 1
  • 12
  • 29
kolo yolo
  • 23
  • 9

1 Answers1

1

The way you have your application configured, your filter will be invoked for every request because the security FilterChain "applies" to every request.

This line:

http.authorizeRequests()
        .antMatchers("/api/test2").authenticated()      
        .antMatchers("/api/test3").hasRole("ADMIN")
        ...

... does not prevent a request from going through the FilterChain. It simply allows a request matching those paths to pass through the filters without being authenticated. But every filter you register (like JwtFilter) will be invoked.

Solution

You could disable the entire FilterChain for certain requests by overriding the configure(WebSecurity) method in WebSecurityConfigurerAdapter:

@Override
public void configure(WebSecurity web) {
    web.ignoring().mvcMatchers(...);
}

But in your case, it's better to change your filter so that it skips certain requests based on the path of the request coming in. You could do something like:

public class JwtFilter extends BasicAuthenticationFilter {
    private AntPathRequestMatcher filterPath;

    // the path you provide will restrict the filter to only be "applied"
    // to requests with that path
    public JwtFilter(AuthenticationManager authenticationManager, String path) {
        super(authenticationManager);
        this.filterPath = new AntPathRequestMatcher(path);
    }

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

        // the filter will do nothing if the request doesn't match
        if(!filterPath.matches(request) {
            chain.doFilter(request, response);
        }

        // your filter logic goes here...

        chain.doFilter(request, response);
    }

    // ...
}


NatFar
  • 2,090
  • 1
  • 12
  • 29
  • Thank you very much! Based on your advice, I made a little correction: if (header == null) { chain.doFilter(request, response); } And now it is working! – kolo yolo Mar 02 '20 at 16:31