I am writing authentication using Spring Security and faced the following problem: after entering my username and password, I am redirected to defaultSuccessUrl("/"), however, the SecurityContextHolder.getContext() method.getAuthentication().getPrincipal().toString() returns anonimousUser. Why is this happening and how to fix it? I will be grateful for your help.
Here is the code I wrote. My pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>MyStore</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MyStore</name>
<description>MyStore</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
My WebSecurityConfig:
package com.example.mystore.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class WebSecurityConfig {
protected UserDetailsService userDetailsService;
public WebSecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public static final String[] possiblePatterns = new String[]{
"/",
"/registration",
"/category",
"/style.css"
};
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// .cors()
// .and()
// .csrf()
// .disable()
.authorizeHttpRequests(this::buildRequests)
.formLogin(this::formCustomizer)
.logout(LogoutConfigurer::permitAll)
.userDetailsService(userDetailsService)
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
protected AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry
buildRequests(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry requests) {
return requests.requestMatchers(possiblePatterns)
.permitAll()
.anyRequest()
.permitAll();
}
private FormLoginConfigurer<HttpSecurity> formCustomizer(FormLoginConfigurer<HttpSecurity> form) {
return form.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll();
}
}
User:
package com.example.mystore.model;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@Entity
@Table(name = "user")
@Getter
@Setter
@NoArgsConstructor
public class User implements UserDetails, GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false)
protected int id;
@Column(name = "username", nullable = false)
protected String username;
@Column(name = "password", nullable = false)
protected String password;
@Column(name = "role", nullable = false)
protected String role;
public User(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.stream(this.getRole().split(","))
.map(SimpleGrantedAuthority::new)
.toList();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public String getAuthority() {
return getRole();
}
}
UserRepository:
package com.example.mystore.repository;
import com.example.mystore.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByUsername(String username);
}
UserService:
package com.example.mystore.service;
import com.example.mystore.model.User;
import com.example.mystore.repository.UserRepository;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User saveUser(User user){
return userRepository.save(user);
}
public User getByUsername( String username)
{
Optional<User> optionalUser = userRepository.findByUsername(username);
return optionalUser.orElse(null);
}
}
JpaUserDetailService:
package com.example.mystore.service;
import com.example.mystore.model.User;
import com.example.mystore.repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class JpaUserDetailsService implements UserDetailsService {
UserRepository userRepository;
public JpaUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository
.findByUsername(username)
.map(this::userMapper)
.orElseThrow(() -> new UsernameNotFoundException("Username not found: " username));
}
protected User userMapper(User user) {
String password = user.getPassword();
user.setPassword("{noop}" password);
return user;
}
}
CodePudding user response:
This is happening because you have used a STATELESS sessionCreationPolicy in your web security config. The stateless creation policy means that the logged-in user details are not maintained on the server side. This is why you are getting anonymousUser from the principal. The server authenticates the client for each and every single request and checks the authority of the user. This is generally used in situations when the authentication is done using tokens to increase the scalability of the application.
The default value of the sessionCreationPolicy attribute is SessionCreationPolicy.IF_REQUIRED
. Using this should solve your problem. Let me know if it helps.