-1

I'm a newbie when talking about Spring Security, specially with JWT and CORS, so I apologise in advance if I don't speak clearly about the matter.

We were asked to make an application which simulates a private clinic website, on which patients can make an appointment with a doctor and buy products from the pharmacy. Doctors can introduce information in the database about their patients. Our project has a Restful API as well, which can be accessed through a mobile app (or Postman). What the API does is showing a list of products we have stored in the database.

All users can log in through a log in form, which uses Spring Security. On the other hand, if we wanted to retrieve the information of our API, CORS and JWT are used in addition to Spring Security.

The problem comes when I set up a custom authorization filter our teacher gave us to do this (I have commented the line that does this). We can access our API using Postman perfectly: we log in with the admin user and pass the authorization token to our API route, and in return we get the list of products. But when the filter is working, we can no longer use the log in form of our website to authenticate. The whole proccess goes like this:

  1. The application starts at the main page (localhost:8080/inicio).
  2. In the main page there is a 'Login' button which appears when the user is not authenticated . Clicking it takes us to the log in form.
  3. Once in the log in form (localhost:8080/auth/login) we fill all the fields neccesary for us to log in as an user from the database (in this case, username: admin, password: admin).
  4. We submit the form, which takes us to the petition in charge of the authentication proccess (localhost:8080/login/login-post).
  5. At the end of the proccess, we are redirected back to the main page. The "Login" button should appear as "Logout" when the user is authenticated. But it doesn't. We cannot navigate to other pages the authenticated user should have access to neither.

No error messages are provided by the console, and all it does is taking me back to the main page without having the user authenticated.

This is my Spring Security configuration class:

@Autowired
@Qualifier("userService")
private UserService userService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}

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

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

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .csrf().disable()
    // .addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
    .authorizeRequests()
        .antMatchers("/", "/css/**",  "/img/**", "/js/**", "/vendor/**", "/inicio/**", "/pacientes/altaPaciente/**", "/pacientes/addPaciente/**", "/auth/**").permitAll()
        .antMatchers(HttpMethod.GET, "/authRest/**").permitAll()
        .antMatchers(HttpMethod.POST, "/authRest/**").permitAll()
        .anyRequest().authenticated()
        .and()
    .formLogin()
        .loginPage("/auth/login")
        .defaultSuccessUrl("/inicio/", true)
        .loginProcessingUrl("/auth/login-post")
        .permitAll()
        .and()
    .logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/auth/login?logout")
        .permitAll();
}

And my JWT Authorization filter:

private final String HEADER = "Authorization";
private final String PREFIX = "Bearer ";
private final String SECRET = "mySecretKey";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {
    try {
        if (checkJWTToken(request, response)) {
            Claims claims = validateToken(request);
            if (claims.get("authorities") != null) {
                setUpSpringAuthentication(claims);
            } else {
                SecurityContextHolder.clearContext();
            }
        } else {
            SecurityContextHolder.clearContext();
        }
        chain.doFilter(request, response);
    } catch(ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
        return;
    }
}

private Claims validateToken(HttpServletRequest request) {
    String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
    return Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(jwtToken).getBody();
}

private void setUpSpringAuthentication(Claims claims) {
    @SuppressWarnings("unchecked")
    List<String> authorities = (List<String>) claims.get("authorities");
    
    UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
            claims.getSubject(), 
            null, 
            authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
    );
    SecurityContextHolder.getContext().setAuthentication(auth);
}

private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) {
    String authenticationHeader = request.getHeader(HEADER);
    if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX)) {
        return false;
    }
    return true;
}

EDIT: As requested, here are the logs I get when I try to log in as an existing user in the database using the web form: https://pastebin.com/7SYX2MZF

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
Dancli
  • 3
  • 3
  • why are you using a custom jwt filter when there is already one built into spring security? https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-jwt-architecture – Toerktumlare Feb 13 '21 at 13:15
  • That is the one our teacher has showed us to use to make JWT Authentication work, we did not even knew Spring Security has one by default to begin with. But, if it's already defined in the Spring Security configuration, then it's doing nothing to my Resful API's security, I can use whichever authorization token I want to have access to it. – Dancli Feb 13 '21 at 14:05
  • in its current state this question needs more clarity `the only one working is the JWT Authentication` can mean anything, what is `EXACTLY` not working, clear steps to reproduce. Can you login? Post spring security debug logs, and post what your requests look like. With out logs or steps to reproduce, no one will be able to answer. – Toerktumlare Feb 13 '21 at 14:38
  • I will try my best to redo my question, so it becomes more clear but, as I said, this is a topic I'm not very familiarized with, and it's quite difficult for me to understand. – Dancli Feb 13 '21 at 16:41
  • you can start out by activating debug logs https://stackoverflow.com/a/47729991/1840146 restart the server, do your requests, then update the question with the debug logs – Toerktumlare Feb 13 '21 at 17:19
  • I have provided a debug log, and I changed the explanation of my problem too. I hope this is more clear than before. – Dancli Feb 13 '21 at 18:35
  • what do you mean `But when the filter is working, we can no longer use the log in form of our website` is it when you are logging it it doesnt work, when you are requesting what url. Please dont write vague descriptions of what isn't working. As mentioned before, which you have not posted, you need to write clear steps to reproduce – Toerktumlare Feb 13 '21 at 20:31
  • when you add the filter, what that filter does is check if there is a token in the request, if there is no token, you remove the current authenticated user by calling `SecurityContextHolder.clearContext()` so as soon as you log in, the filter probably removes you. – Toerktumlare Feb 13 '21 at 20:58
  • That makes more sense to what is happening. Is there a way I can bypass the filter when I'm trying to authenticate in the login form? – Dancli Feb 13 '21 at 21:07
  • Why even clear the context? – Toerktumlare Feb 14 '21 at 08:27
  • I think that removing `SecurityContextHolder.clearContext();` was what I had to do to make the entire thing work! Although it retrieves an internal server error when I try to access the API with the wrong token for a second time, instead of a forbidden error. Thank you for your assistance, really, you helped the rest of the class with this. And sorry for all the incoveniences I caused! – Dancli Feb 14 '21 at 10:39

1 Answers1

0

the fault is probably (after discussion)

somewhere here:

if (checkJWTToken(request, response)) {
    Claims claims = validateToken(request);
    if (claims.get("authorities") != null) {
         setUpSpringAuthentication(claims);
    } else {
        SecurityContextHolder.clearContext();
    }
} else {
        SecurityContextHolder.clearContext();
}

a check is done in checkJWTToken for the presence of a Authorization header and if there is none, the current SecurityContext is cleared, meaning it will remove whatever principal present.

This removes whomever is previously logged in, which in turn the principal that is constructed when logging in initially.

So you login, the securitycontext is populated by the principal, then it's suddenly removed in the next filter.

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54