Skip to content

SAML Saml2WebSsoAuthenticationFilter SHOULD wrap AuthenticationConverter convert exception to AuthenticationException #9310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
GitHanter opened this issue Dec 24, 2020 · 8 comments
Assignees
Labels
in: saml2 An issue in SAML2 modules status: feedback-provided Feedback has been provided type: enhancement A general enhancement

Comments

@GitHanter
Copy link
Contributor

Saml2WebSsoAuthenticationFilter doesn't catch AuthenticationConverter convert exception, we should wrapper the exception as a Saml2AuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, ex.getMessage(), ex).

May be we can add a configure method to Saml2LoginConfigurer to configure property authenticationConverter of Saml2WebSsoAuthenticationFilter , e.g through getSharedOrBean so that developer can customize it as a Bean

@GitHanter GitHanter added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Dec 24, 2020
@jzheaux
Copy link
Contributor

jzheaux commented Jan 5, 2021

Thanks, @GitHanter, for the suggestion.

Can you elaborate on what problems you are experiencing from the code the way that it is? The reason I ask is that it's probably a bit too aggressive to state that any exception from AuthenticationConverter is an INTERNAL_VALIDATION_ERROR. There may be ways to improve the error handling here, but I'll need more specifics to understand your situation.

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue in: saml2 An issue in SAML2 modules and removed status: waiting-for-triage An issue we've not yet triaged labels Jan 5, 2021
@jzheaux jzheaux self-assigned this Jan 6, 2021
@spring-projects-issues
Copy link

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Jan 12, 2021
@GitHanter
Copy link
Contributor Author

GitHanter commented Jan 13, 2021

Thanks, @GitHanter, for the suggestion.

Can you elaborate on what problems you are experiencing from the code the way that it is? The reason I ask is that it's probably a bit too aggressive to state that any exception from AuthenticationConverter is an INTERNAL_VALIDATION_ERROR. There may be ways to improve the error handling here, but I'll need more specifics to understand your situation.

Because the Saml2AuthenticationTokenConverter can throw Saml2Exception or any other non-AuthenticationException Exception, in case of that, the AbstractAuthenticationProcessingFilter will not able to catch the Exception. I think we need wrap the exception as a AuthenticationException , it can be Saml2AuthenticationException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR...) or it can be Saml2AuthenticationException with another ErrorCode, so we can handle the exception in AuthenticationFailureHandler, currently I have to do following decorate

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    @SuppressWarnings("unchecked")
    protected void configure(HttpSecurity http) throws Exception {
        //make all exception thrown from Saml2WebSsoAuthenticationFilter AuthenticationException, so the
        //AuthenticationFailureHandler will take over control
        ObjectPostProcessor<Saml2WebSsoAuthenticationFilter> objectPostProcessor = new ObjectPostProcessor<Saml2WebSsoAuthenticationFilter>() {
            @Override
            public <O extends Saml2WebSsoAuthenticationFilter> O postProcess(O object) {
                Field field = ReflectionUtils.findField(Saml2WebSsoAuthenticationFilter.class, "authenticationConverter");
                ReflectionUtils.makeAccessible(Objects.requireNonNull(field));
                Saml2AuthenticationTokenConverter originalConverter = (Saml2AuthenticationTokenConverter) ReflectionUtils.getField(field, object);
                AuthenticationConverter decorator = (request) -> {
                    try {
                        return Objects.requireNonNull(originalConverter).convert(request);
                    } catch (AuthenticationException ex) {
                        throw ex;
                    } catch (Exception ex) {
                        throw new Saml2AuthenticationException(new Saml2Error(SAML_SSO_INTERNAL_ERROR, ex.getMessage()), ex);
                    }
                };
                ReflectionUtils.setField(field, object, decorator);

                return object;
            }
        };
}

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue labels Jan 13, 2021
@jzheaux
Copy link
Contributor

jzheaux commented Jan 19, 2021

Thanks for the extra detail, @GitHanter.

I wonder if the following would be simpler:

AuthenticationConverter converter = new Saml2AuthenticationTokenConverter(...);
http
    .saml2Login((saml2) -> saml2
        .authenticationConverter(new MyExceptionWrappingAuthenticationConverter(converter))
    );

Would that work to remove the reflection?

It may be valuable to change Saml2AuthenticationTokenConverter to use Saml2AuthenticationException since otherwise the unsuccessfulAuthentication method won't get invoked.

To better understand the right error code, under what circumstances are you getting an exception? Specifically, we need to be clear whether errors are the client's fault or the server's fault.

@jzheaux jzheaux added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Jan 19, 2021
@GitHanter
Copy link
Contributor Author

GitHanter commented Jan 26, 2021

@jzheaux
Thanks for the authenticationConverter tips, we use spring-security v5.3.6.RELEASE so we didn't see the authenticationConverter configure method in class Saml2LoginConfigurer, after upgrade to latest version, we can do exactly you specified in the comment to remove the reflection.

For Saml2AuthenticationException issue, you can ref org.springframework.security.web.authentication.www.BasicAuthenticationConverter, this AuthenticationConverter handled convert exception internally and wrapper it to a BadCredentialsException

@Override
	public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) {
		String header = request.getHeader(AUTHORIZATION);
		if (header == null) {
			return null;
		}

		header = header.trim();
		if (!StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) {
			return null;
		}

		byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
		byte[] decoded;
		try {
			decoded = Base64.getDecoder().decode(base64Token);
		}
		catch (IllegalArgumentException e) {
			throw new BadCredentialsException(
					"Failed to decode basic authentication token");
		}

		String token = new String(decoded, getCredentialsCharset(request));

		int delim = token.indexOf(":");

		if (delim == -1) {
			throw new BadCredentialsException("Invalid basic authentication token");
		}
		UsernamePasswordAuthenticationToken result  = new UsernamePasswordAuthenticationToken(token.substring(0, delim), token.substring(delim + 1));
		result.setDetails(this.authenticationDetailsSource.buildDetails(request));
		return result;
	}

For Saml2AuthenticationTokenConverter

@Override
	public Saml2AuthenticationToken convert(HttpServletRequest request) {
		RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationResolver.convert(request);
		if (relyingPartyRegistration == null) {
			return null;
		}
		String saml2Response = request.getParameter("SAMLResponse");
		if (saml2Response == null) {
			return null;
		}
		byte[] b = samlDecode(saml2Response);
		saml2Response = inflateIfRequired(request, b);
		return new Saml2AuthenticationToken(relyingPartyRegistration, saml2Response);
	}

	private String inflateIfRequired(HttpServletRequest request, byte[] b) {
		if (HttpMethod.GET.matches(request.getMethod())) {
			return samlInflate(b);
		}
		return new String(b, StandardCharsets.UTF_8);
	}

	private byte[] samlDecode(String s) {
		return BASE64.decode(s);
	}

If saml2Response is not a valid BASE64 encoded string or not deflated properly (client's fault), the covert method will throw the exception directly which will not handle by unsuccessfulAuthentication method.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 26, 2021
@jzheaux
Copy link
Contributor

jzheaux commented Jan 27, 2021

Sounds great, @GitHanter. Would you be able to submit a PR that wraps the deflation and decoding and throws a Saml2AuthenticationException that uses Saml2ErrorCodes.INVALID_RESPONSE?

It would be great to include some tests: First, in Saml2AuthenticationTokenConverterTests for showing that when the converter can't deflate and decode the response, it throws an exception. Second, in Saml2LoginConfigurerTests that demonstrates that the authentication failed handler gets invoked.

@GitHanter
Copy link
Contributor Author

@jzheaux Sure, I'll submit the PR with #9317

@jzheaux
Copy link
Contributor

jzheaux commented Jan 28, 2021

Sounds good, @GitHanter. In that case, please make sure that the two changes are in separate commits as this simplifies maintenance.

GitHanter added a commit to GitHanter/spring-security that referenced this issue Feb 27, 2021
…eption and inflate exception to Saml2AuthenticationException

Update copyright year to 2021

Closes spring-projectsgh-9310
GitHanter added a commit to GitHanter/spring-security that referenced this issue Feb 27, 2021
@jzheaux jzheaux closed this as completed in 6e41246 Mar 2, 2021
jzheaux pushed a commit that referenced this issue Mar 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: saml2 An issue in SAML2 modules status: feedback-provided Feedback has been provided type: enhancement A general enhancement
Projects
None yet
3 participants