Issue
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
Solution
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]"
}
SecurityContextHolder.getContext() still return null Authentication, but permitAll() is working correctly, so i guess it's normal behaviour. This whole time i suspected if 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.
Answered By - F.I.
Answer Checked By - Katrina (JavaFixing Volunteer)