web-dev-qa-db-ja.com

Spring Security / SpringMVCで認証済みユーザーを手動で設定する方法

新しいユーザーが「新しいアカウント」フォームを送信した後、後続のページでログインする必要がないように、そのユーザーを手動でログインしたいと思います。

春のセキュリティインターセプターを通過する通常のフォームのログインページは正常に動作します。

New-account-formコントローラーで、UsernamePasswordAuthenticationTokenを作成し、SecurityContextで手動で設定しています。

SecurityContextHolder.getContext().setAuthentication(authentication);

同じページで、後でユーザーがログインしていることを確認します。

SecurityContextHolder.getContext().getAuthentication().getAuthorities();

これは、認証で以前に設定した権限を返します。すべては順調です。

しかし、次にロードするページで同じコードが呼び出されると、認証トークンはUserAnonymousになります。

前のリクエストで設定した認証を保持しなかった理由がわかりません。何かご意見は?

  • セッションIDが正しくセットアップされていないことに関係があるでしょうか?
  • 認証を何らかの形で上書きしている可能性があるものはありますか?
  • おそらく、認証を保存するために別の手順が必要なのでしょうか?
  • または、何らかの方法で単一の要求ではなく、セッション全体で認証を宣言するために必要なことはありますか?

ここで何が起こっているかを見るのに役立つかもしれない考えを探しています。

101
David Parks

しばらく前と同じ問題がありました。詳細は思い出せませんが、次のコードでうまくいきました。このコードは、Spring Webflowフロー内で使用されるため、RequestContextクラスとExternalContextクラスです。ただし、最も重要な部分はdoAutoLoginメソッドです。

public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
                           RequestContext requestContext,
                           ExternalContext externalContext) {

    try {
        Locale userLocale = requestContext.getExternalContext().getLocale();
        this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
        String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
        String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
        doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
        return "success";

    } catch (EmailAddressNotUniqueException e) {
        MessageResolver messageResolvable 
                = new MessageBuilder().error()
                                      .source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
                                      .code("userRegistration.emailAddress.not.unique")
                                      .build();
        requestContext.getMessageContext().addMessage(messageResolvable);
        return "error";
    }

}


private void doAutoLogin(String username, String password, HttpServletRequest request) {

    try {
        // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        token.setDetails(new WebAuthenticationDetails(request));
        Authentication authentication = this.authenticationProvider.authenticate(token);
        logger.debug("Logging in with [{}]", authentication.getPrincipal());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    } catch (Exception e) {
        SecurityContextHolder.getContext().setAuthentication(null);
        logger.error("Failure in autoLogin", e);
    }

}
61

私は他の完全なソリューションを見つけることができなかったので、私は私のものを投稿すると思った。これはちょっとしたハッキン​​グかもしれませんが、上記の問題の問題を解決しました。

public void login(HttpServletRequest request, String userName, String password)
{

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);

    // Authenticate the user
    Authentication authentication = authenticationManager.authenticate(authRequest);
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(authentication);

    // Create a new session and add the security context.
    HttpSession session = request.getSession(true);
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
60
Stuart McIntyre

最終的に問題の根本を突き止めました。

セキュリティコンテキストを手動で作成すると、セッションオブジェクトは作成されません。リクエストの処理が終了したときのみ、Spring Securityメカニズムはセッションオブジェクトがnullであることを認識します(リクエストの処理後にセキュリティコンテキストをセッションに保存しようとしたとき)。

リクエストの最後に、Spring Securityは新しいセッションオブジェクトとセッションIDを作成します。ただし、この新しいセッションIDは、ブラウザーへの応答が行われた後、要求の最後に発生するため、ブラウザーに送信されることはありません。これにより、次のリクエストに以前のセッションIDが含まれる場合、新しいセッションID(および手動でログオンしたユーザーを含むセキュリティコンテキスト)は失われます。

15
David Parks

デバッグロギングをオンにして、何が起きているかをより正確に把握します。

セッションCookieが設定されているかどうかは、ブラウザー側のデバッガーを使用してHTTP応答で返されたヘッダーを確認することで確認できます。 (他の方法もあります。)

1つの可能性は、SpringSecurityが安全なセッションCookieを設定しており、要求された次のページに「https」URLではなく「http」URLが含まれていることです。 (ブラウザは「http」URLのセキュアCookieを送信しません。)

5
Stephen C

Servlet 2.4の新しいフィルタリング機能は、フィルターがアプリケーションサーバーによる実際のリクエスト処理の前後のリクエストフローでのみ動作できるという制限を基本的に緩和します。代わりに、Servlet 2.4フィルターは、すべてのディスパッチポイントでリクエストディスパッチャーと対話できるようになりました。これは、Webリソースが別のリソース(たとえば、同じアプリケーション内のJSPページにリクエストを転送するサーブレット)にリクエストを転送する場合、リクエストがターゲットリソースによって処理される前にフィルターが動作できることを意味します。また、Webリソースに他のWebリソース(たとえば、複数の他のJSPページからの出力を含むJSPページ)からの出力または機能が含まれる場合、サーブレット2.4フィルターは、含まれる各リソースの前後で機能します。 。

その機能をオンにするには、次のものが必要です。

web.xml

<filter>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter>  
<filter-mapping>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <url-pattern>/<strike>*</strike></url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

RegistrationController

return "forward:/login?j_username=" + registrationModel.getUserEmail()
        + "&j_password=" + registrationModel.getPassword();
5
AlexK

私はextjsアプリケーションをテストしようとしていましたが、testingAuthenticationTokenを正常に設定した後、これは明らかな原因なしに突然動作を停止しました。

上記の答えを得ることができなかったので、私の解決策は、テスト環境でこの春を少しスキップすることでした。私はこのような春の周りに縫い目を導入しました:

public class SpringUserAccessor implements UserAccessor
{
    @Override
    public User getUser()
    {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        return (User) authentication.getPrincipal();
    }
}

ここでは、ユーザーはカスタムタイプです。

それから、テストコードがスプリングアウトを切り替えるためのオプションを備えたクラスにそれをラップします。

public class CurrentUserAccessor
{
    private static UserAccessor _accessor;

    public CurrentUserAccessor()
    {
        _accessor = new SpringUserAccessor();
    }

    public User getUser()
    {
        return _accessor.getUser();
    }

    public static void UseTestingAccessor(User user)
    {
        _accessor = new TestUserAccessor(user);
    }
}

テストバージョンは次のようになります。

public class TestUserAccessor implements UserAccessor
{
    private static User _user;

    public TestUserAccessor(User user)
    {
        _user = user;
    }

    @Override
    public User getUser()
    {
        return _user;
    }
}

呼び出しコードでは、データベースからロードされた適切なユーザーを使用しています。

    User user = (User) _userService.loadUserByUsername(username);
    CurrentUserAccessor.UseTestingAccessor(user);

セキュリティを実際に使用する必要がある場合、これは適切ではありませんが、テスト展開用にセキュリティなしのセットアップで実行しています。他の誰かが同様の状況に陥る可能性があると思いました。これは、以前に静的依存関係をモックアウトするために使用したパターンです。もう1つの方法は、ラッパークラスの静的性を維持できることですが、必要なクラスにCurrentUserAccessorを渡す必要があるため、コードの依存関係がより明確であるため、これを好みます。

1
JonnyRaa