Spring Security / SpringMVCで認証されたユーザーを手動で設定する方法


107

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

春のセキュリティインターセプターを通過する通常のフォームログインページは問題なく機能します。

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

SecurityContextHolder.getContext().setAuthentication(authentication);

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

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

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

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

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

  • セッションIDが正しく設定されていないことが原因である可能性がありますか?
  • どういうわけか私の認証を上書きしている可能性があるものはありますか?
  • おそらく、認証を保存するために別の手順が必要なだけですか?
  • または、どういうわけか単一の要求ではなく、セッション全体で認証を宣言するために必要なことはありますか?

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



2
読者は、この質問への回答に注意してくださいSecurityContextHolder.getContext().setAuthentication(authentication)。これは機能し、一般的ですが、それを行うだけでは深刻な機能上の欠点が発生します。:詳細情報については、私の質問を参照してください、と答えstackoverflow.com/questions/47233187/...
ヤギ

回答:


62

しばらく前に私はあなたと同じ問題を抱えていました。詳細を思い出せませんが、次のコードでうまくいきました。このコードは、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);
    }

}

2
ありがとうございます。コードは、適切な領域でトラブルシューティングを行っていることを知るのに非常に役立ちます。私は喫煙銃を持っているように見えますが、手動認証の後に新しいセッションIDを作成していますが、古いセッションIDはまだCookieから識別されています。なぜ今なのか理解しなければならないが、少なくとも私は順調に進んでいる。ありがとう!
David Parks

4
このガイダンスに従って、誰でもまた、この関連の問題が表示されます。stackoverflow.com/questions/4824395/...
デヴィッド・パークス

14
あなたはどのようにauthenticationProvider取得していることを説明していただけます
Vivex

1
@ s1moner3dは、IoC-> \ @Autowired
Hartmut

1
@Configuration public class WebConfig extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationProvider() throws Exception { return super.authenticationManagerBean(); } }
slisnychyi

66

他の完全な解決策を見つけることができなかったので、私の投稿を投稿すると思いました。これは少しハックかもしれませんが、問題を上記の問題に解決しました:

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

7
+1-これは私を助けました!SPRING_SECURITY_CONTEXTの更新がありませんでした。...しかし、これはどのように「ダーティ」ですか。
l3dx

12
どこauthenticationManagerから来たの?
アイザック

2
authenticationManagerは、この@Autowired AuthenticationServiceImpl authenticationManagerのようにクラス内でオートワイヤーされます。また、xml構成にはBeanインジェクションが必要なので、Springは何をインジェクトするかを認識しています。

1
AuthenticationServiceImplの実装はどこにありますか?このクラスは何を保持していますか?
Pra_A

3
新しいセッションを作成する必要があるのはなぜですか?SecurityContextはそれを処理しませんか?
Vlad Manuel Mureșan 2016年

17

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

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

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


4
正直なところ、これは何よりも春のセキュリティの設計上の欠陥のように感じます。他の言語で書かれたフレームワークはたくさんありますが、これで問題はありませんが、Spring Securityは機能しなくなります。
chubbsondubs

3
そして解決策は?
s1moner3d 2016年

2
そして解決策は何ですか?
チアゴ2017

6

デバッグログをオンにして、何が起こっているかをよりよく把握します。

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

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


おかげで、これらはすべて非常に役に立ち、関連する提案でした!
David Parks

5

サーブレット2.4の新しいフィルタリング機能は、基本的に、アプリケーションサーバーによる実際のリクエスト処理の前後に、リクエストフローでのみフィルターが機能できるという制限を緩和します。代わりに、サーブレット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();

良い情報ですが、ユーザー名とパスワードをURLに入れるのはよくありません。1)エスケープは行われないため、特殊文字を含むユーザー名またはパスワードは壊れる可能性が高く、さらに悪い場合には、セキュリティエクスプロイトベクトルとして使用される可能性があります。2)URLはしばしばディスクに記録されるため、URL内のパスワードは適切ではありません。これは、セキュリティ上かなり悪いことです。
ヤギ

1

私は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を必要なクラスに渡す必要があるため、コードの依存関係がより明示的であるため、私はこれを好みます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.