“在 SecurityContext 中未找到身份验证对象” - Spring Boot 中的 UserDetailsS​​ervice 设置

vic*_*vic 5 spring-security spring-boot

在 Spring Boot 应用程序中,我有一个内存中的 Spring Security 设置。它可以按需要工作。

@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();
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,我使用以下代码将其转换为基于数据库的方法。

@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<>();
   ...
}
Run Code Online (Sandbox Code Playgroud)

存储库:

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

  @PreAuthorize("hasRole('USER')")
  Optional<Account> findByUserName(@Param("userName") String userName);
}
Run Code Online (Sandbox Code Playgroud)

用户详细信息服务:

@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)));
  }
}
Run Code Online (Sandbox Code Playgroud)

以及 WebSecurityConfigurerAdapter 配置的修改:

@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
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

但是,当我使用一对用户名和密码作为基本身份验证发送与内存版本相同的请求时,我收到了 401 错误:

{
  "timestamp": 1489430818803,
  "status": 401,
  "error": "Unauthorized",
  "message": "An Authentication object was not found in the SecurityContext",
  "path": "/foos"
}
Run Code Online (Sandbox Code Playgroud)

阅读了一些相关文档和示例代码后,我看不出错误的原因。错误消息说的是用户不在 Spring Security 上下文中。userDetailsS​​ervice(userDetailsS​​ervice) 中的 AuthenticationManagerBuilder 用法行应该负责在 SecurityContext 中设置这些用户,不是吗?

Spring Boot 版本是 1.4.3.RELEASE。

Pri*_*mal 5

preAuthorize从 中删除此注释crud repository。当您尝试登录但使用preauthorize注释期望用户登录时,将使用此方法。

  @PreAuthorize("hasRole('USER')")
  Optional<Account> findByUserName(@Param("userName") String userName);
Run Code Online (Sandbox Code Playgroud)

我对该configure方法做了一些更改websecurityconfigureadapter ,您可能需要允许在没有登录权限的情况下访问配置方法中的登录登录URL 。

@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
  }
Run Code Online (Sandbox Code Playgroud)

/foo/** 当需要此类url的管理员权限时要小心,这表明所有以 开头的 urlfoo只允许管理员使用。