166

私の会社では、Spring MVC を評価して、次のプロジェクトで使用する必要があるかどうかを判断しています。今のところ、Spring Security モジュールを調べて、使用できる/使用する必要があるかどうかを判断しています。

私たちのセキュリティ要件は非常に基本的なものです。ユーザーは、ユーザー名とパスワードを入力するだけで、サイトの特定の部分にアクセスできます (自分のアカウントに関する情報を取得するなど)。サイトには、匿名ユーザーにアクセスを許可する必要があるページ (FAQ、サポートなど) がいくつかあります。

私が作成しているプロトタイプでは、認証されたユーザーのセッションに "LoginCredentials" オブジェクト (ユーザー名とパスワードのみを含む) を格納しています。たとえば、一部のコントローラーは、このオブジェクトがセッション内にあるかどうかを確認して、ログインしているユーザー名への参照を取得します。代わりに、この自家製のロジックを Spring Security に置き換えようと考えています。これにより、「ログインしているユーザーをどのように追跡するか」というあらゆる種類のものを削除できるという利点があります。および「ユーザーをどのように認証しますか?」私のコントローラー/ビジネスコードから。

Spring Security は、アプリのどこからでもユーザー名/プリンシパル情報にアクセスできるように、(スレッドごとの)「コンテキスト」オブジェクトを提供しているようです...

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

...ある意味で、このオブジェクトは(グローバル)シングルトンであるため、非常に春らしくないようです。

私の質問は次のとおりです。これが Spring Security で認証されたユーザーに関する情報にアクセスする標準的な方法である場合、Authentication オブジェクトを SecurityContext に挿入して、単体テストで認証されたユーザー?

各テスト ケースの初期化メソッドでこれを接続する必要がありますか?

protected void setUp() throws Exception {
    ...
    SecurityContextHolder.getContext().setAuthentication(
        new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
    ...
}

これは冗長すぎるようです。もっと簡単な方法はありますか?

オブジェクト自体はSecurityContextHolder非常に春らしくないようです...

4

11 に答える 11

212

通常の方法でそれを実行SecurityContextHolder.setContext()し、テストクラスで使用して挿入します。次に例を示します。

コントローラ:

Authentication a = SecurityContextHolder.getContext().getAuthentication();

テスト:

Authentication authentication = Mockito.mock(Authentication.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
于 2013-06-20T18:17:21.720 に答える
49

問題は、Spring Security が Authentication オブジェクトをコンテナー内の Bean として使用できるようにしないため、そのままで簡単に注入または自動配線する方法がないことです。

Spring Security の使用を開始する前に、コンテナーにセッション スコープの Bean を作成してプリンシパルを格納し、これを「AuthenticationService」(シングルトン) に注入してから、この Bean を現在のプリンシパルの知識が必要な他のサービスに注入していました。

独自の認証サービスを実装している場合は、基本的に同じことを行うことができます。「プリンシパル」プロパティを使用してセッションスコープの Bean を作成し、これを認証サービスに注入し、認証サービスに認証の成功時にプロパティを設定させてから、必要に応じて、認証サービスを他の Bean で使用できるようにします。

SecurityContextHolder を使用することについて、私はそれほど悪いとは思いません。けれど。私はそれが静的/シングルトンであり、Spring がそのようなものを使用することを思いとどまらせることを知っていますが、それらの実装は環境に応じて適切に動作するように注意します: サーブレット コンテナーでのセッション スコープ、JUnit テストでのスレッド スコープなど。本当の制限要因シングルトンの重要性は、さまざまな環境に対して柔軟性のない実装を提供する場合です。

于 2008-12-16T19:27:37.913 に答える
30

心配するのは当然です。静的メソッドの呼び出しは、依存関係を簡単にモックできないため、単体テストでは特に問題になります。これからお見せするのは、Spring IoC コンテナーに面倒な作業を任せて、きれいでテスト可能なコードを残す方法です。SecurityContextHolder はフレームワーク クラスであり、低レベルのセキュリティ コードを関連付けても問題ないかもしれませんが、UI コンポーネント (コントローラーなど) へのより適切なインターフェイスを公開することをお勧めします。

独自の「主要な」タイプを作成し、インスタンスをコンシューマーに注入します。2.x で導入されたSpring < aop:scoped-proxy /> タグは、リクエスト スコープ Bean 定義と組み合わされており、ファクトリ メソッドのサポートは、最も読みやすいコードへのチケットとなる可能性があります。

次のように機能します。

public class MyUserDetails implements UserDetails {
    // this is your custom UserDetails implementation to serve as a principal
    // implement the Spring methods and add your own methods as appropriate
}

public class MyUserHolder {
    public static MyUserDetails getUserDetails() {
        Authentication a = SecurityContextHolder.getContext().getAuthentication();
        if (a == null) {
            return null;
        } else {
            return (MyUserDetails) a.getPrincipal();
        }
    }
}

public class MyUserAwareController {        
    MyUserDetails currentUser;

    public void setCurrentUser(MyUserDetails currentUser) { 
        this.currentUser = currentUser;
    }

    // controller code
}

ここまで複雑なことはありませんよね?実際、おそらくこれのほとんどを既に実行しているはずです。次に、Bean コンテキストで、プリンシパルを保持するリクエスト スコープの Bean を定義します。

<bean id="userDetails" class="MyUserHolder" factory-method="getUserDetails" scope="request">
    <aop:scoped-proxy/>
</bean>

<bean id="controller" class="MyUserAwareController">
    <property name="currentUser" ref="userDetails"/>
    <!-- other props -->
</bean>

aop:scoped-proxy タグの魔法のおかげで、新しい HTTP リクエストが着信するたびに静的メソッド getUserDetails が呼び出され、currentUser プロパティへのすべての参照が正しく解決されます。単体テストは簡単になります。

protected void setUp() {
    // existing init code

    MyUserDetails user = new MyUserDetails();
    // set up user as you wish
    controller.setCurrentUser(user);
}

お役に立てれば!

于 2008-12-28T09:41:52.170 に答える
9

個人的には、PowermockをMockitoまたはEasymockと一緒に使用して、ユニット/統合テストで静的SecurityContextHolder.getSecurityContext()をモックします。

@RunWith(PowerMockRunner.class)
@PrepareForTest(SecurityContextHolder.class)
public class YourTestCase {

    @Mock SecurityContext mockSecurityContext;

    @Test
    public void testMethodThatCallsStaticMethod() {
        // Set mock behaviour/expectations on the mockSecurityContext
        when(mockSecurityContext.getAuthentication()).thenReturn(...)
        ...
        // Tell mockito to use Powermock to mock the SecurityContextHolder
        PowerMockito.mockStatic(SecurityContextHolder.class);

        // use Mockito to set up your expectation on SecurityContextHolder.getSecurityContext()
        Mockito.when(SecurityContextHolder.getSecurityContext()).thenReturn(mockSecurityContext);
        ...
    }
}

確かに、ここにはかなりのボイラープレートコードがあります。つまり、認証オブジェクトをモックし、SecurityContextをモックして認証を返し、最後にSecurityContextHolderをモックしてSecurityContextを取得しますが、非常に柔軟性があり、null認証オブジェクトなどのシナリオの単体テストが可能です。 (テスト以外の)コードを変更せずに

于 2012-03-07T21:03:26.710 に答える
7

この場合に static を使用することは、安全なコードを記述するための最良の方法です。

はい、静的は一般的に悪いです-一般的に、しかしこの場合、静的はあなたが望むものです. セキュリティ コンテキストはプリンシパルを現在実行中のスレッドに関連付けるため、最も安全なコードはスレッドから可能な限り直接的に静的にアクセスします。注入されたラッパー クラスの背後にアクセスを隠すことで、攻撃者はより多くの攻撃ポイントを得ることができます。コードにアクセスする必要はありません (jar が署名されている場合、コードを変更するのに苦労するでしょう)。必要なのは、構成をオーバーライドする方法だけです。これは、実行時に行うか、XML をクラスパスに挿入することができます。アノテーション インジェクションを使用しても、外部 XML でオーバーライドできます。このような XML は、実行中のシステムに不正なプリンシパルを挿入する可能性があります。

于 2010-02-04T12:59:51.983 に答える
4

全般的

それまでの間 (バージョン 3.2 以降、2013 年にはSEC-2298 のおかげで) 注釈@AuthenticationPrincipalを使用して認証を MVC メソッドに挿入できます。

@Controller
class Controller {
  @RequestMapping("/somewhere")
  public void doStuff(@AuthenticationPrincipal UserDetails myUser) {
  }
}

テスト

単体テストでは、明らかにこのメソッドを直接呼び出すことができます。を使用した統合テストでは、次のようにユーザーを挿入するためにorg.springframework.test.web.servlet.MockMvc使用できます。org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()

mockMvc.perform(get("/somewhere").with(user(myUserDetails)));

ただし、これは SecurityContext を直接埋めるだけです。テストでユーザーがセッションからロードされていることを確認したい場合は、これを使用できます。

mockMvc.perform(get("/somewhere").with(sessionUser(myUserDetails)));
/* ... */
private static RequestPostProcessor sessionUser(final UserDetails userDetails) {
    return new RequestPostProcessor() {
        @Override
        public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
            final SecurityContext securityContext = new SecurityContextImpl();
            securityContext.setAuthentication(
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
            );
            request.getSession().setAttribute(
                HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext
            );
            return request;
        }
    };
}
于 2015-11-16T11:52:58.703 に答える
2

ここで説明されている Spring の抽象テスト クラスとモック オブジェクトを見てみましょう。これらは、Spring 管理対象オブジェクトを自動配線する強力な方法を提供し、単体テストと統合テストを容易にします。

于 2008-12-11T19:20:09.893 に答える