Home > Software design >  Spring Security permitAll() doesn't work with Anonymous [Null authentication]
Spring Security permitAll() doesn't work with Anonymous [Null authentication]

Time:11-05

so i'm trying to implement a register and login mechanism using JWT. But somehow despite using permitAll() in security configuration. It still return 401 when unauthenticated user trying to access "/user/register"

Here is UserServiceImpl.java

package com.kelompok7.bukuku.user;

import com.kelompok7.bukuku.user.role.ERole;
import com.kelompok7.bukuku.user.role.Role;
import com.kelompok7.bukuku.user.verificationToken.VerificationToken;
import com.kelompok7.bukuku.user.verificationToken.VerificationTokenRepo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service @RequiredArgsConstructor @Transactional @Slf4j
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired
private final UserRepo userRepo;
@Autowired
private final VerificationTokenRepo verificationTokenRepo;
@Autowired
private JavaMailSender mailSender;

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepo.findByUsername(username);
        if(user == null){
            log.error("{}", SecurityContextHolder.getContext().toString());
            log.error("User not found in the database");
            throw new UsernameNotFoundException("User not found in the database");
        }
        else{
            log.info("User found in the database: {}", username);
        }
        Collection<SimpleGrantedAuthority> authorities = user.getAuthorities();
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
    }
    
    @Override
    public User register(User user) throws MessagingException, UnsupportedEncodingException {
        log.info("Saving new user {} to the database", user.getName());
        user.setPassword(encoder().encode(user.getPassword()));
        Set<Role> role = new HashSet<>();
        role.add(new Role(ERole.ROLE_USER));
        user.setRoles(role);
        user.setEnabled(false);
        userRepo.save(user);
        return user;
    }

}

Here is UserController.java

package com.kelompok7.bukuku.user;

import lombok.RequiredArgsConstructor;
import org.springframework.data.repository.query.Param;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.\*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.mail.MessagingException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;

    @PostMapping("/register")
    public ResponseEntity<User> register(@RequestBody User user) throws MessagingException, UnsupportedEncodingException {
        URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("user/register").toUriString());
        return ResponseEntity.created(uri).body(userService.register(user));
    }
}

Here is SecurityConfiguration.java

package com.kelompok7.bukuku.security;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    @Autowired
    private final RsaKeyProperties rsaKeys;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .cors(cors -> cors.disable())
                .authorizeRequests(auth -> auth
                        .antMatchers("/**").permitAll()
                        .anyRequest().permitAll()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .httpBasic(Customizer.withDefaults())
                .build();
    }

//    @Bean
//    public WebSecurityCustomizer webSecurityCustomizer() {
//        return (web) -\> web.ignoring()
//                .antMatchers("/\*\*");
//    }

    @Bean
    JwtDecoder jwtDecoder(){
        return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build();
    }
    
    @Bean
    JwtEncoder jwtEncoder(){
        JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }

}

And finally the log

2022-11-03 16:15:29.525 ERROR 19358 --- [nio-8081-exec-2] c.kelompok7.bukuku.user.UserServiceImpl  : SecurityContextImpl [Null authentication]
2022-11-03 16:15:29.525 ERROR 19358 --- [nio-8081-exec-2] c.kelompok7.bukuku.user.UserServiceImpl  : User not found in the database

I'm expecting that the request will go through and be processed, instead it seems it get caught in the SecurityFilterChain and get 401 Unauthorized instead. I've tried disabling CSRF and CORS but still failed. I've even just set permitAll() to anyRequest but somehow still getting 401.

The only thing to be working seems to be using webSecurityCustomizer and use web.ignoring(), but i've read that it will skip the securityFilterChain entirely so i'm not sure if it's safe. Is it safe? is it how it normally be done? Is there any better way?

Also, even if web.ignoring() work, i also wanted to know why the permitAll() doesn't work. Is it normal?

Thank you for your answer

CodePudding user response:

Have you tried giving full path in antmatcher

.antMatchers("user/register).permitAll()

?

CodePudding user response:

I'm really sorry, but turns out it was caused by my dumb mistake of including Authorization in the request when i shouldn't.

My original request :

POST /user/register HTTP/1.1
Host: localhost:8080
Authorization: Basic dXNlcjpwYXNzd29yZA==
Content-Type: application/json
Content-Length: 100

{
    "username": "user",
    "password": "password",
    "email": "[email protected]"
}

What it should be, and working :

POST /user/register HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Content-Length: 100

{
    "username": "user",
    "password": "password",
    "email": "[email protected]"
}

The SecurityContextHolder.getContext() is still null Authentication, but the permitAll() working correctly, so i guess it's normal behaviour. This whole time i suspect that was the cause so i've spent many days trying to solve it to no avail.

I appreciate all the attempt to help me. Thank you.

  • Related