アクティブユーザーのUserDetailsを取得する方法


170

私のコントローラーで、アクティブな(ログインした)ユーザーが必要な場合、次のようにしてUserDetails実装を取得します。

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());

正常に動作しますが、このような場合、Springを使用すると生活が楽になると思います。持ってする方法があるUserDetailsコントローラまたは方法のいずれかにautowiredは?

たとえば、次のようなものです。

public ModelAndView someRequestHandler(Principal principal) { ... }

しかしUsernamePasswordAuthenticationToken、を取得するUserDetails代わりに、私は代わりに取得しますか?

エレガントなソリューションを探しています。何か案は?

回答:


226

前文: Spring-Security 3.2以降@AuthenticationPrincipal、この回答の最後に説明されている素晴らしい注釈があります。これは、Spring-Security> = 3.2を使用する場合に最適な方法です。

あなたが:

  • Spring-Securityの古いバージョンを使用し、
  • プリンシパルに保存されている情報(ログインやIDなど)によってデータベースからカスタムユーザーオブジェクトをロードする必要がある
  • 方法を学びたいHandlerMethodArgumentResolverか、WebArgumentResolver後ろの背景を勉強したいエレガントな方法でこれを解決することができ、またはちょうど@AuthenticationPrincipalAuthenticationPrincipalArgumentResolver(それが基づいているためHandlerMethodArgumentResolver

その後、読み続けます—それ以外の場合は@AuthenticationPrincipal、使用してRob Winch(の作者@AuthenticationPrincipal)およびLukas Schmelzeisen(彼の回答)に感謝します。

(ところで、私の回答は少し古い(2012年1月)ので、Spring Security 3.2に基づく注釈ソリューションを最初に使用したのはLukas Schmelzeisen@AuthenticationPrincipalでした。)


その後、コントローラで使用できます

public ModelAndView someRequestHandler(Principal principal) {
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...
}

あなたが一度それを必要とするならば、それは大丈夫です。ただし、インフラストラクチャの詳細でコントローラーを汚染するために醜い数回必要になる場合、通常はフレームワークによって非表示にする必要があります。

だからあなたが本当に望むかもしれないのは、このようなコントローラーを持つことです:

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
   ...
}

したがって、実装する必要があるのはだけですWebArgumentResolver。それには方法があります

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

これはWebリクエスト(2番目のパラメーター)を取得Userし、メソッドの引数(最初のパラメーター)を担当していると感じた場合はを返す必要があります。

Spring 3.1以降、という新しい概念がありHandlerMethodArgumentResolverます。Spring 3.1以降を使用している場合は、それを使用する必要があります。(これはこの回答の次のセクションで説明されています))

public class CurrentUserWebArgumentResolver implements WebArgumentResolver{

   Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
        if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) {
           Principal principal = webRequest.getUserPrincipal();
           return (User) ((Authentication) principal).getPrincipal();
        } else {
           return WebArgumentResolver.UNRESOLVED;
        }
   }
}

カスタムアノテーションを定義する必要があります。Userのすべてのインスタンスを常にセキュリティコンテキストから取得する必要があるが、コマンドオブジェクトではない場合は、スキップできます。

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActiveUser {}

構成では、これを追加するだけです:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    id="applicationConversionService">
    <property name="customArgumentResolver">
        <bean class="CurrentUserWebArgumentResolver"/>
    </property>
</bean>

@See:Spring MVCの@Controllerメソッドの引数をカスタマイズする方法を学ぶ

Spring 3.1を使用している場合、WebArgumentResolverよりもHandlerMethodArgumentResolverを推奨することに注意してください。-ジェイのコメントを参照


HandlerMethodArgumentResolverSpring 3.1+ と同じ

public class CurrentUserHandlerMethodArgumentResolver
                               implements HandlerMethodArgumentResolver {

     @Override
     public boolean supportsParameter(MethodParameter methodParameter) {
          return
              methodParameter.getParameterAnnotation(ActiveUser.class) != null
              && methodParameter.getParameterType().equals(User.class);
     }

     @Override
     public Object resolveArgument(MethodParameter methodParameter,
                         ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest,
                         WebDataBinderFactory binderFactory) throws Exception {

          if (this.supportsParameter(methodParameter)) {
              Principal principal = webRequest.getUserPrincipal();
              return (User) ((Authentication) principal).getPrincipal();
          } else {
              return WebArgumentResolver.UNRESOLVED;
          }
     }
}

構成では、これを追加する必要があります

<mvc:annotation-driven>
      <mvc:argument-resolvers>
           <bean class="CurrentUserHandlerMethodArgumentResolver"/>         
      </mvc:argument-resolvers>
 </mvc:annotation-driven>

@See Spring MVC 3.1 HandlerMethodArgumentResolverインターフェースの活用


Spring-Security 3.2ソリューション

春のセキュリティは、3.2(春3.2と混同しないでください)溶液中で独自のビルドを持っています@AuthenticationPrincipalorg.springframework.security.web.bind.annotation.AuthenticationPrincipal)。これはルーカス・シュメルツァイセンの答えでうまく説明されています

それはただ書いている

ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }

これは作業を取得するには、登録する必要がありますAuthenticationPrincipalArgumentResolverorg.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver「活性化」によって、次のいずれか)、 @EnableWebMvcSecurityまたは内部にこのBeanを登録してmvc:argument-resolvers-私は上記の5月の春3.1ソリューションでそれを説明したのと同じ方法を。

@「Spring Security 3.2 Reference、Chapter 11.2」を参照してください。@AuthenticationPrincipal


Spring-Security 4.0ソリューション

それは春3.2ソリューションのように動作しますが、春4.0 @AuthenticationPrincipalAuthenticationPrincipalArgumentResolver他のパッケージに「移動」されました:

(ただし、古いパッケージの古いクラスはまだ存在するため、混在させないでください!)

それはただ書いている

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

あなたが登録する必要が働いて、これを取得するには(org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver:いずれかの「活性化」によって、 @EnableWebMvcSecurityまたは内このBeanを登録することにより、mvc:argument-resolvers-私は上記の5月の春3.1ソリューションでそれを説明したのと同じ方法。

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

@Spring Security 5.0リファレンスの第39.3章を参照@AuthenticationPrincipal


または、getUserDetails()メソッドを持つBeanを作成し、@ Autowireをコントローラーに追加します。
sourcedelica

このソリューションを実装して、resolveArgument()の先頭にブレークポイントを設定しましたが、アプリがWeb引数リゾルバーにステップインすることはありません。ルートコンテキストではなく、サーブレットコンテキストでのSpringの設定ですよね?
ジェイ

@Jay:この構成は、サーブレットコンテキストではなく、ルートコンテキストの一部です。- (id="applicationConversionService")例でidを指定するのを忘れているようです
Ralph

11
Spring 3.1を使用している場合、WebArgumentResolverよりもHandlerMethodArgumentResolverを推奨することに注意してください。サーブレットコンテキストで<annotation-driven>を設定してHandlerMethodArgumentResolverを機能させました。それ以外は、ここに掲載されているとおりに回答を実装しました。すべてがうまくいきました。
Jayが

@sourcedelicaなぜ静的メソッドを作成しないのですか?
Alex78191

66

一方でラルフス回答は春のセキュリティ3.2あなた独自のものを実装するために、不要になって、エレガントなソリューションを提供しますArgumentResolver

UserDetails実装がある場合は、CustomUserこれを行うことができます:

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

Spring Securityのドキュメントを参照してください:@AuthenticationPrincipal


2
提供されたリンクを読むのは好きではない人のために、これは、のいずれかによって有効にする必要があります@EnableWebMvcSecurityまたはXMLに:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik

customUserがnullかどうかを確認するにはどうすればよいですか?
Sajad 2016年

if (customUser != null) { ... }
T3rm1 2016

27

Spring Securityは、Spring以外のフレームワークと連携することを目的としているため、Spring MVCと緊密に統合されていません。Spring SecurityはデフォルトでメソッドAuthenticationからオブジェクトを返すHttpServletRequest.getUserPrincipal()ため、これがプリンシパルとして取得します。UserDetailsこれを使用してオブジェクトを直接取得できます

UserDetails ud = ((Authentication)principal).getPrincipal()

また、オブジェクトタイプは、使用される認証メカニズムによって異なる場合があり(UsernamePasswordAuthenticationTokenたとえば、を取得できない場合があります)、Authenticationにが含まれている必要はありませんUserDetails。文字列または他の任意のタイプにすることができます。

SecurityContextHolder直接呼び出したくない場合、最もエレガントなアプローチ(私が従う)は、ニーズとユーザーオブジェクトタイプに一致するようにカスタマイズされた独自のカスタムセキュリティコンテキストアクセサーインターフェイスを挿入することです。関連するメソッドを使用してインターフェースを作成します。次に例を示します。

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

その後SecurityContextHolder、標準の実装でにアクセスしてこれを実装し、Spring Securityからコードを完全に切り離すことができます。次に、これを、セキュリティ情報または現在のユーザーに関する情報にアクセスする必要があるコントローラーに挿入します。

もう1つの主な利点は、スレッドローカルの入力などを気にすることなく、テスト用の固定データを使用して簡単な実装を簡単に作成できることです。


私はこのアプローチを検討していましたが、a)正しい方法(TM)を正確に行う方法、およびb)スレッドの問題があるかどうかはわかりませんでした。そこに問題がないと確信していますか?上記のアノテーションメソッドを使用しますが、これはまだ適切な方法です。投稿いただきありがとうございます。:)
オーニーベア

4
これは技術的にはコントローラーから直接SecurityContextHolderにアクセスするのと同じなので、スレッドの問題は発生しません。呼び出しを1か所に保持するだけで、テストの代替手段を簡単に挿入できます。セキュリティ情報にアクセスする必要がある他の非Webクラスで同じアプローチを再利用することもできます。
Shaun the Sheep

Gotcha ...それは私が考えていたものです。これは、アノテーションメソッドを使用した単体テストで問題が発生した場合や、Springとの結合を減らしたい場合に戻るのに適しています。
Awnry Bear、2012

@LukeTaylorこのアプローチについて詳しく説明してもらえますか。私はSpringとSpringのセキュリティに少し慣れていないので、これを実装する方法がわかりません。アクセスできるように、これをどこに実装すればよいですか?私のUserServiceImplに?
Cu7l4ss

9

次のように、HandlerInterceptorインターフェースを実装しUserDetails、モデルを持つ各リクエストにを注入します。

@Component 
public class UserInterceptor implements HandlerInterceptor {
    ....other methods not shown....
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null){
            modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        }
}

1
@atrainに感謝します。これは便利でエレガントです。また、<mvc:interceptors>アプリケーション構成ファイルにを追加する必要がありました。
Eric

テンプレートで承認されたユーザーを取得することをおspring-security-taglibs
勧め

9

Spring Securityバージョン3.2以降では、古い回答の一部によって実装されたカスタム機能が、に@AuthenticationPrincipal裏付けられた注釈の形でそのまま使用できAuthenticationPrincipalArgumentResolverます。

これの簡単な使用例は次のとおりです。

@Controller
public class MyController {
   @RequestMapping("/user/current/show")
   public String show(@AuthenticationPrincipal CustomUser customUser) {
        // do something with CustomUser
       return "view";
   }
}

CustomUserはから割り当て可能である必要があります authentication.getPrincipal()

AuthenticationPrincipalおよびAuthenticationPrincipalArgumentResolverの対応するJavadocは次のとおりです


1
@nbroバージョン固有のソリューションを追加したとき、他のソリューションはどれもこのソリューションを考慮に入れるように更新されていませんでした
geoおよび

AuthenticationPrincipalArgumentResolverは非推奨になりました
Igor Donin

5
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

0

また、テンプレート(JSPなど)で承認されたユーザーが必要な場合は、

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>

一緒に

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>

0

これを試すことができます:Springの認証オブジェクトを使用することで、コントローラーメソッドでそこからユーザーの詳細を取得できます。以下は、引数とともにコントローラメソッドで認証オブジェクトを渡すことによる例です。ユーザーが認証されると、詳細が認証オブジェクトに入力されます。

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}

答えを詳しく説明してください。
Nikolai Shevchenko

私は自分の回答を編集しました。認証済みのユーザーのユーザー詳細を含む認証オブジェクトを使用できます。
Mirza Shujathullah
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.