2011-01-12 60 views
89

新しいユーザーが「新規アカウント」フォームを送信した後、そのユーザーを手動でログして後続のページにログインする必要がなくなります。Spring Security/SpringMVCで認証されたユーザーを手動で設定する方法

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

私はUsernamePasswordAuthenticationTokenを作成し、手動たSecurityContextでそれを設定しています新しいアカウントフォームコントローラで

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

SecurityContextHolder.getContext().setAuthentication(authentication); 

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

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

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

なぜ私は前回のリクエストで設定した認証を保持していないのか分かりません。何かご意見は?

  • セッションIDが正しく設定されていない可能性がありますか。
  • どういうわけか私の認証を上書きする可能性がありますか?
  • 私は認証を保存するために別の手順が必要なのでしょうか?
  • また、何らかの理由で1回のリクエストではなく、セッション全体で認証を宣言する必要があるのでしょうか?

ここで何が起こっているのかを知るのに役立ついくつかの考えを探してください。

+1

あなたは私の答えに従うことができますhttp://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form/7972971#7972971 – AlexK

+1

読者は、 'SecurityContextHolder.getContext()。あなたがそうするように言うなら、この質問。setAuthentication(認証) '。それは機能し、一般的ですが、あなたがそれを行うならば、あなたが満たすであろう重大な機能上の欠点があります。詳細については、私の質問と答えを参照してください:https://stackoverflow.com/questions/47233187/spring-security-manual-login-best-practice – goat

答えて

59

私はあなたと同じ問題がありました。私は細部を覚えていないが、次のコードは私のために働いている。このコードは、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は依然としてクッキーから特定されています。なぜ今、なぜ私は明らかに軌道に乗っているかを把握する必要があります。ありがとう! –

+4

このガイダンスに従う人は、次の関連問題も参照する必要があります。http://stackoverflow.com/questions/4824395/spring-security-forward-directive-cant-forward-to-login-form –

+12

認証プロバイダを取得 – Vivek

5

デバッグログを有効にすると、何が起こっているかをより正確に把握できます。

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

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

+0

これらはすべて非常に有用で関連性の高い提案でした! –

15

最終的には問題の根本を理解しました。

セキュリティコンテキストを手動で作成すると、セッションオブジェクトは作成されません。要求が処理を終了したときだけ、Springセキュリティ機構はセッションオブジェクトがnullであることを認識します(要求が処理された後にセキュリティコンテキストをセッションに格納しようとすると)。

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

+3

正に、これは何よりも春のセキュリティの設計上の欠陥のように感じます。これに問題のない他の言語で書かれたフレームワークはたくさんありますが、Spring Securityはちょうど壊れてしまいます。 – chubbsondubs

+2

であり、解は? – s1moner3d

+1

とは何ですか? – Thiago

5

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

私は思ったので、私は他の完全な解決策を見つけることができませんでした私は私のものを投稿するだろう。これはハックの少しかもしれないが、それは、上記の問題に問題を解決:私はExtJSのアプリケーションをテストしようとしていた

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

+8

どこから 'authenticationManager'を取得しますか? – Isaac

+1

authenticationManagerは次のようにクラス内でautowiredされています@Autowired \t AuthenticationServiceImpl authenticationManager。また、xml構成にBeanインジェクションがなければならないので、Springは何を注入すべきかを知っています。 – test

1

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

テストバージョンは、ちょうど次のようになります。これは文句を言わないことが明らかに

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

:私はまだデータベースからロードされた適切なユーザーを使用している呼び出し元のコードで

public class TestUserAccessor implements UserAccessor 
{ 
    private static User _user; 

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

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

実際にセキュリティを使用する必要がある場合には適していますが、テスト展開のためのセキュリティなしの設定で実行しています。他の人が同様の状況に陥るかもしれないと思った。これは静的な依存関係をあらかじめ模倣していたパターンです。もう一つの方法は、ラッパークラスの静的性を維持することですが、必要なクラスにCurrentUserAccessorを渡す必要があるので、コードの依存関係がより明示的であるため、このクラスを優先します。

+0

@downvoterあなたが好きではなかったことについてコメントしてください。 –

関連する問題