Spring Security SAML2 issue signing SAMLRequest - spring-security

I'm using spring boot 2.4.1 and spring security SAML2 support
I successfully configured my Service Provider. I created a self-signed certificate and I'm trying to use an IDP that requires signed AuthnRequests.
This is my RelyingPartyRegistrationRepository configuration:
#Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception{
KeyStore ks = KeyStore.getInstance(this.keyStoreType);
char[] pwd = keyStorePassword != null ? keyStorePassword.toCharArray() : null;
String ksName = keyStoreName.replaceAll("classpath:", "");
Resource keystoreRes = new ClassPathResource(ksName);
ks.load(keystoreRes.getInputStream(), pwd);
PrivateKey privateKey = (PrivateKey)ks.getKey(keyStoreAlias, keyStoreKeyPassword.toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(keyStoreAlias);
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId(registrationId)
.entityId(spEntityId)
.signingX509Credentials((c) -> c.add(Saml2X509Credential.signing(privateKey, cert)))
.decryptionX509Credentials((c)->c.add(Saml2X509Credential.decryption(privateKey, cert)))
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
The application successfully starts but,, every time I makes a new request, I got an exception on the IDP side because no KeyInfo element is found in the AuthnRequest
By seeing my application logs, I found this log:
2021-01-06 12:20:35,650 23472 [XNIO-1 task-7] INFO o.o.x.s.support.SignatureSupport - No KeyInfoGenerator was supplied in parameters or resolveable for credential type org.opensaml.security.x509.X509Credential, No KeyInfo will be generated for Signature
I can't understand if I'm missing something in the configuration.
Please note that the same happens also with a certificate released by a trusted CA and not only with self-signed certificate. So I'm thinking it's a kind of configuration mistake I'm doing or a kind of bug.
May you kindly give me tip in how to solve this issue?
Angelo
UPDATE
I solved my current issue. Anyway I think it's a my mistake. Basically I modified the org.springframework.security.saml2.provider.service.authentication.OpenSamlAuthenticationRequestFactory
I added the following method:
private KeyInfoGenerator x509KeyInfoGenerator() {
X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory();
generator.setEmitEntityCertificate(true);
generator.setEmitEntityCertificateChain(true);
return generator.newInstance();
}
I called this method here:
private SignatureSigningParameters resolveSigningParameters(RelyingPartyRegistration relyingPartyRegistration) {
List<Credential> credentials = resolveSigningCredentials(relyingPartyRegistration);
List<String> algorithms = Collections.singletonList(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
List<String> digests = Collections.singletonList(SignatureConstants.ALGO_ID_DIGEST_SHA256);
String canonicalization = SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS;
SignatureSigningParametersResolver resolver = new SAMLMetadataSignatureSigningParametersResolver();
CriteriaSet criteria = new CriteriaSet();
BasicSignatureSigningConfiguration signingConfiguration = new BasicSignatureSigningConfiguration();
signingConfiguration.setSigningCredentials(credentials);
signingConfiguration.setSignatureAlgorithms(algorithms);
signingConfiguration.setSignatureReferenceDigestMethods(digests);
signingConfiguration.setSignatureCanonicalizationAlgorithm(canonicalization);
criteria.add(new SignatureSigningConfigurationCriterion(signingConfiguration));
try {
SignatureSigningParameters parameters = resolver.resolveSingle(criteria);
parameters.setKeyInfoGenerator(x509KeyInfoGenerator());
Assert.notNull(parameters, "Failed to resolve any signing credential");
return parameters;
}
catch (Exception ex) {
throw new Saml2Exception(ex);
}
}
Now I don't have errors on IdP side but I'm thinking I'm missing something in my configuration. This is my whole web security configuration:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class ApplicazioneMockWebSecurityCfg extends WebSecurityConfigurerAdapter {
static {
OpenSamlInitializationService.requireInitialize((registry) -> {
X509KeyInfoGeneratorFactory generator = new X509KeyInfoGeneratorFactory();
generator.setEmitEntityCertificate(true);
generator.setEmitEntityCertificateChain(true);
NamedKeyInfoGeneratorManager manager = new NamedKeyInfoGeneratorManager();
manager.registerDefaultFactory(generator);
});
}
#Value("${applicazione.mock.external.idp.metadata.location}")
private String assertingPartyMetadataLocation;
#Value("${applicazione.mock.external.idp.metadata.registration.id}")
private String registrationId;
#Value("${server.ssl.key-alias}")
private String keyStoreAlias;
#Value("${server.ssl.key-password}")
private String keyStoreKeyPassword;
#Value("${server.ssl.key-store-password}")
private String keyStorePassword;
#Value("${server.ssl.keystore}")
private String keyStoreName;
#Value("${server.ssl.key-store-type}")
private String keyStoreType;
#Value("${sael.spid.service.provider.applicazione.mock.metadata.entity.id}")
private String spEntityId;
public static final String LOGOUT_URL = "/public/logout";
public static final String LOGIN_PAGE = "/public/home";
#Override
protected void configure(HttpSecurity http) throws Exception {
OpenSamlAuthenticationProvider authenticationProvider = new OpenSamlAuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
// Saml2Authentication authentication = OpenSamlAuthenticationProvider
// .createDefaultResponseAuthenticationConverter()
// .convert(responseToken);
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
String username = assertion.getSubject().getNameID().getValue();
List<AttributeStatement> attrStatements = assertion.getAttributeStatements();
String valoreAttributo = null;
Map<String, String> samlAttributes = new HashMap<>();
for (AttributeStatement attrStatement : attrStatements) {
List<Attribute> attrs = attrStatement.getAttributes();
for (Attribute attr : attrs) {
String nomeAttributo = attr.getName();
List<XMLObject> valoriAttributo = attr.getAttributeValues();
//In genere la lista dei valori รจ di 1 elemento
XMLObject valueObj = valoriAttributo.get(0);
valoreAttributo = getValue(valueObj, valoreAttributo);
samlAttributes.put(nomeAttributo, valoreAttributo);
}
}
if( !StringUtils.hasText(valoreAttributo) ) {
throw new IllegalStateException("Impossibile proseguire. Codice Fiscale non trovato tra gli attributi SAML");
}
UserDetails userDetails = new ApplicazioneMockLoggedUser(username, "[PROTECTED]", samlAttributes, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
return new SaelSamlAuthentication(userDetails);
});
Converter<HttpServletRequest, RelyingPartyRegistration> relyingPartyRegistrationResolver =
new DefaultRelyingPartyRegistrationResolver(this.relyingPartyRegistrations());
http
.authorizeRequests()
.antMatchers("/protected/**")
.authenticated()
.antMatchers("/public/**")
.permitAll()
.and()
.saml2Login(authorize ->{
authorize
.loginPage(LOGIN_PAGE)
.authenticationManager(new ProviderManager(authenticationProvider))
;
})
.logout(logout->{
logout
.logoutUrl(LOGOUT_URL)
.logoutSuccessHandler(saelLogoutSuccessHanlder())
.logoutRequestMatcher(saelRequestMatcher())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
//.logoutSuccessUrl(LOGIN_PAGE+"?logout")
.permitAll();
})
.addFilterBefore(new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver()), Saml2WebSsoAuthenticationFilter.class);
}
#Bean
public RequestMatcher saelRequestMatcher() {
return new SaelRequestMatcher();
}
#Bean
public LogoutSuccessHandler saelLogoutSuccessHanlder() {
return new SaelLogoutSuccessHandler();
}
#Bean
Saml2AuthenticationRequestFactory authenticationRequestFactory(
AuthnRequestConverter authnRequestConverter) {
OpenSamlAuthenticationRequestFactory authenticationRequestFactory =
new OpenSamlAuthenticationRequestFactory();
authenticationRequestFactory.setAuthenticationRequestContextConverter(authnRequestConverter);
return authenticationRequestFactory;
}
#Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception{
KeyStore ks = KeyStore.getInstance(this.keyStoreType);
char[] pwd = keyStorePassword != null ? keyStorePassword.toCharArray() : null;
String ksName = keyStoreName.replaceAll("classpath:", "");
Resource keystoreRes = new ClassPathResource(ksName);
ks.load(keystoreRes.getInputStream(), pwd);
PrivateKey privateKey = (PrivateKey)ks.getKey(keyStoreAlias, keyStoreKeyPassword.toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(keyStoreAlias);
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId(registrationId)
.entityId(spEntityId)
.signingX509Credentials((c) -> c.add(Saml2X509Credential.signing(privateKey, cert)))
.decryptionX509Credentials((c)->c.add(Saml2X509Credential.decryption(privateKey, cert)))
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
private String getValue( XMLObject valueObj, String defaultValue ) {
if( valueObj instanceof XSStringImpl ) {
XSStringImpl stringImpl = (XSStringImpl)valueObj;
return stringImpl.getValue();
}
return defaultValue;
}
}
May you help me in understanding if I'm missing something (I think I'm missing something)

Related

Walmart.io authentication issue - Could not authenticate in-request, auth signature in C#

I am trying to implement for C#, here is my code:
WebClient downloader = new WebClient();
downloader.Headers["WM_CONSUMER.ID"] = consumerId;
long intimestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds;
downloader.Headers["WM_CONSUMER.INTIMESTAMP"] = intimestamp.ToString();
downloader.Headers["WM_SEC.KEY_VERSION"] = priviateKeyVersion;
string data = downloader.Headers["WM_CONSUMER.ID"] + "\n" + downloader.Headers["WM_CONSUMER.INTIMESTAMP"] + "\n" + downloader.Headers["WM_SEC.KEY_VERSION"] + "\n";
downloader.Headers["WM_SEC.WM_SEC.AUTH_SIGNATURE"] = getWalmartSig(data);
url = "https://developer.api.walmart.com/api-proxy/service/affil/product/v2/items/" + id;
string json = downloader.DownloadString(url);
to get signature, I use BouncyCastle
private string getWalmartSig(string data)
{
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(#"key.pem"))
{ // file containing RSA PKCS1 private key
keyPair = (AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
RSAParameters rsaParam = DotNetUtilities.ToRSAParameters((RsaKeyParameters)keyPair.Public);
ISigner signer = SignerUtilities.GetSigner("SHA256WithRSA");
signer.Init(true, keyPair.Private);
byte[] msg = Encoding.UTF8.GetBytes(data);
signer.BlockUpdate(msg, 0, msg.Length);
return Convert.ToBase64String(signer.GenerateSignature());
}
}
keep getting forbidden. Please help.
If your private key has a password you have to get the pair using another method.
private static string getWalmartSig(string data)
{
try
{
AsymmetricCipherKeyPair keyPair;
using (var reader = File.OpenText(#"key.pem"))
{ // file containing RSA PKCS1 private key
keyPair = DecodePrivateKey(reader.ReadToEnd(), Constants.password); //modified to include password for reading private key.
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
RSAParameters rsaParam = DotNetUtilities.ToRSAParameters((RsaKeyParameters)keyPair.Public);
ISigner signer = SignerUtilities.GetSigner("SHA256WITHRSAENCRYPTION"); //CryptoConfig.MapNameToOID("SHA256") //SHA256WithRSA //modified for using different Encryption.
signer.Init(true, keyPair.Private);
byte[] msg = Encoding.UTF8.GetBytes(data);
signer.BlockUpdate(msg, 0, msg.Length);
return Convert.ToBase64String(signer.GenerateSignature());
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
return null;
}
}
Refer to Decrypt passphrase protected PEM containing private key for reference.
private static AsymmetricCipherKeyPair DecodePrivateKey(string encryptedPrivateKey, string password) //from https://stackoverflow.com/questions/44767290/decrypt-passphrase-protected-pem-containing-private-key
{
try
{
TextReader textReader = new StringReader(encryptedPrivateKey);
PemReader pemReader = new PemReader(textReader, new PasswordFinder(password));
var privateKeyObject = (AsymmetricCipherKeyPair)pemReader.ReadObject(); //modified for direct casting.
RsaPrivateCrtKeyParameters rsaPrivatekey = (RsaPrivateCrtKeyParameters)privateKeyObject.Private; //modified to use the private key
RsaKeyParameters rsaPublicKey = new RsaKeyParameters(false, rsaPrivatekey.Modulus, rsaPrivatekey.PublicExponent);
AsymmetricCipherKeyPair kp = new AsymmetricCipherKeyPair(rsaPublicKey, rsaPrivatekey);
return kp;
}
catch (Exception ex)
{
Console.WriteLine(ex);
return null;
}
}
And now the extension class. Refer to the same link for reference
private class PasswordFinder : IPasswordFinder
{
private string password;
public PasswordFinder(string password)
{
this.password = password;
}
public char[] GetPassword()
{
return password.ToCharArray();
}
}
Notice the changes I have made to the methods. That should get your code running.
Olorunfemi Ajibulu is correct, you have a typo on your AUTH_SIGNATURE header name. This is why you are getting a forbidden. However once you correct that, I can almost guarantee you will be getting a 401 from here on out. API does not seem to be authenticating.

Manually set authentication with ReactiveSecurityContextHolder

I'm trying to setup Spring Security with Spring Web Flux. I don't understand how to manually set the SecurityContext with ReactiveSecurityContextHolder. Do you have any resource or hint?
Take for example this filter I've written that reads a JWT token and needs to set the authentication manually:
#Slf4j
public class JwtTokenAuthenticationFilter implements WebFilter {
private final JwtAuthenticationConfig config;
private final JwtParser jwtParser = Jwts.parser();
public JwtTokenAuthenticationFilter(JwtAuthenticationConfig config) {
this.config = config;
jwtParser.setSigningKey(config.getSecret().getBytes());
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst(config.getHeader());
if (token != null && token.startsWith(config.getPrefix() + " ")) {
token = token.replace(config.getPrefix() + " ", "");
try {
Claims claims = jwtParser.parseClaimsJws(token).getBody();
String username = claims.getSubject();
#SuppressWarnings("unchecked")
List<String> authorities = claims.get("authorities", List.class);
if (username != null) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
// TODO set authentication into ReactiveSecurityContextHolder
}
} catch (Exception ex) {
log.warn(ex.toString(), ex);
ReactiveSecurityContextHolder.clearContext();
}
}
return chain.filter(exchange);
}
}
I managed to update the SecurityContext by calling:
return chain.filter(exchange).subscriberContext(ReactiveSecurityContextHolder.withAuthentication(auth));
Correct me if I'm wrong or if there is a better way to manage it.
I searched a lot about this issue and get this thing worked.
You can try setting the context while passing the filter chain like below.
return chain.filter(exchange).contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));

Generate nonce in an Spring Security application using OpenID connect

i'm plugging a Spring security application to an IDP/OP (IDentity Provider, or Openid connect Identity Provider according to the OpenID connect terminology)
I'm using the authorization code flow.
I used this implementation to start my code :
https://github.com/gazbert/openid-connect-spring-client
It's working with several IDP, until i found one that requires the nonce parameter.
However i could not managed to configure my application to generate a nonce, and add it in the url (I know that's the nonce because when i add it manually : it works)
It's when the application redirect the user to the IDP (authorization endpoint) that i wish to have a nonce.
And it would be perfect if the nonce could be verified on the return.
I searched the web for 2 hours, i found this may be the thing to use
org.springframework.security.oauth.provider.nonce
but didn't found any example, or clue on how to add it in my code
Here is the interesting part of the code where i think i have to tell Spring to use the nonce :
public OAuth2RestTemplate getOpenIdConnectRestTemplate(#Qualifier("oauth2ClientContext")
OAuth2ClientContext clientContext) {
return new OAuth2RestTemplate(createOpenIdConnectCodeConfig(), clientContext);
}
public OAuth2ProtectedResourceDetails createOpenIdConnectCodeConfig() {
final AuthorizationCodeResourceDetails resourceDetails = new AuthorizationCodeResourceDetails();
resourceDetails.setClientAuthenticationScheme(AuthenticationScheme.form); // include client credentials in POST Content
resourceDetails.setClientId(clientId);
resourceDetails.setClientSecret(clientSecret);
resourceDetails.setUserAuthorizationUri(authorizationUri);
resourceDetails.setAccessTokenUri(tokenUri);
final List<String> scopes = new ArrayList<>();
scopes.add("openid"); // always need this
scopes.addAll(Arrays.asList(optionalScopes.split(",")));
resourceDetails.setScope(scopes);
resourceDetails.setPreEstablishedRedirectUri(redirectUri);
resourceDetails.setUseCurrentUri(false);
return resourceDetails;
}
If there is a modification i believe it's there.
If that's a duplicate i apologies, and i'll never shame myself again.
Any help would be appreciated, i can post more details if needed, i didn't want to confuse by posting too much
Thanks for reading me
I struggled with this as well. Fortunately, there is some recent developments in Spring Security documentation, and after some back and forth with one of the GitHub developers, I came up with a solution in Kotlin (translating to Java should be fairly easy). The original discussion can be found here.
Ultimately, my SecurityConfig class ended up looking like this:
#EnableWebSecurity
class SecurityConfig #Autowired constructor(loginGovConfiguration: LoginGovConfiguration) : WebSecurityConfigurerAdapter() {
#Autowired
lateinit var clientRegistrationRepository: ClientRegistrationRepository
private final val keystore: MutableMap<String, String?> = loginGovConfiguration.keystore
private final val keystoreUtil: KeystoreUtil = KeystoreUtil(
keyStore = keystore["file"],
keyStorePassword = keystore["password"],
keyAlias = keystore["alias"],
keyPassword = null,
keyStoreType = keystore["type"]
)
private final val allowedOrigin: String = loginGovConfiguration.allowedOrigin
companion object {
const val LOGIN_ENDPOINT = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL
const val LOGIN_SUCCESS_ENDPOINT = "/login_success"
const val LOGIN_FAILURE_ENDPOINT = "/login_failure"
const val LOGIN_PROFILE_ENDPOINT = "/login_profile"
const val LOGOUT_ENDPOINT = "/logout"
const val LOGOUT_SUCCESS_ENDPOINT = "/logout_success"
}
override fun configure(http: HttpSecurity) {
http.authorizeRequests()
// login, login failure, and index are allowed by anyone
.antMatchers(
LOGIN_ENDPOINT,
LOGIN_SUCCESS_ENDPOINT,
LOGIN_PROFILE_ENDPOINT,
LOGIN_FAILURE_ENDPOINT,
LOGOUT_ENDPOINT,
LOGOUT_SUCCESS_ENDPOINT,
"/"
)
.permitAll()
// any other requests are allowed by an authenticated user
.anyRequest()
.authenticated()
.and()
// custom logout behavior
.logout()
.logoutRequestMatcher(AntPathRequestMatcher(LOGOUT_ENDPOINT))
.logoutSuccessUrl(LOGOUT_SUCCESS_ENDPOINT)
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutSuccessHandler(LoginGovLogoutSuccessHandler())
.and()
// configure authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider
.oauth2Login()
.authorizationEndpoint()
.authorizationRequestResolver(LoginGovAuthorizationRequestResolver(clientRegistrationRepository))
.authorizationRequestRepository(authorizationRequestRepository())
.and()
.tokenEndpoint()
.accessTokenResponseClient(accessTokenResponseClient())
.and()
.failureUrl(LOGIN_FAILURE_ENDPOINT)
.successHandler(LoginGovAuthenticationSuccessHandler())
}
#Bean
fun corsFilter(): CorsFilter {
// fix OPTIONS preflight login profile request failure with 403 Invalid CORS request
val config = CorsConfiguration()
config.addAllowedOrigin(allowedOrigin)
config.allowCredentials = true
config.allowedHeaders = listOf("x-auth-token", "Authorization", "cache", "Content-Type")
config.addAllowedMethod(HttpMethod.OPTIONS)
config.addAllowedMethod(HttpMethod.GET)
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration(LOGIN_PROFILE_ENDPOINT, config)
return CorsFilter(source)
}
#Bean
fun authorizationRequestRepository(): AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
return HttpSessionOAuth2AuthorizationRequestRepository()
}
#Bean
fun accessTokenResponseClient(): OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
val accessTokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
accessTokenResponseClient.setRequestEntityConverter(LoginGovTokenRequestConverter(clientRegistrationRepository, keystoreUtil))
return accessTokenResponseClient
}
}
And my custom authorization resolver LoginGovAuthorizationRequestResolver:
class LoginGovAuthorizationRequestResolver(clientRegistrationRepository: ClientRegistrationRepository) : OAuth2AuthorizationRequestResolver {
private val REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"
private var defaultAuthorizationRequestResolver: OAuth2AuthorizationRequestResolver = DefaultOAuth2AuthorizationRequestResolver(
clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI
)
private val authorizationRequestMatcher: AntPathRequestMatcher = AntPathRequestMatcher(
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}")
override fun resolve(request: HttpServletRequest?): OAuth2AuthorizationRequest? {
val authorizationRequest: OAuth2AuthorizationRequest? = defaultAuthorizationRequestResolver.resolve(request)
return if(authorizationRequest == null)
{ null } else { customAuthorizationRequest(authorizationRequest) }
}
override fun resolve(request: HttpServletRequest?, clientRegistrationId: String?): OAuth2AuthorizationRequest? {
val authorizationRequest: OAuth2AuthorizationRequest? = defaultAuthorizationRequestResolver.resolve(request, clientRegistrationId)
return if(authorizationRequest == null)
{ null } else { customAuthorizationRequest(authorizationRequest) }
}
private fun customAuthorizationRequest(authorizationRequest: OAuth2AuthorizationRequest?): OAuth2AuthorizationRequest {
val registrationId: String = this.resolveRegistrationId(authorizationRequest)
val additionalParameters = LinkedHashMap(authorizationRequest?.additionalParameters)
// set login.gov specific params
// https://developers.login.gov/oidc/#authorization
if(registrationId == LOGIN_GOV_REGISTRATION_ID) {
additionalParameters["nonce"] = "1234567890" // generate your nonce here (should actually include per-session state and be unguessable)
// add other custom params...
}
return OAuth2AuthorizationRequest
.from(authorizationRequest)
.additionalParameters(additionalParameters)
.build()
}
private fun resolveRegistrationId(authorizationRequest: OAuth2AuthorizationRequest?): String {
return authorizationRequest!!.additionalParameters[OAuth2ParameterNames.REGISTRATION_ID] as String
}
}

How can I convert an Object to Json in a Rabbit reply?

I have two applications communicating with each other using rabbit.
I need to send (from app1) an object to a listener (in app2) and after some process (on listener) it answer me with another object, now I am receiving this error:
ClassNotFound
I am using this config for rabbit in both applications:
#Configuration
public class RabbitConfiguration {
public final static String EXCHANGE_NAME = "paymentExchange";
public final static String EVENT_ROUTING_KEY = "eventRoute";
public final static String PAYEMNT_ROUTING_KEY = "paymentRoute";
public final static String QUEUE_EVENT = EXCHANGE_NAME + "." + "event";
public final static String QUEUE_PAYMENT = EXCHANGE_NAME + "." + "payment";
public final static String QUEUE_CAPTURE = EXCHANGE_NAME + "." + "capture";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_EVENT, QUEUE_PAYMENT);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public DirectExchange exchange() {
return new DirectExchange(EXCHANGE_NAME);
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
r.setExchange(EXCHANGE_NAME);
r.setChannelTransacted(false);
r.setConnectionFactory(rabbitConnectionFactory);
r.setMessageConverter(jsonMessageConverter());
return r;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
private List<Declarable> queues(String... nomes) {
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < nomes.length; i++) {
result.add(newQueue(nomes[i]));
if (nomes[i].equals(QUEUE_EVENT))
result.add(makeBindingToQueue(nomes[i], EVENT_ROUTING_KEY));
else
result.add(makeBindingToQueue(nomes[i], PAYEMNT_ROUTING_KEY));
}
return result;
}
private static Binding makeBindingToQueue(String queueName, String route) {
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, route, null);
}
private static Queue newQueue(String nome) {
return new Queue(nome);
}
}
I send the message using this:
String response = (String) rabbitTemplate.convertSendAndReceive(RabbitConfiguration.EXCHANGE_NAME,
RabbitConfiguration.PAYEMNT_ROUTING_KEY, domainEvent);
And await for a response using a cast to the object.
This communication is between two different applications using the same rabbit server.
How can I solve this?
I expected rabbit convert the message to a json in the send operation and the same in the reply, so I've created the object to correspond to a json of reply.
Show, please, the configuration for the listener. You should be sure that ListenerContainer there is supplied with the Jackson2JsonMessageConverter as well to carry __TypeId__ header back with the reply.
Also see Spring AMQP JSON sample for some help.

How to set custom time out for remember me cookie in grails spring security?

I have tokenValiditySeconds set in the Config.groovy as
grails.plugins.springsecurity.rememberMe.tokenValiditySeconds=31*24*60*60
However I want to set a different validity for all requests that comes from, say a sub-domain. I can identify domain info from the request object, but I am not able to override the tokenValiditySeconds from the CustomRememberMeService class.
By default the tokens will be valid for 14 days from the last
successful authentication attempt. This can be changed using
AbstractRememberMeServices.setTokenValiditySeconds(int). If this value
is less than zero, the expiryTime will remain at 14 days, but the
negative value will be used for the maxAge property of the cookie,
meaning that it will not be stored when the browser is closed.
As per the documentation, I should be able to change the validity by using setTokenValiditySeconds(int) method but it does not have any effect.
So how to override the value set in the config file?
Thanks.
Edit:
class CustomRememberMeService extends TokenBasedRememberMeServices {
def springSecurityService;
public final LoggedInUserDetails customAutoLogin(HttpServletRequest request, HttpServletResponse response) {
def cookies = request.getCookies();
if (!cookies) return null;
String rememberMeCookie = extractRememberMeCookie(request);
for (int i = 0; i < cookies.length; i++) {
Cookie c = cookies[i];
if(c.getName().equals('remember_me') && rememberMeCookie == null) {
rememberMeCookie = c.getValue();
}
}
if (rememberMeCookie == null) return null
logger.debug("rememberMeCookie is : ${rememberMeCookie}");
if (rememberMeCookie.length() == 0) {
cancelCookie(request, response);
return null;
}
String[] cookieTokens = decodeCookie(rememberMeCookie);
String username = cookieTokens[0];
def loginContext = request.getParameter('loginContext')
loginContext = (loginContext == null) ? "mainWeb" : loginContext
setTokenValiditySeconds(60) // not working
LoggedInUserDetails user = getUserDetailsService().loadUserByUsername("${username}#${request.getServerName().trim()}#${loginContext}")
springSecurityService.reauthenticate("${username}#${request.getServerName().trim()}#${loginContext}")
}
}
The resource.groovy file looks like:
//..
customRememberMeService(com.rwi.springsecurity.services.CustomRememberMeService) {
userDetailsService = ref('userDetailsService')
springSecurityService = ref('springSecurityService')
key = "${grailsApplication.config.grails.plugins.springsecurity.rememberMe.key}"
}
customRememberMeServicesFilter(CustomRememberMeServicesFilter){
authenticationManager = ref('authenticationManager')
rememberMeServices = ref('rememberMeServices')
customRememberMeService = ref('customRememberMeService')
}
//..
CustomRemeberMEService.groovy
// ..
class CustomRememberMeServicesFilter extends RememberMeAuthenticationFilter {
def customRememberMeService;
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (SecurityContextHolder.getContext().getAuthentication() == null) {
LoggedInUserDetails rememberMeAuth = customRememberMeService.customAutoLogin(request, response);
}
chain.doFilter(request, response);
}
}
Override the method calculateLoginLifetime, by default this will return the value as set in the configuration (it calls getTokenValiditySeconds(). By overriding this you can determine (based on the request) if the normal timeout should be passed or a custom one.
protected int calculateLoginLifetime(HttpServletRequest request, Authentication authentication) {
if (request.getRemoteAddr().startsWith("subdomain") {
return 15; // Or whatever you want, you could also make it configurable.
}
return getTokenValiditySeconds();
}

Resources