Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"An Authentication object was not found in the SecurityContext" - UserDetailsService Setup in Spring Boot

In a Spring Boot application, I have an in-memory Spring Security setup. And it works as desired.

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("kevin").password("password1").roles("USER").and()
            .withUser("diana").password("password2").roles("USER", "ADMIN");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {

    http
            .httpBasic().and()
            .authorizeRequests()
            .antMatchers(HttpMethod.POST, "/foos").hasRole("ADMIN")
            .antMatchers(HttpMethod.PUT, "/foos/**").hasRole("ADMIN")
            .antMatchers(HttpMethod.PATCH, "/foos/**").hasRole("ADMIN")
            .antMatchers(HttpMethod.DELETE, "/foos/**").hasRole("ADMIN")
            .and()
            .csrf().disable();
  }
}

Now, I convert it to a database based approach with the following code.

@Entity
class Account {

  enum Role {ROLE_USER, ROLE_ADMIN}

  @Id
  @GeneratedValue
  private Long id;

  private String userName;

//  @JsonIgnore
  private String password;

  @ElementCollection(fetch = FetchType.EAGER)
  Set<Role> roles = new HashSet<>();
   ...
}

The repository:

@RepositoryRestResource
interface AccountRepository extends CrudRepository<Account, Long>{

  @PreAuthorize("hasRole('USER')")
  Optional<Account> findByUserName(@Param("userName") String userName);
}

The UserDetailsService:

@Component
class MyUserDetailsService implements UserDetailsService {

  private AccountRepository accountRepository;

  MyUserDetailsService(AccountRepository accountRepository){
    this.accountRepository = accountRepository;
  }

  @Override
  public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
    Optional<Account> accountOptional = this.accountRepository.findByUserName(name);
    if(!accountOptional.isPresent())
        throw new UsernameNotFoundException(name);

    Account account = accountOptional.get();
    return new User(account.getUserName(), account.getPassword(),
        AuthorityUtils.createAuthorityList(account.getRoles().stream().map(Account.Role::name).toArray(String[]::new)));
  }
}

And the modification of the WebSecurityConfigurerAdapter configuration:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  private MyUserDetailsService userDetailsService;

  SecurityConfiguration(MyUserDetailsService userDetailsService){
    this.userDetailsService = userDetailsService;
  }

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);  // <-- replacing the in-memory anthentication setup
  }
  ...
}

When I send the same request with a pair of username and password as the basic authentication as for the in-memory version, I, however, get the 401 error:

{
  "timestamp": 1489430818803,
  "status": 401,
  "error": "Unauthorized",
  "message": "An Authentication object was not found in the SecurityContext",
  "path": "/foos"
}

After reading through some related document and sample code, I can't see the cause of the error. What the error message says is that the user isn't in the Spring Security context. The line of AuthenticationManagerBuilder usage in the userDetailsService(userDetailsService) shall take care of setting up these users in the SecurityContext, isn't it?

The Spring Boot version is 1.4.3.RELEASE.

like image 229
vic Avatar asked Mar 13 '17 19:03

vic


1 Answers

remove this preAuthorize annotation from the crud repository. this method is used when ever you are trying to login but with the preauthorize annotation it expects a user to be logged in.

  @PreAuthorize("hasRole('USER')")
  Optional<Account> findByUserName(@Param("userName") String userName);

and i made some changes to the configure method in websecurityconfigureadapter you might need to allow access to the sign in and login urls in the configure method without login permission.

@Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeUrls()
        .antMatchers("/signup","/about").permitAll() // #4
        .antMatchers("/admin/**").hasRole("ADMIN") // #6
        .anyRequest().authenticated() // 7
        .and()
    .formLogin()  // #8
        .loginUrl("/login") // #9
        .permitAll(); // #5
  }

be careful when requiring admin permission to /foo/** these kind of urls this tells that all urls begin with foo are only allowed for admins.

like image 178
Priyamal Avatar answered Sep 27 '22 15:09

Priyamal