diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java index ead74cbaff..425e17e1f7 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,11 @@ package org.springframework.security.oauth2.core.user; +import java.net.URL; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; @@ -85,13 +87,37 @@ public class OAuth2UserAuthority implements GrantedAuthority { if (!this.getAuthority().equals(that.getAuthority())) { return false; } - return this.getAttributes().equals(that.getAttributes()); + Map thatAttributes = that.getAttributes(); + if (getAttributes().size() != thatAttributes.size()) { + return false; + } + for (Map.Entry e : getAttributes().entrySet()) { + String key = e.getKey(); + Object value = convertURLIfNecessary(e.getValue()); + if (value == null) { + if (!(thatAttributes.get(key) == null && thatAttributes.containsKey(key))) { + return false; + } + } + else { + Object thatValue = convertURLIfNecessary(thatAttributes.get(key)); + if (!value.equals(thatValue)) { + return false; + } + } + } + return true; } @Override public int hashCode() { int result = this.getAuthority().hashCode(); - result = 31 * result + this.getAttributes().hashCode(); + result = 31 * result; + for (Map.Entry e : getAttributes().entrySet()) { + Object key = e.getKey(); + Object value = convertURLIfNecessary(e.getValue()); + result += Objects.hashCode(key) ^ Objects.hashCode(value); + } return result; } @@ -100,4 +126,12 @@ public class OAuth2UserAuthority implements GrantedAuthority { return this.getAuthority(); } + /** + * @return {@code URL} converted to a string since {@code URL} shouldn't be used for + * equality/hashCode. For other instances the value is returned as is. + */ + private static Object convertURLIfNecessary(Object value) { + return (value instanceof URL) ? ((URL) value).toExternalForm() : value; + } + } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthorityTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthorityTests.java index 4b59b143fb..db8555026b 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthorityTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthorityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.core.user; +import java.net.MalformedURLException; +import java.net.URL; import java.util.Collections; import java.util.Map; @@ -35,6 +37,22 @@ public class OAuth2UserAuthorityTests { private static final Map ATTRIBUTES = Collections.singletonMap("username", "test"); + private static final OAuth2UserAuthority AUTHORITY_WITH_OBJECTURL; + + private static final OAuth2UserAuthority AUTHORITY_WITH_STRINGURL; + + static { + try { + AUTHORITY_WITH_OBJECTURL = new OAuth2UserAuthority( + Collections.singletonMap("someurl", new URL("https://localhost"))); + AUTHORITY_WITH_STRINGURL = new OAuth2UserAuthority( + Collections.singletonMap("someurl", "https://localhost")); + } + catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } + @Test public void constructorWhenAuthorityIsNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException().isThrownBy(() -> new OAuth2UserAuthority(null, ATTRIBUTES)); @@ -58,4 +76,22 @@ public class OAuth2UserAuthorityTests { assertThat(userAuthority.getAttributes()).isEqualTo(ATTRIBUTES); } + @Test + public void equalsRegardlessOfUrlType() { + assertThat(AUTHORITY_WITH_OBJECTURL).isEqualTo(AUTHORITY_WITH_OBJECTURL); + assertThat(AUTHORITY_WITH_STRINGURL).isEqualTo(AUTHORITY_WITH_STRINGURL); + + assertThat(AUTHORITY_WITH_OBJECTURL).isEqualTo(AUTHORITY_WITH_STRINGURL); + assertThat(AUTHORITY_WITH_STRINGURL).isEqualTo(AUTHORITY_WITH_OBJECTURL); + } + + @Test + public void hashCodeIsSameRegardlessOfUrlType() { + assertThat(AUTHORITY_WITH_OBJECTURL.hashCode()).isEqualTo(AUTHORITY_WITH_OBJECTURL.hashCode()); + assertThat(AUTHORITY_WITH_STRINGURL.hashCode()).isEqualTo(AUTHORITY_WITH_STRINGURL.hashCode()); + + assertThat(AUTHORITY_WITH_OBJECTURL.hashCode()).isEqualTo(AUTHORITY_WITH_STRINGURL.hashCode()); + assertThat(AUTHORITY_WITH_STRINGURL.hashCode()).isEqualTo(AUTHORITY_WITH_OBJECTURL.hashCode()); + } + }