31

CORS を Spring Security とうまく連携させようとしていますが、準拠していません。この記事で説明されている変更を行い、この行を変更するapplicationContext-security.xmlと、アプリで POST および GET 要求が機能するようになりました (コントローラー メソッドを一時的に公開するため、CORS をテストできます)。

  • 前:<intercept-url pattern="/**" access="isAuthenticated()" />
  • 後:<intercept-url pattern="/**" access="permitAll" />

残念ながら、AJAX を介した Spring Security ログインを許可する次の URL は応答していません: http://localhost:8080/mutopia-server/resources/j_spring_security_check. http://localhost:80からへの AJAX リクエストを作成していますhttp://localhost:8080

Chrome で

アクセスしようとすると、OPTIONS プリフライト リクエストのj_spring_security_checkため(pending)に Chrome にアクセスし、AJAX 呼び出しで HTTP ステータス コード 0 とメッセージ「エラー」が返されます。

Firefox の場合

プリフライトは HTTP ステータス コード 302 で成功しますが、その後すぐに AJAX リクエストのエラー コールバックが返され、HTTP ステータス 0 とメッセージ「エラー」が表示されます。

ここに画像の説明を入力

ここに画像の説明を入力

AJAX リクエスト コード

function get(url, json) {
    var args = {
        type: 'GET',
        url: url,
        // async: false,
        // crossDomain: true,
        xhrFields: {
            withCredentials: false
        },
        success: function(response) {
            console.debug(url, response);
        },
        error: function(xhr) {
            console.error(url, xhr.status, xhr.statusText);
        }
    };
    if (json) {
        args.contentType = 'application/json'
    }
    $.ajax(args);
}

function post(url, json, data, dataEncode) {
    var args = {
        type: 'POST',
        url: url,
        // async: false,
        crossDomain: true,
        xhrFields: {
            withCredentials: false
        },
        beforeSend: function(xhr){
            // This is always added by default
            // Ignoring this prevents preflight - but expects browser to follow 302 location change
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.setRequestHeader("X-Ajax-call", "true");
        },
        success: function(data, textStatus, xhr) {
            // var location = xhr.getResponseHeader('Location');
            console.error('success', url, xhr.getAllResponseHeaders());
        },
        error: function(xhr) {
            console.error(url, xhr.status, xhr.statusText);
            console.error('fail', url, xhr.getAllResponseHeaders());
        }
    }
    if (json) {
        args.contentType = 'application/json'
    }
    if (typeof data != 'undefined') {
        // Send JSON raw in the body
        args.data = dataEncode ? JSON.stringify(data) : data;
    }
    console.debug('args', args);
    $.ajax(args);
}

var loginJSON = {"j_username": "username", "j_password": "password"};

// Fails
post('http://localhost:8080/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);

// Works
post('http://localhost/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);

// Works
get('http://localhost:8080/mutopia-server/landuses?projectId=6', true);

// Works
post('http://localhost:8080/mutopia-server/params', true, {
    "name": "testing",
    "local": false,
    "generated": false,
    "project": 6
}, true);

注意してください-Spring Security ログインを除いて、CORS を介してアプリ内の他の URL に POST できます。私は多くの記事を読んできたので、この奇妙な問題についての洞察をいただければ幸いです

4

8 に答える 8

5

ほとんどの場合、OPTIONS リクエストは、スプリング セキュリティの認証用の Cookie を持ちません。
これを解決するには、Spring セキュリティの構成を変更して、認証なしでOPTIONSリクエストを許可します。
私は多くのことを調査し、2 つの解決策を得ました
。1.Spring セキュリティ構成で Java 構成を使用する

@Override
protected void configure(HttpSecurity http) throws Exception
{
    http
    .csrf().disable()
    .authorizeRequests()
    .antMatchers(HttpMethod.OPTIONS,"/path/to/allow").permitAll()//allow CORS option calls
    .antMatchers("/resources/**").permitAll()
    .anyRequest().authenticated()
    .and()
    .formLogin()
    .and()
    .httpBasic();
}

2. XML の使用 (: "POST,GET" は記述できません):

<http auto-config="true">
    <intercept-url pattern="/client/edit" access="isAuthenticated" method="GET" />
    <intercept-url pattern="/client/edit" access="hasRole('EDITOR')" method="POST" />
    <intercept-url pattern="/client/edit" access="hasRole('EDITOR')" method="GET" />
</http>

最後に、解決策のソースがあります... :)

于 2016-06-03T10:08:47.620 に答える
3

私にとっての問題は、OPTIONSその呼び出しで資格情報が渡されなかったため、プリフライト チェックが認証に失敗したことでした。

これは私のために働く:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Configuration
@EnableAsync
@EnableScheduling
@EnableSpringDataWebSupport
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable()
                .httpBasic().and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and().anonymous().disable()
                .exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint() {
            @Override
            public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
                if(HttpMethod.OPTIONS.matches(request.getMethod())){
                    response.setStatus(HttpServletResponse.SC_OK);
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HttpHeaders.ORIGIN));
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                }else{
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
                }
            }
        });

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }
}

関連する部分は次のとおりです。

.exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint() {
            @Override
            public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
                if(HttpMethod.OPTIONS.matches(request.getMethod())){
                    response.setStatus(HttpServletResponse.SC_OK);
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HttpHeaders.ORIGIN));
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
                    response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                }else{
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
                }
            }
        });

これにより、プリフライトの問題が修正されOPTIONSます。ここで何が起こるかというと、呼び出しを受信して​​認証が失敗した場合、それが呼び出しであるかどうかを確認し、OPTIONS呼び出しである場合は、それを通過させて、やりたいことをすべて実行させます。これにより、基本的にすべてのブラウザー側のプリフライト チェックが無効になりますが、通常のクロスドメイン ポリシーは引き続き適用されます。

Spring の最新バージョンを使用している場合は、以下のコードを使用してクロス オリジン リクエストをグローバルに (すべてのコントローラーに対して) 許可できます。

import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Component
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("http://localhost:3000");
    }
}

このようにハードコーディングすることは、めったに良い考えではないことに注意してください。私が働いたことのあるいくつかの企業では、許可されたオリジンは管理ポータルから構成可能でした。そのため、開発環境では、必要なすべてのオリジンを追加できました。

于 2016-04-09T02:20:45.523 に答える
2

Bludreamの回答には完全に同意しますが、いくつかの発言があります。

CORS フィルターの if 句を、origin ヘッダーで NULL チェックを使用して拡張します。

public class CorsFilter extends OncePerRequestFilter {

    private static final String ORIGIN = "Origin";


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

        if (request.getHeader(ORIGIN) == null || request.getHeader(ORIGIN).equals("null")) {
            response.addHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.addHeader("Access-Control-Max-Age", "10");

            String reqHead = request.getHeader("Access-Control-Request-Headers");

            if (!StringUtils.isEmpty(reqHead)) {
                response.addHeader("Access-Control-Allow-Headers", reqHead);
            }
        }
        if (request.getMethod().equals("OPTIONS")) {
            try {
                response.getWriter().print("OK");
                response.getWriter().flush();
            } catch (IOException e) {
            e.printStackTrace();
            }
        } else{
            filterChain.doFilter(request, response);
        }
    }
 }

さらに、次の望ましくない動作に気付きました: 無許可のロールで REST API にアクセスしようとすると、Spring セキュリティが HTTP ステータス 403: FORBIDDEN を返し、CORS ヘッダーが返されます。ただし、不明なトークンまたは無効になったトークンを使用すると、HTTP ステータス 401: UNAUTHORIZED が CORS ヘッダーなしで返されます。

次のようにセキュリティ XML のフィルター構成を変更することで、なんとか機能させることができました。

<security:http use-expressions="true" .... >
    ...
    //your other configs
    <sec:custom-filter ref="corsFilter" before="HEADERS_FILTER"/>
</security:http>

そして、カスタム フィルター用の次の Bean :

<bean id="corsFilter" class="<<location of the CORS filter class>>" />
于 2014-10-21T13:21:46.053 に答える