복붙노트

[SPRING] / oauth / token에 유효하지 않은 XSRF 토큰이 있습니다.

SPRING

/ oauth / token에 유효하지 않은 XSRF 토큰이 있습니다.

다중 요소 인증의 Spring OAuth2 구현을위한 전체 코드가이 링크의 파일 공유 사이트에 업로드되었습니다. 아래의 지침은 몇 분 안에 모든 컴퓨터에서 현재 문제를 재현하는 것입니다.

현재 문제 :

대부분의 인증 알고리즘이 올바르게 작동합니다. 프로그램은 아래에 표시된 제어 흐름의 마지막까지 중단되지 않습니다. 특히, 잘못된 CSRF는 HTTP에 대한 발견 토큰 : // localhost를 : 9999 / UAA / OAuth를 / 토큰 오류가 아래의 두 번째 PASS의 끝에서 발생되고있다. 위의 링크에있는 응용 프로그램이 봄 부팅 OAuth2를 GitHub의 샘플의 authserver 응용 프로그램에 사용자 정의 OAuth2RequestFactory, TwoFactorAuthenticationFilter 및 TwoFactorAuthenticationController을 추가하여 개발되었다. 이 CSRF 토큰 오류를 해결하고 2 단계 인증을 사용하려면 아래 코드에 어떤 특별한 변경을해야합니까?

필자의 연구에 따르면 CustomOAuth2RequestFactory (이 링크의 API)가 AuthorizationRequests 및 TokenRequests를 관리하기위한 방법을 정의하므로 솔루션을 구성 할 수있는 곳이 될 수 있습니다.

공식 OAuth2 사양의이 절에서는 인증 끝점에 대한 요청의 상태 매개 변수가 csrf 토큰이 추가 된 위치임을 나타냅니다.

또한 링크의 코드는 공식 사양에 대한이 링크에 설명 된 인증 코드 부여 유형을 사용합니다. 즉, 흐름의 단계 C가 csrf 코드를 업데이트하지 않으므로 단계 D에서 오류가 발생합니다. 공식 흐름에 C 단계와 D 단계를 포함한 전체 흐름).

현재 오류를 둘러싸고있는 제어 흐름 :

아래의 순서도에서 TwoFactorAuthenticationFilter를 통해 SECOND PASS 중에 현재 오류가 발생했습니다. 제어 흐름이 SECOND PASS에 도달 할 때까지 모든 것이 의도 한대로 작동합니다.

다음 순서도는 다운로드 가능한 앱의 코드에서 사용되는 2 요소 인증 프로세스의 제어 흐름을 보여줍니다.

특히, POST 및 GET 시퀀스에 대한 Firefox HTTP 헤더는 시퀀스의 모든 요청과 함께 동일한 XSRF 쿠키가 전송되었음을 보여줍니다. XSRF 토큰 값은 / oauth / authorize 및 / oauth / token 엔드 포인트에서 서버 처리를 트리거하는 POST / secure / two_factor_authentication 이후에야 문제가 발생하지 않으며 / oauth / token은 잘못된 CSRF 토큰을 던져 http : / localhost : 9999 / uaa / oauth / token 오류입니다.

위의 제어 흐름도와 / oauth / authorize 및 / oauth / token 끝점 간의 관계를 이해하려면 위의 흐름도를 별도의 브라우저 창에서 공식 사양의 단일 요인 흐름 차트와 나란히 비교할 수 있습니다. 위의 두 번째 패스는 두 번째로 한 요소 공식에서 나온 단계를 단순히 실행하지만 두 번째 패스에서는 더 큰 권한을가집니다.

로그 내용 :

HTTP 요청 및 응답 헤더는 다음을 나타냅니다.

1.) 올바른 사용자 이름과 비밀번호로 제출 된 POST는 9999 / secure / two_factor_authenticated로 이어지는 9999 / authorize? client_id = acme & redirect_uri = / login & response_type = code & state = sGXQ4v로 리디렉션됩니다. 하나의 XSRF 토큰은이 교환기간에 일정하게 유지됩니다.

2.) 정확한 핀 코드로 9999 / secure / two_factor_authentication에 대한 POST가 동일한 XSRF 토큰을 보내고 POST 9999 / oauth / authorize로 성공적으로 리다이렉트되어 TwoFactorAuthenticationFilter.doFilterInternal ()로 만들어 9999 / oauth / token이지만 9999 / oauth / token은 동일한 이전 XSRF 토큰이 FIRST PASS 중에 분명히 생성 된 새 XSRF 토큰 값과 일치하지 않기 때문에 요청을 거부합니다.

1)과 2) 사이의 명백한 차이점은 두 번째 요청 9999 / oauth / authorize 2)에 9999 / authorize에 대한 첫 번째 요청에 포함 된 url 매개 변수가 포함되어 있지 않다는 것입니다. client_id = acme & redirect_uri = / login & response_type = 코드 및 상태 = 1로 sGXQ4v), 공식 스펙에 정의되어 있습니다. 그러나 이것이 문제를 일으키는 지 여부는 분명하지 않습니다.

또한 TwoFactorAuthenticationController.POST에서 완전히 형성된 요청을 보내기 위해 매개 변수에 액세스하는 방법이 명확하지 않습니다. 나는 POST 9999 / secure / two_factor_authentication 컨트롤러 메소드에 대한 HttpServletRequest에서 매개 변수 Map의 SYSO를 수행했으며, 그 모두는 pinVal 및 _csrf 변수이다.

이 링크를 클릭하면 파일 공유 사이트에서 모든 HTTP 헤더 및 스프링 부트 로그를 읽을 수 있습니다.

실패한 접근법 :

스프링 보안 3.2 환경에서 비슷한 문제에 대한 @ RobWinch의 접근 방식을 시도했지만 접근 방식이 Spring OAuth2의 컨텍스트에 적용되지 않는 것처럼 보입니다. 특히, 아래에 표시된 TwoFactorAuthenticationFilter 코드에서 다음 XSRF 업데이트 코드 블록의 주석 처리를 제거하면 다운 스트림 요청 헤더에 다른 / 새 XSRF 토큰 값이 표시되지만 동일한 오류가 발생합니다.

if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
    CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
    response.setHeader("XSRF-TOKEN"/*"X-CSRF-TOKEN"*/, token.getToken());
}

이는 / oauth / authorize 및 / oauth / token이 서로 통신하고 클라이언트 및 자원 응용 프로그램과 통신하여 XSRF 토큰 값을 성공적으로 관리 할 수 ​​있도록 XSRF 구성을 업데이트해야 함을 나타냅니다. 아마도 CustomOAuth2RequestFactory가이 작업을 수행하기 위해 변경해야 할 것입니다. 그러나 어떻게?

관련 코드 :

맞춤 OAuth2RequestFactory의 코드는 다음과 같습니다.

public class CustomOAuth2RequestFactory extends DefaultOAuth2RequestFactory {

    public static final String SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME = "savedAuthorizationRequest";

    public CustomOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
        super(clientDetailsService);
    }

    @Override
    public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession session = attr.getRequest().getSession(false);
        if (session != null) {
            AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
            if (authorizationRequest != null) {
                session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
                return authorizationRequest;
            }
        }

        return super.createAuthorizationRequest(authorizationParameters);
    }
}

Two Factor 인증 필터의 코드는 다음과 같습니다.

//This class is added per: https://stackoverflow.com/questions/30319666/two-factor-authentication-with-spring-security-oauth2
/**
 * Stores the oauth authorizationRequest in the session so that it can
 * later be picked by the {@link com.example.CustomOAuth2RequestFactory}
 * to continue with the authoriztion flow.
 */
public class TwoFactorAuthenticationFilter extends OncePerRequestFilter {

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    private OAuth2RequestFactory oAuth2RequestFactory;
    //These next two are added as a test to avoid the compilation errors that happened when they were not defined.
    public static final String ROLE_TWO_FACTOR_AUTHENTICATED = "ROLE_TWO_FACTOR_AUTHENTICATED";
    public static final String ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED = "ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED";

    @Autowired
    public void setClientDetailsService(ClientDetailsService clientDetailsService) {
        oAuth2RequestFactory = new DefaultOAuth2RequestFactory(clientDetailsService);
    }

    private boolean twoFactorAuthenticationEnabled(Collection<? extends GrantedAuthority> authorities) {
        System.out.println(">>>>>>>>>>> List of authorities includes: ");
        for (GrantedAuthority authority : authorities) {
            System.out.println("auth: "+authority.getAuthority() );
        }
        return authorities.stream().anyMatch(
            authority -> ROLE_TWO_FACTOR_AUTHENTICATION_ENABLED.equals(authority.getAuthority())
        );
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("------------------ INSIDE TwoFactorAuthenticationFilter.doFilterInternal() ------------------------");
        // Check if the user hasn't done the two factor authentication.
        if (AuthenticationUtil.isAuthenticated() && !AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)) {
        System.out.println("++++++++++++++++++++++++ AUTHENTICATED BUT NOT TWO FACTOR +++++++++++++++++++++++++");
        AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(paramsFromRequest(request));
            /* Check if the client's authorities (authorizationRequest.getAuthorities()) or the user's ones
               require two factor authenticatoin. */
        System.out.println("======================== twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) is: " + twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) );
        System.out.println("======================== twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) is: " + twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities()) );
        if (twoFactorAuthenticationEnabled(authorizationRequest.getAuthorities()) ||
                twoFactorAuthenticationEnabled(SecurityContextHolder.getContext().getAuthentication().getAuthorities())) {
                // Save the authorizationRequest in the session. This allows the CustomOAuth2RequestFactory
                // to return this saved request to the AuthenticationEndpoint after the user successfully
                // did the two factor authentication.
                request.getSession().setAttribute(CustomOAuth2RequestFactory.SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME, authorizationRequest);

                // redirect the the page where the user needs to enter the two factor authentiation code
                redirectStrategy.sendRedirect(request, response,
                    ServletUriComponentsBuilder.fromCurrentContextPath()
                        .path(TwoFactorAuthenticationController.PATH)
                        .toUriString());
                return;
            }
        }
        //THE NEXT "IF" BLOCK DOES NOT RESOLVE THE ERROR WHEN UNCOMMENTED
        //if(AuthenticationUtil.hasAuthority(ROLE_TWO_FACTOR_AUTHENTICATED)){
        //    CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
            // this is the value of the token to be included as either a header or an HTTP parameter
        //    response.setHeader("XSRF-TOKEN", token.getToken());
        //}

        filterChain.doFilter(request, response);
    }

    private Map<String, String> paramsFromRequest(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            params.put(entry.getKey(), entry.getValue()[0]);
        }
        return params;
    }
}

컴퓨터 문제 재현 :

다음과 같은 간단한 단계를 수행하면 몇 분 안에 모든 컴퓨터에서 문제를 재현 할 수 있습니다.

1.)이 링크를 클릭하여 파일 공유 사이트에서 응용 프로그램의 압축 된 버전을 다운로드하십시오.

2.) 다음을 입력하여 응용 프로그램의 압축을 풉니 다. tar -zxvf oauth2.tar (2) .gz

3.) oauth2 / authserver로 이동 한 다음 mvn spring-boot :를 입력하여 authserver 응용 프로그램을 실행하십시오.

4.) oauth2 / resource로 이동 한 다음 mvn을 입력하여 리소스 앱을 시작합니다. spring-boot : run

5.) oauth2 / ui로 이동 한 다음 mvn spring-boot :를 입력하여 UI 응용 프로그램을 시작합니다

6.) 웹 브라우저를 열고 http : // localhost : 8080으로 이동하십시오

7.) 로그인을 클릭 한 다음 사용자로 Frodo를 입력하고 암호로 MyRing을 입력하고 제출을 클릭하십시오.

8.) 5309를 Pin Code로 입력하고 제출을 클릭하십시오. 위의 오류가 트리거됩니다.

다음과 같은 방법으로 전체 소스 코드를 볼 수 있습니다.

a.) maven 프로젝트를 IDE로 가져 오거나,

b.) 압축 해제 된 디렉토리를 탐색하고 텍스트 편집기로여십시오.

이 링크를 클릭하면 파일 공유 사이트에서 모든 HTTP 헤더 및 스프링 부트 로그를 읽을 수 있습니다.

해결법

  1. ==============================

    1.내 머리에 튀어 나온 한 가지 생각 :

    내 머리에 튀어 나온 한 가지 생각 :

    세션 고정이 활성화 된 경우 사용자가 성공적으로 인증 된 후에 새 세션이 만들어집니다 (SessionFixationProtectionStrategy 참조). 또한 기본 HttpSessionCsrfTokenRepository를 사용하는 경우 새 csrf 토큰을 만듭니다. XSRF-TOKEN 헤더를 언급하고 있기 때문에 일부 JavaScript 프론트 엔드를 사용한다고 가정합니다. 로그인에 사용 된 원래의 csrf 토큰이 저장되고 나중에 다시 사용된다고 상상할 수 있습니다.이 csrf 토큰이 더 이상 유효하지 않기 때문에 작동하지 않습니다.

    세션 고정을 비활성화 (http.sessionManagement (). sessionFixation (). none () 또는 ) 시도하거나 로그인 후에 현재 CSRF 토큰을 다시 얻을 수 있습니다.

  2. ==============================

    2.CustomOAuth2RequestFactory는 현재 요청의 현재 위치에서 이전 요청을 처리합니다. 그러나이 스위치를 만들 때 이전 요청에서 XSRF 토큰을 업데이트하지 않습니다. 다음은 업데이트 된 CustomOAuth2Request에 대한 제안입니다.

    CustomOAuth2RequestFactory는 현재 요청의 현재 위치에서 이전 요청을 처리합니다. 그러나이 스위치를 만들 때 이전 요청에서 XSRF 토큰을 업데이트하지 않습니다. 다음은 업데이트 된 CustomOAuth2Request에 대한 제안입니다.

    @Override
    public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession session = attr.getRequest().getSession(false);
        if (session != null) {
            AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
            if (authorizationRequest != null) {
                session.removeAttribute(SAVED_AUTHORIZATION_REQUEST_SESSION_ATTRIBUTE_NAME);
    //UPDATE THE STATE VARIABLE WITH THE NEW TOKEN.  THIS PART IS NEW
                CsrfToken csrf = (CsrfToken) attr.getRequest().getAttribute(CsrfToken.class.getName());
                String attrToken = csrf.getToken();
                authorizationRequest.setState(attrToken);                
    
                return authorizationRequest;
            }
        }
    
        return super.createAuthorizationRequest(authorizationParameters);
    }
    

    내 초기 답변 초안이 downvoted 때문에 나는 이것을 다시 방문하고 있습니다. 이 버전은 동일한 경로를 따라 더 가깝습니다. 올바른 접근 방법이라고 생각합니다.

  3. from https://stackoverflow.com/questions/37061697/invalid-xsrf-token-at-oauth-token by cc-by-sa and MIT license