2017-03-13 8 views
2

Spring起動アプリケーションでは、メモリ内のSpring Security設定があります。それは必要に応じて動作します。"SecurityContextにAuthenticationオブジェクトが見つかりませんでした" - SpringブートのUserDetailsS​​ervice設定

@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(); 
    } 
} 

ここでは、次のコードを使用してデータベースベースのアプローチに変換します。

@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<>(); 
    ... 
} 

リポジトリ:

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

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

UserDetailsS​​ervice:

@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))); 
    } 
} 

そして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 
    } 
    ... 
} 

私は同じ要求を送信します「私ができる、いくつかの関連ドキュメントとサンプルコードを読んだ後

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

基本認証では、メモリ版用としてとしてユーザー名とパスワードのペアで、私は、しかし、401エラーが出ますエラーの原因を確認してください。エラーメッセージは、ユーザーがSpring Securityのコンテキストにないことを示しています。 userDetailsS​​ervice(userDetailsS​​ervice)内のAuthenticationManagerBuilderの使用行は、SecurityContextでこれらのユーザーを設定する必要がありますか?

スプリングブートバージョンは1.4.3.RELEASEです。

答えて

1

crud repositoryからpreAuthorize注釈を削除します。今まであなたがそれはユーザーがログインすることを期待し​​注釈を使ってログインしようとしているときに、この方法は

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

。使用されていると私はあなたがアクセスを許可する必要があるかもしれませんwebsecurityconfigureadapterconfigure方法にいくつかの変更を行いました記号ログイン URL をログイン許可なしに設定します。これはすべてのURLは、管理者のみに許可されているfooで始まることを告げる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 
    } 

/foo/**にこれらの種類を管理者権限を必要とする場合は注意が

+0

入力いただきありがとうございます。 preAuhorizeアノテーションを削除した後で動作します。エラーメッセージは認証に関するものですが、承認に関するものではありません。また、エラー応答メッセージに記載されているパスはfooについてですが、アカウントではありません。 URLのセキュリティ設定に関しては、httpメソッドで定義することもできます。 – vic

1

1つの問題は、リポジトリファインダに@PreAuthorize("hasRole('USER')")という注釈が付けられていることです。このメソッドは、リクエストが(完全に)認証される前に呼び出されるため、チェックに合格することはなく、適切な資格情報を渡しても例外が発生します。

もう1つのポイントは、本当にあなたのユーザーアカウントデータをReSTリソースとして公開したいのですか?現時点では、curl http://localhost:8080/accountsのようなリクエストは、匿名ユーザーに対しても、すべてのユーザーデータをJSON + HALレスポンスとして返します。

それはのようなものを返します:

{ 
    "_embedded" : { 
    "accounts" : [ { 
     "userName" : "admin", 
     "password" : "admin", 
     "roles" : [ "ROLE_USER", "ROLE_ADMIN" ], 
     "_links" : { 
    "self" : { 
     "href" : "http://localhost:8080/accounts/1" 
    }, 
    "account" : { 
     "href" : "http://localhost:8080/accounts/1" 
    } 
     } 
    } ] 
    }, 
    "_links" : { 
    "self" : { 
     "href" : "http://localhost:8080/accounts" 
    }, 
    "profile" : { 
     "href" : "http://localhost:8080/profile/accounts" 
    }, 
    "search" : { 
     "href" : "http://localhost:8080/accounts/search" 
    } 
    } 
} 

だから @RepositoryRestResource@Repositoryは、あなたは自分の要求をのauthするデータベーステーブルを使用したい場合は移動するための方法で使用して、ここでは危険です。

詳細は、documentationを参照してください。

あなたのコードが示唆しているように(あなたのAccountクラスの注釈@JsonIgnore注釈を使用して)応答からパスワードを削除したとしても、ユーザー名や役割などのセキュリティ関連情報が公開されます。それに加えて、Entityを悪用してレスポンスオブジェクトを形作るのは悪い習慣です。代わりに代わりにProjectionsを使用してください。

/accountsエンドポイントを維持したい場合は、少なくとも.antMatchers("/accounts/**").hasRole("ADMIN")を設定に追加することで保護する必要があります。

+0

あなたの入力をありがとう、ケビン。そして、情報に感謝します。アカウントエンティティのセキュリティ設定に関して、私はここですべてのコードを持っていました。私はエンドポイントのセキュリティ設定をしています。 ADMINロールのみがこれらのエンドポイントにアクセスすることができません。誰でも新しいアカウントを作成し、一般的な使用のために編集することができます。 – vic

+0

私は、より洗練されたセキュリティはコントローラでのみ行うことができることに同意します。たとえば、作成操作と編集操作では、同じリポジトリ・メソッドsaveが呼び出されます。誰もがユーザーデータを作成することを許可されますが、ユーザー自身またはADMINロールを持つユーザーのみがユーザーデータを編集できます。 – vic

+0

:)主なことは、あなたがリスクを認識していることです。 –

0

あなたの本当の問題はUserDetailService.loadUseByUsernameにある - そこにあなたが呼び出す:

Optional<Account> accountOptional = this.accountRepository.findByUserName(name); 

と、それは、TE認証プロセス中に行われています。しかし、 accountRepositoryメソッドには PreAuthorizeと注釈が付けられ、 contextからのみ呼び出すことができ、 Authentication Objectが含まれます。

はそれを理解するために簡単なテストを実行します。

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

    @PreAuthorize("hasRole('USER')") 
    Page<Account> findAll(Pageable pageable); 

    Optional<Account> findByUserName(@Param("userName") String userName); 
} 

を次にあなたが正しく、すべてのユーザーのために照会することができることを、確認してください。唯一の問題は、の再帰の性質findByUserNameの方法です。

乾杯!

関連する問題