1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Remove OpenSaml4 Components

Issue gh-17707
This commit is contained in:
Josh Cummings
2025-08-14 18:01:02 -06:00
parent 2258699f5d
commit 5506c487de
48 changed files with 68 additions and 9856 deletions
@@ -35,18 +35,15 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.OpenSaml4AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.OpenSaml5AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
@@ -381,10 +378,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return openSamlAuthenticationRequestResolver;
}
else {
OpenSaml4AuthenticationRequestResolver openSamlAuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
relyingPartyRegistrationRepository(http));
openSamlAuthenticationRequestResolver.setRequestMatcher(getAuthenticationRequestMatcher());
return openSamlAuthenticationRequestResolver;
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
}
@@ -429,15 +424,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
converter.setRequestMatcher(getLoginProcessingEndpoint());
return converter;
}
authenticationConverterBean = getBeanOrNull(http, OpenSaml4AuthenticationTokenConverter.class);
if (authenticationConverterBean != null) {
return authenticationConverterBean;
}
OpenSaml4AuthenticationTokenConverter converter = new OpenSaml4AuthenticationTokenConverter(
this.relyingPartyRegistrationRepository);
converter.setAuthenticationRequestRepository(getAuthenticationRequestRepository(http));
converter.setRequestMatcher(getLoginProcessingEndpoint());
return converter;
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
private void registerDefaultAuthenticationProvider(B http) {
@@ -448,10 +436,8 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
}
}
else {
OpenSaml4AuthenticationProvider provider = getBeanOrNull(http, OpenSaml4AuthenticationProvider.class);
if (provider == null) {
http.authenticationProvider(postProcess(new OpenSaml4AuthenticationProvider()));
}
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
}
@@ -35,8 +35,6 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2ResponseAssertionAccessor;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator;
@@ -44,9 +42,6 @@ import org.springframework.security.saml2.provider.service.authentication.logout
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestValidatorParametersResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutRequestValidatorParametersResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutResponseResolver;
@@ -250,10 +245,8 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
parameters.setRequestMatcher(requestMatcher);
return parameters;
}
OpenSaml4LogoutRequestValidatorParametersResolver parameters = new OpenSaml4LogoutRequestValidatorParametersResolver(
registrations);
parameters.setRequestMatcher(requestMatcher);
return parameters;
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
private Saml2LogoutResponseFilter createLogoutResponseProcessingFilter(
@@ -384,7 +377,8 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (USE_OPENSAML_5) {
return new OpenSaml5LogoutRequestValidator();
}
return new OpenSaml4LogoutRequestValidator();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
private Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
@@ -394,7 +388,8 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (USE_OPENSAML_5) {
return new OpenSaml5LogoutRequestResolver(registrations);
}
return new OpenSaml4LogoutRequestResolver(registrations);
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
}
@@ -454,7 +449,8 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (USE_OPENSAML_5) {
return new OpenSaml5LogoutResponseValidator();
}
return new OpenSaml4LogoutResponseValidator();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
private Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
@@ -464,7 +460,8 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (USE_OPENSAML_5) {
return new OpenSaml5LogoutResponseResolver(registrations);
}
return new OpenSaml4LogoutResponseResolver(registrations);
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
}
@@ -24,7 +24,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
@@ -113,10 +112,8 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
metadata.setRequestMatcher(getRequestMatcherBuilder().matcher(metadataUrl));
return metadata;
}
RequestMatcherMetadataResponseResolver metadata = new RequestMatcherMetadataResponseResolver(registrations,
new OpenSaml4MetadataResolver());
metadata.setRequestMatcher(getRequestMatcherBuilder().matcher(metadataUrl));
return metadata;
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
};
return this;
}
@@ -156,7 +153,8 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
if (USE_OPENSAML_5) {
return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml5MetadataResolver());
}
return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml4MetadataResolver());
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
private RelyingPartyRegistrationRepository getRelyingPartyRegistrationRepository(H http) {
@@ -24,13 +24,11 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
import org.springframework.util.StringUtils;
@@ -90,16 +88,16 @@ final class Saml2LoginBeanDefinitionParserUtils {
.addConstructorArgValue(defaultRelyingPartyRegistrationResolver)
.getBeanDefinition();
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4AuthenticationRequestResolver.class)
.addConstructorArgValue(defaultRelyingPartyRegistrationResolver)
.getBeanDefinition();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
static BeanDefinition createAuthenticationProvider() {
if (USE_OPENSAML_5) {
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml5AuthenticationProvider.class).getBeanDefinition();
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4AuthenticationProvider.class).getBeanDefinition();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
static BeanMetadataElement getAuthenticationConverter(Element element) {
@@ -22,14 +22,10 @@ import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml4LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutRequestValidator;
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSaml5LogoutResponseValidator;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml4LogoutResponseResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.logout.OpenSaml5LogoutResponseResolver;
import org.springframework.util.StringUtils;
@@ -76,9 +72,8 @@ final class Saml2LogoutBeanDefinitionParserUtils {
.addConstructorArgValue(registrations)
.getBeanDefinition();
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutResponseResolver.class)
.addConstructorArgValue(registrations)
.getBeanDefinition();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
static BeanMetadataElement getLogoutRequestValidator(Element element) {
@@ -89,7 +84,8 @@ final class Saml2LogoutBeanDefinitionParserUtils {
if (USE_OPENSAML_5) {
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml5LogoutRequestValidator.class).getBeanDefinition();
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutRequestValidator.class).getBeanDefinition();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
static BeanMetadataElement getLogoutResponseValidator(Element element) {
@@ -100,7 +96,8 @@ final class Saml2LogoutBeanDefinitionParserUtils {
if (USE_OPENSAML_5) {
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml5LogoutResponseValidator.class).getBeanDefinition();
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutResponseValidator.class).getBeanDefinition();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
static BeanMetadataElement getLogoutRequestRepository(Element element) {
@@ -121,9 +118,8 @@ final class Saml2LogoutBeanDefinitionParserUtils {
.addConstructorArgValue(registrations)
.getBeanDefinition();
}
return BeanDefinitionBuilder.rootBeanDefinition(OpenSaml4LogoutRequestResolver.class)
.addConstructorArgValue(registrations)
.getBeanDefinition();
throw new IllegalArgumentException(
"Spring Security does not support OpenSAML " + Version.getVersion() + ". Please use OpenSAML 5");
}
}
@@ -32,7 +32,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.opensaml.core.Version;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.saml.saml2.core.Assertion;
@@ -69,7 +68,6 @@ import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
@@ -84,7 +82,6 @@ import org.springframework.security.saml2.provider.service.web.DefaultRelyingPar
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.FilterChainProxy;
@@ -139,8 +136,6 @@ public class Saml2LoginConfigurerTests {
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
private static final boolean USE_OPENSAML_5 = Version.getVersion().startsWith("5");
private static String SIGNED_RESPONSE;
private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
@@ -550,13 +545,7 @@ public class Saml2LoginConfigurerTests {
RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
registrations);
if (USE_OPENSAML_5) {
OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
}
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
@@ -589,13 +578,7 @@ public class Saml2LoginConfigurerTests {
RelyingPartyRegistrationRepository registrations) {
RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver(
registrations);
if (USE_OPENSAML_5) {
OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
}
OpenSaml4AuthenticationRequestResolver delegate = new OpenSaml4AuthenticationRequestResolver(
OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver(
registrationResolver);
delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true));
return delegate;
@@ -773,8 +756,7 @@ public class Saml2LoginConfigurerTests {
@Import(Saml2LoginConfigBeans.class)
static class CustomAuthenticationProviderConfig {
private final AuthenticationProvider provider = spy(
USE_OPENSAML_5 ? new OpenSaml5AuthenticationProvider() : new OpenSaml4AuthenticationProvider());
private final AuthenticationProvider provider = spy(new OpenSaml5AuthenticationProvider());
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
@@ -30,7 +30,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.saml2.provider.service.metadata.OpenSaml4MetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.OpenSaml5MetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver;
@@ -159,7 +159,7 @@ public class Saml2MetadataConfigurerTests {
// should ignore
@Bean
Saml2MetadataResponseResolver metadataResponseResolver(RelyingPartyRegistrationRepository registrations) {
return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml4MetadataResolver());
return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml5MetadataResolver());
}
}
@@ -20,7 +20,7 @@ import java.nio.charset.StandardCharsets;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import net.shibboleth.shared.xml.SerializeSupport;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -37,7 +37,7 @@ import org.springframework.security.saml2.provider.service.registration.RelyingP
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
@@ -279,8 +279,8 @@ public class RelyingPartyRegistrationsBeanDefinitionParserTests {
public void parseWhenRelayStateResolverThenUses() {
this.spring.configLocations(xml("RelayStateResolver")).autowire();
Converter<HttpServletRequest, String> relayStateResolver = this.spring.getContext().getBean(Converter.class);
OpenSaml4AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext()
.getBean(OpenSaml4AuthenticationRequestResolver.class);
OpenSaml5AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext()
.getBean(OpenSaml5AuthenticationRequestResolver.class);
MockHttpServletRequest request = get("/saml2/authenticate/one").build();
authenticationRequestResolver.resolve(request);
verify(relayStateResolver).convert(request);
@@ -47,7 +47,7 @@
</asserting-party>
</relying-party-registrations>
<b:bean class="org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver">
<b:bean class="org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver">
<b:constructor-arg ref="registrations"/>
<b:property name="relayStateResolver" ref="relayStateResolver"/>
</b:bean>