5

最初に、スタック オーバーフローの他の質問を既に確認し、回答に基づいて独自のアプローチを実装したことをコメントしたいと思います。 /2487263

Spring 3.2、Spring MVC アプリケーションで Spring セキュリティ 3.1 を使用して REST API を保護しようとしています。単純な構成で基本的な認証アプローチを使用しています。

<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <http-basic />
</http>

ご覧のとおり、カスタム エントリ ポイントを使用しています。独自の ErrorResponse オブジェクトがあり、json 形式で http 応答に追加します。以下のコードを参照してください。

    @Component
public class AuthenticationFailedEntryPoint implements AuthenticationEntryPoint {
    static Logger log = Logger.getLogger(AuthenticationFailedEntryPoint.class);

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        log.error(ExceptionUtils.getStackTrace(authException));
        ErrorResponse errorResponse = new ErrorResponse();  

            ... here I fill my errorResponse object ...

        ObjectMapper jsonMapper = new ObjectMapper();

        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(status); 

        PrintWriter out = response.getWriter();
        out.print(jsonMapper.writeValueAsString(errorResponse));   
    }
}

このアプローチを 2 つのテスト ケースで試しました。

  1. 基本認証ヘッダーを提供せずに、1 つのサービスを使用してみてください。

これはリクエスト/レスポンスです:

GET http://localhost:8081/accounts/accounts?accountNumber=1013


 -- response --
401 Unauthorized
Server:  Apache-Coyote/1.1

Content-Type:  application/json;charset=UTF-8

Content-Length:  320

Date:  Fri, 25 Oct 2013 17:11:15 GMT

Proxy-Connection:  Keep-alive

{"status":401,"messages":[{"code":"000011","message":"You are not authorized to reach this endpoint"}]}

2.- 同じサービスの利用を試みますが、基本認証ヘッダーを間違ったパスワードで送信します。

これはリクエスト/レスポンスです:

GET http://localhost:8081/accounts/accounts?accountNumber=1013
Authorization: Basic bXl1c2VyOmdvb2RieWU=


 -- response --
401 Unauthorized
Server:  Apache-Coyote/1.1

WWW-Authenticate:  Basic realm="Spring Security Application"

Content-Type:  text/html;charset=utf-8

Content-Length:  1053

Date:  Fri, 25 Oct 2013 17:03:09 GMT

Proxy-Connection:  Keep-alive

<html> ... ugly html generated by tc server ... </html>

最初のケースでわかるように、エントリ ポイントに到達し、begin メソッドが実行されて例外が適切に処理され、json 応答が返されました。しかし、パスワードが間違っていた場合はそうはなりませんでした。

ログでは、両方のケースで異なるフローが生成されていることがわかりました。

ケース 1 (認証ヘッダーなし) の場合:

...
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /accounts?accountNumber=1013; Attributes: [ROLE_USER]
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter@11da1f99, returned: -1
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter@7507ef7, returned: 0
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.web.access.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
...

ケース 2 (間違ったパスワード) の場合:

...
2013-10-25 13:03:08,941 DEBUG tomcat-http--11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Basic Authentication Authorization header found for user 'myuser'
2013-10-25 13:03:08,941 DEBUG tomcat-http--11 org.springframework.security.authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2013-10-25 13:03:09,544 DEBUG tomcat-http--11 org.springframework.security.authentication.dao.DaoAuthenticationProvider - Authentication failed: password does not match stored value
2013-10-25 13:03:09,545 DEBUG tomcat-http--11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-10-25 13:00:30,136 DEBUG tomcat-http--9 org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
...

最初のケースでは AccessDeniedException がスローされ、これがキャッチされてエントリ ポイントの begin メソッドに送信されますが、BadCredentialsException をスローする 2 番目のケースではエントリ ポイントに送られません。

ここで奇妙なのは、開始メソッドが AuthenticationException を受け取ることになっているが、AccessDeniedException は AuthenticationException ではなく、BadCredentialsException であるということです。Spring security 3.1 API ドキュメントの継承ツリーを参照してください。

java.lang.Object
  extended by java.lang.Throwable
      extended by java.lang.Exception
          extended by java.lang.RuntimeException
              extended by org.springframework.security.access.AccessDeniedException

java.lang.Object
  extended by java.lang.Throwable
      extended by java.lang.Exception
          extended by java.lang.RuntimeException
              extended by org.springframework.security.core.AuthenticationException
                  extended by org.springframework.security.authentication.BadCredentialsException

正しいタイプではない例外で begin メソッドが呼び出されるのはなぜですか? また、正しいタイプの BadCredentialsException があるのに呼び出されないのはなぜですか?

編集 --- @Luke による回答を実装

説明されている 2 つのソリューションは、質問に示されているカスタム AuthenticationEntryPoint を使用します。次の 2 つのオプションのいずれかを選択して構成を変更する必要があります。

  1. カスタム BASIC_AUTH_FILTER の追加:

    <http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
        <intercept-url pattern="/**" access="ROLE_USER"/>
        <custom-filter position="BASIC_AUTH_FILTER" ref="authenticationFilter" /> 
    </http>
    
    <beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
        <beans:constructor-arg name="authenticationManager" ref="authenticationManager" />
        <beans:constructor-arg name="authenticationEntryPoint" ref="authenticationFailedEntryPoint" />
    </beans:bean>
    
  2. または http-basic 要素にエントリ ポイントを追加すると、IMO が最もクリーンなソリューションになります。

    <http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
        <intercept-url pattern="/**" access="ROLE_USER"/>
        <http-basic entry-point-ref="authenticationFailedEntryPoint" />
    </http>
    
4

1 に答える 1

4

問題は、基本認証が失敗した後、エントリポイントへの呼び出しがBasicAuthenticationFilterから直接行われることだと思います。これはデフォルトで組み込み実装になります。

これを修正するentry-point-refには、要素に属性http-basicも設定する必要があります。

または、基本認証フィルターを Bean として定義し、名前空間を完全に回避することもできます。

于 2013-10-27T12:56:17.730 に答える