268

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

要件:

  • クライアントは資格情報を使用して.../authenticate(保護されていない URL) に要求を行います。サーバーは、サーバーが将来のリクエストを検証してステートレスのままにするのに十分な情報を含むセキュア トークンを返します。これは、Spring Security のRemember-Me Tokenと同じ情報で構成される可能性があります。

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

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

  • 既に Spring を使用しているため、ソリューションでは Spring Security を使用する必要があります。

私たちはこれを機能させるために頭を壁にぶつけてきたので、誰かがすでにこの問題を解決していることを願っています。

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

4

4 に答える 4

192

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>

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

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

これで、良いスタートが切れるはずです。

于 2012-06-02T16:33:25.613 に答える
27

Digest Access Authenticationを検討するかもしれません。基本的に、プロトコルは次のとおりです。

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

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

ダイジェスト アクセス認証は、Spring Securityでサポートされています。ドキュメントには、クライアントのプレーンテキスト パスワードにアクセスする必要があると書かれていますが、クライアントのHA1 ハッシュがあれば、正常に認証できることに注意してください。

于 2012-05-31T02:43:21.943 に答える