10

Spring で基本認証を使用しているときに、HTTP 応答ヘッダー「Access-Control-Allow-Origin」に関連する問題が発生しました。次のコードのように、手動で認証する場合 (私は REST を使用しています):

@RequestMapping(value = "/login", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(value = HttpStatus.OK)
public void login(@RequestBody String body, HttpServletResponse response)
        throws IOException {
    try {
        User user = gson.fromJson(body, User.class);

        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
                usuario.getUsername(), usuario.getPassword());

        authenticationManager.authenticate(token);
    } catch (BadCredentialsException e) {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    } catch (Exception e) {
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
}

すべて正常に動作し、次の HTTP 応答を受け取ります。

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Content-Type: text/html;charset=utf-8
Content-Length: 951
Date: Fri, 17 May 2013 19:14:36 GMT

ご覧のとおり、「Access-Control-Allow-Origin」が応答に含まれています。ここではすべて問題ありません。ajax 呼び出しで 401 エラーをキャッチできます。

ただし、次のコードのように、認証が自動的に実行される場合:

@RequestMapping(value = "/name", method = RequestMethod.POST, consumes = "application/json")
@PreAuthorize("hasRole('ROLE_CUSTOMER')")
public @ResponseBody String getName(HttpServletResponse response) throws IOException {
    String json = null;

    try {
        User userSession = (User) SecurityContextHolder.getContext()
                .getAuthentication().getPrincipal();

        Customer customer = customerDao.getNameByUsername(userSession.getUsername());

        json = gson.toJson(customer);

    } catch (Exception e) {
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }

    return json;
}

HTTP 応答は次のとおりです。

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html;charset=utf-8
Content-Length: 981
Date: Fri, 17 May 2013 19:41:08 GMT

応答に「Access-Control-Allow-Origin」がありません

Google Chrome コンソールに次のエラーが表示されます。

Origin null is not allowed by Access-Control-Allow-Origin

私の ajax 呼び出しは 401 Unauthorized エラーを返さず、HTTP 応答はそれを返しますが (上記の応答)、不明なエラーを受け取ります。

すべてのブラウザーで、HTTP 応答に「Access-Control-Allow-Origin」が必要であることがわかりました。そうしないと、何らかのサイレント エラーが生成され、ajax 呼び出しが失敗します (401 エラーをキャッチできません)。実際には、javascript は黙って失敗します。XMLHttpRequest は、「Access-Control-Allow-Origin」のない HTTP 応答を受け入れません。

基本認証の HTTP 応答にこの「Access-Control-Allow-Origin」を Spring に挿入させるにはどうすればよいですか?

これは私のSpring Security xmlです:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
          http://www.springframework.org/schema/security
          http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <security:http create-session="stateless" entry-point-ref="authenticationEntryPoint">
        <security:intercept-url pattern="/customer/**" />
        <security:http-basic />
        <security:custom-filter ref="basicAuthenticationFilter"
            after="BASIC_AUTH_FILTER" />
    </security:http>

    <bean id="basicAuthenticationFilter"
        class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
    </bean>

    <bean id="authenticationEntryPoint"
        class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
        <property name="realmName" value="teste.com" />
    </bean>

    <!-- It is responsible for validating the user's credentials -->
    <security:authentication-manager alias="authenticationManager">

        <!-- It is responsible for providing credential validation to the AuthenticationManager -->
        <security:authentication-provider>
            <security:password-encoder ref="passwordEncoder" />

            <security:jdbc-user-service
                data-source-ref="mySQLdataSource"
                users-by-username-query="select username, password, enabled from usuario where username = ?"
                authorities-by-username-query="select username, papel from autoridade where username = ?" />

        </security:authentication-provider>

    </security:authentication-manager>

    <bean class="org.springframework.security.crypto.password.StandardPasswordEncoder"
        id="passwordEncoder" />

</beans>
4

1 に答える 1

14

自分のやり方を見つけました:

まず第一に、なぜこの行をここに置いたのかよく覚えていませんが、コードを台無しにしていたのです。

<security:http-basic />

第二に、この答えは私にパスを示します: Spring Security の基本認証の未承認のエラーメッセージを処理します。Access-Control-Allow-Origin を送信するために、カスタム認証エントリ ポイントを作成する必要がありました。

だから、これは私のコードです:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
              http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
              http://www.springframework.org/schema/security
              http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <security:http create-session="stateless"
        entry-point-ref="authenticationEntryPoint">
        <security:intercept-url pattern="/api/admin/**" />
        <security:intercept-url pattern="/medico/**" />
        <!-- <security:http-basic />  -->
        <security:custom-filter ref="basicAuthenticationFilter"
            after="BASIC_AUTH_FILTER" />
    </security:http>

    <bean id="basicAuthenticationFilter"
        class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
    </bean>

            <!-- 
    <bean id="authenticationEntryPoint" 
        class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint">
        <property name="realmName" value="test.com" />
    </bean> -->


    <bean id="authenticationEntryPoint" 
        class="com.test.util.PlainTextBasicAuthenticationEntryPoint">
        <property name="realmName" value="test.com" />
    </bean> 

    <!-- It is responsible for validating the user's credentials -->
    <security:authentication-manager alias="authenticationManager">

        <!-- It is responsible for providing credential validation to the AuthenticationManager -->
        <security:authentication-provider>
            <security:password-encoder ref="passwordEncoder" />

            <security:jdbc-user-service
                data-source-ref="mySQLdataSource"
                users-by-username-query="select username, password, enabled from usuario where username = ?"
                authorities-by-username-query="select username, papel from autoridade where username = ?" />

        </security:authentication-provider>

    </security:authentication-manager>

    <bean
        class="org.springframework.security.crypto.password.StandardPasswordEncoder"
        id="passwordEncoder" />

</beans>
package com.test.util;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

public class PlainTextBasicAuthenticationEntryPoint extends
        BasicAuthenticationEntryPoint {

      @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.addHeader("Access-Control-Allow-Origin", "null");
            response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            PrintWriter writer = response.getWriter();
            writer.println("HTTP Status " + HttpServletResponse.SC_UNAUTHORIZED + " - " + authException.getMessage());
        }

}

私のhttp応答:

HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: null
WWW-Authenticate: Basic realm="test.com"
Content-Length: 35
Date: Mon, 20 May 2013 20:05:03 GMT

HTTP Status 401 - Bad credentials

変更前に、次のエラー メッセージが表示されました。

OPTIONS http://localhost:8080/test/customer/name 200 (OK) jquery-1.8.2.min.js:2
XMLHttpRequest cannot load http://localhost:8080/test/customer/name. Origin null is     not allowed by Access-Control-Allow-Origin. 

そして今、予想通り、私はこれを手に入れました:

OPTIONS http://localhost:8080/test/customer/name 200 (OK) jquery-1.8.2.min.js:2
POST http://localhost:8080/test/customer/name 401 (Unauthorized) 
于 2013-05-20T20:16:15.793 に答える