web-dev-qa-db-ja.com

Spring経由のRESTful認証

問題:
機密情報を含むSpring MVCベースのRESTful APIがあります。 APIは保護する必要がありますが、リクエストごとにユーザーの資格情報(user/passコンボ)を送信することは望ましくありません。 RESTガイドライン(および内部のビジネス要件)に従って、サーバーはステートレスのままにする必要があります。 APIはマッシュアップスタイルのアプローチで別のサーバーによって消費されます。

必要条件:

  • クライアントは、資格情報を使用して.../authenticate(保護されていないURL)に要求を出します。サーバーは、将来の要求を検証してステートレスを維持するのに十分な情報を含む安全なトークンをサーバーに返します。これはおそらくSpring Securityのものと同じ情報からなるでしょう Remember-Me Token

  • クライアントは、以前に取得したトークンをクエリパラメータ(またはあまり望ましくないがHTTPリクエストヘッダ)として追加して、さまざまな(保護された)URLに後続のリクエストを行います。

  • クライアントがクッキーを保存することは期待できません。

  • 私たちはすでにSpringを使っているので、解決策はSpring Securityを利用するべきです。

私たちはこの作品を作ろうとしている壁に頭をぶつけてきたので、うまくいけばそこに誰かがこの問題をすでに解決しています。

上記のシナリオを考えると、この特定のニーズをどのように解決できるでしょうか。

244
Chris Cashwell

私たちはこれをOPの説明どおりに機能させることができたので、他の人がこのソリューションを利用できることを願っています。これが私たちがしたことです。

セキュリティコンテキストを次のように設定します。

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

ご覧のとおり、カスタムのAuthenticationEntryPointを作成しました。これは、要求がフィルターチェーンでAuthenticationTokenProcessingFilterによって認証されなかった場合に基本的には401 Unauthorizedを返すだけです。

CustomAuthenticationEntryPoint

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

AuthenticationTokenProcessingFilter

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;

    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter

            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));         
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

明らかに、TokenUtilsはいくつかの秘密の(そして非常に大文字小文字の区別がある)コードを含んでいて、すぐに共有することはできません。そのインターフェースは次のとおりです。

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

それは良いスタートにあなたを降りるべきです。ハッピーコーディング:)

177
Chris Cashwell

あなたは ダイジェストアクセス認証 を考えるかもしれません。基本的にプロトコルは以下の通りです。

  1. 依頼者から依頼
  2. サーバは一意の一回だけの文字列で応答する
  3. クライアントは、md5がナンスでハッシュしたユーザー名とパスワード(およびその他の値)を提供します。このハッシュはHA1として知られています
  4. サーバーはクライアントの身元を確認し、要求された資料を提供することができます
  5. サーバが新しいナンスを提供するまで、ナンスとの通信を続けることができます(リプレイ攻撃を排除するためにカウンタが使用されます)。

この通信はすべてヘッダーを介して行われます。これは、jmort253で指摘されているように、urlパラメーターで機密情報を通信するよりも一般的に安全です。

ダイジェストアクセス認証は Spring Security でサポートされています。ドキュメントにはクライアントのプレーンテキストパスワードにアクセスする必要があると記載されていますが、クライアントに対して HA1ハッシュがある場合は正常に認証されます を実行できます。

22
Tim Pote

情報を運ぶトークンに関しては、JSON Web Tokens( http://jwt.io )は素晴らしい技術です。主な概念は、情報要素(クレーム)をトークンに埋め込んでから、トークン全体に署名して、検証側がクレームが本当に信頼できるものであることを検証できるようにすることです。

私はこのJava実装を使います。 https://bitbucket.org/b_c/jose4j/wiki/Home

Springモジュール(spring-security-jwt)もありますが、それが何をサポートしているのかは調べていません。

4
Leif John

JSON WebTokensでOAuthを使い始めませんか?

http://projects.spring.io/spring-security-oauth/

OAuth2は標準化された認証プロトコル/フレームワークです。公式OAuth2によると 仕様

あなたはより多くの情報を見つけることができます ここ

1
vaquar khan