From a831ec9eed20a3fdc56dd8802146db3f4e0e6703 Mon Sep 17 00:00:00 2001 From: gzhao9 <74684732+gzhao9@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:43:22 +0800 Subject: [PATCH 01/68] Merged changes from Example 1: Avoid duplicate creation of mocks in ActiveDirectoryLdapAuthenticationProviderTests.java --- ...ectoryLdapAuthenticationProviderTests.java | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java index 4668d371745..ced26c10862 100644 --- a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java +++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java @@ -69,10 +69,11 @@ public class ActiveDirectoryLdapAuthenticationProviderTests { ActiveDirectoryLdapAuthenticationProvider provider; UsernamePasswordAuthenticationToken joe = UsernamePasswordAuthenticationToken.unauthenticated("joe", "password"); - + DirContext ctx; @BeforeEach public void setUp() { this.provider = new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/"); + ctx = mock(DirContext.class); } @Test @@ -90,8 +91,6 @@ public void successfulAuthenticationProducesExpectedAuthorities() throws Excepti @Test public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Exception { String customSearchFilter = "(&(objectClass=user)(sAMAccountName={0}))"; - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); DirContextAdapter dca = new DirContextAdapter(); SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes()); given(ctx.search(any(Name.class), eq(customSearchFilter), any(Object[].class), any(SearchControls.class))) @@ -107,8 +106,6 @@ public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Excepti @Test public void defaultSearchFilter() throws Exception { final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))"; - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); DirContextAdapter dca = new DirContextAdapter(); SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes()); given(ctx.search(any(Name.class), eq(defaultSearchFilter), any(Object[].class), any(SearchControls.class))) @@ -126,8 +123,6 @@ public void defaultSearchFilter() throws Exception { public void bindPrincipalAndUsernameUsed() throws Exception { final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))"; ArgumentCaptor captor = ArgumentCaptor.forClass(Object[].class); - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); DirContextAdapter dca = new DirContextAdapter(); SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes()); given(ctx.search(any(Name.class), eq(defaultSearchFilter), captor.capture(), any(SearchControls.class))) @@ -153,8 +148,6 @@ public void setSearchFilterEmpty() { @Test public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws Exception { this.provider = new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://192.168.1.200/"); - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); DirContextAdapter dca = new DirContextAdapter(); SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes()); given(ctx.search(eq(LdapNameBuilder.newInstance("DC=mydomain,DC=eu").build()), any(String.class), @@ -167,8 +160,6 @@ public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws @Test public void failedUserSearchCausesBadCredentials() throws Exception { - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); given(ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class))) .willThrow(new NameNotFoundException()); this.provider.contextFactory = createContextFactoryReturning(ctx); @@ -178,8 +169,6 @@ public void failedUserSearchCausesBadCredentials() throws Exception { // SEC-2017 @Test public void noUserSearchCausesUsernameNotFound() throws Exception { - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); given(ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class))) .willReturn(new EmptyEnumeration<>()); this.provider.contextFactory = createContextFactoryReturning(ctx); @@ -196,8 +185,6 @@ public void sec2500PreventAnonymousBind() { @Test @SuppressWarnings("unchecked") public void duplicateUserSearchCausesError() throws Exception { - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); NamingEnumeration searchResults = mock(NamingEnumeration.class); given(searchResults.hasMore()).willReturn(true, true, false); SearchResult searchResult = mock(SearchResult.class); @@ -209,7 +196,6 @@ public void duplicateUserSearchCausesError() throws Exception { assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class) .isThrownBy(() -> this.provider.authenticate(this.joe)); } - static final String msg = "[LDAP: error code 49 - 80858585: LdapErr: DSID-DECAFF0, comment: AcceptSecurityContext error, data "; @Test @@ -357,8 +343,6 @@ DirContext createContext(Hashtable env) { private void checkAuthentication(String rootDn, ActiveDirectoryLdapAuthenticationProvider provider) throws NamingException { - DirContext ctx = mock(DirContext.class); - given(ctx.getNameInNamespace()).willReturn(""); DirContextAdapter dca = new DirContextAdapter(); SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes()); @SuppressWarnings("deprecation") From ee231a5fa673d8cab6195c6e4219c7076584e88f Mon Sep 17 00:00:00 2001 From: gzhao9 <74684732+gzhao9@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:46:50 +0800 Subject: [PATCH 02/68] Merged changes from Example 2: Avoid duplicate creation of mocks in org.springframework.security.authorization.method --- ...izationManagerAfterMethodInterceptorTests.java | 6 ++---- ...zationManagerBeforeMethodInterceptorTests.java | 7 +++---- .../method/MockSecurityContextHolderStrategy.java | 15 +++++++++++++++ ...FilterAuthorizationMethodInterceptorTests.java | 8 ++++---- ...FilterAuthorizationMethodInterceptorTests.java | 6 ++---- 5 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java index e34b45a33cb..00c70003bcc 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java @@ -82,9 +82,8 @@ public void beforeWhenMockAuthorizationManagerThenCheckAndReturnedObject() throw @Test public void afterWhenMockSecurityContextHolderStrategyThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); Authentication authentication = TestAuthentication.authenticatedUser(); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); MethodInvocation invocation = mock(MethodInvocation.class); AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager .authenticated(); @@ -98,10 +97,9 @@ public void afterWhenMockSecurityContextHolderStrategyThenUses() throws Throwabl // gh-12877 @Test public void afterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); Authentication authentication = new TestingAuthenticationToken("john", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); MethodInvocation invocation = mock(MethodInvocation.class); AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager .authenticated(); diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java index 8022609ac7b..a39b1853980 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java @@ -77,10 +77,9 @@ public void beforeWhenMockAuthorizationManagerThenCheck() throws Throwable { @Test public void beforeWhenMockSecurityContextHolderStrategyThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); Authentication authentication = new TestingAuthenticationToken("user", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); MethodInvocation invocation = mock(MethodInvocation.class); AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager.authenticated(); AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor( @@ -93,10 +92,10 @@ public void beforeWhenMockSecurityContextHolderStrategyThenUses() throws Throwab // gh-12877 @Test public void beforeWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + Authentication authentication = new TestingAuthenticationToken("john", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); MethodInvocation invocation = mock(MethodInvocation.class); AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager.authenticated(); AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor( diff --git a/core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java b/core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java new file mode 100644 index 00000000000..eec416925e3 --- /dev/null +++ b/core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java @@ -0,0 +1,15 @@ +package org.springframework.security.authorization.method; + +import org.springframework.security.core.context.*; + +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.*; + +public class MockSecurityContextHolderStrategy { + static SecurityContextHolderStrategy getmock(SecurityContextImpl securityContextImpl){ + + SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + given(strategy.getContext()).willReturn(securityContextImpl); + return strategy; + } +} diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java index 48eec006186..8428ab1ec59 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java @@ -129,10 +129,10 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE @Test public void postFilterWhenMockSecurityContextHolderStrategyThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + Authentication authentication = new TestingAuthenticationToken("john", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); String[] array = { "john", "bob" }; MockMethodInvocation invocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingArrayAuthentication", new Class[] { String[].class }, new Object[] { array }) { @@ -150,10 +150,10 @@ public Object proceed() { // gh-12877 @Test public void postFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); + Authentication authentication = new TestingAuthenticationToken("john", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); String[] array = { "john", "bob" }; MockMethodInvocation invocation = new MockMethodInvocation(new TestClass(), TestClass.class, "doSomethingArrayAuthentication", new Class[] { String[].class }, new Object[] { array }) { diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java index 30d40a369fe..e4d3e74d433 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java @@ -189,10 +189,9 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE @Test public void preFilterWhenMockSecurityContextHolderStrategyThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); Authentication authentication = new TestingAuthenticationToken("john", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); List list = new ArrayList<>(); list.add("john"); list.add("bob"); @@ -207,10 +206,9 @@ public void preFilterWhenMockSecurityContextHolderStrategyThenUses() throws Thro // gh-12877 @Test public void preFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable { - SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class); Authentication authentication = new TestingAuthenticationToken("john", "password", AuthorityUtils.createAuthorityList("authority")); - given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication)); + SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication)); List list = new ArrayList<>(); list.add("john"); list.add("bob"); From 33df5b060e12dabf896e6459c9fccc439864951c Mon Sep 17 00:00:00 2001 From: gzhao9 <74684732+gzhao9@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:48:01 +0800 Subject: [PATCH 03/68] Merged changes from Example 3: Avoid duplicate creation of mocks in ConcurrentSessionFilterTests.java --- .../ConcurrentSessionFilterTests.java | 51 +++++-------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java index 32e6702fde7..1f72e245fb3 100644 --- a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java @@ -164,13 +164,8 @@ public void doFilterWhenNoSessionThenChainIsContinued() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletResponse response = new MockHttpServletResponse(); RedirectStrategy redirect = mock(RedirectStrategy.class); - SessionRegistry registry = mock(SessionRegistry.class); - SessionInformation information = new SessionInformation("user", "sessionId", - new Date(System.currentTimeMillis() - 1000)); - information.expireNow(); - given(registry.getSessionInformation(anyString())).willReturn(information); String expiredUrl = "/expired"; - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredUrl); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry(), expiredUrl); filter.setRedirectStrategy(redirect); MockFilterChain chain = new MockFilterChain(); filter.doFilter(request, response, chain); @@ -199,13 +194,8 @@ public void doFilterWhenCustomRedirectStrategyThenCustomRedirectStrategyUsed() t request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); RedirectStrategy redirect = mock(RedirectStrategy.class); - SessionRegistry registry = mock(SessionRegistry.class); - SessionInformation information = new SessionInformation("user", "sessionId", - new Date(System.currentTimeMillis() - 1000)); - information.expireNow(); - given(registry.getSessionInformation(anyString())).willReturn(information); String expiredUrl = "/expired"; - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredUrl); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry(), expiredUrl); filter.setRedirectStrategy(redirect); filter.doFilter(request, response, new MockFilterChain()); verify(redirect).sendRedirect(request, response, expiredUrl); @@ -218,13 +208,8 @@ public void doFilterWhenOverrideThenCustomRedirectStrategyUsed() throws Exceptio request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); RedirectStrategy redirect = mock(RedirectStrategy.class); - SessionRegistry registry = mock(SessionRegistry.class); - SessionInformation information = new SessionInformation("user", "sessionId", - new Date(System.currentTimeMillis() - 1000)); - information.expireNow(); - given(registry.getSessionInformation(anyString())).willReturn(information); final String expiredUrl = "/expired"; - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredUrl + "will-be-overrridden") { + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry(), expiredUrl + "will-be-overrridden") { @Override protected String determineExpiredUrl(HttpServletRequest request, SessionInformation info) { return expiredUrl; @@ -241,12 +226,7 @@ public void doFilterWhenNoExpiredUrlThenResponseWritten() throws Exception { MockHttpSession session = new MockHttpSession(); request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - SessionRegistry registry = mock(SessionRegistry.class); - SessionInformation information = new SessionInformation("user", "sessionId", - new Date(System.currentTimeMillis() - 1000)); - information.expireNow(); - given(registry.getSessionInformation(anyString())).willReturn(information); - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry()); filter.doFilter(request, response, new MockFilterChain()); assertThat(response.getContentAsString()).contains( "This session has been expired (possibly due to multiple concurrent logins being attempted as the same user)."); @@ -259,12 +239,7 @@ public void doFilterWhenCustomLogoutHandlersThenHandlersUsed() throws Exception MockHttpSession session = new MockHttpSession(); request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - SessionRegistry registry = mock(SessionRegistry.class); - SessionInformation information = new SessionInformation("user", "sessionId", - new Date(System.currentTimeMillis() - 1000)); - information.expireNow(); - given(registry.getSessionInformation(anyString())).willReturn(information); - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry()); filter.setLogoutHandlers(new LogoutHandler[] { handler }); filter.doFilter(request, response, new MockFilterChain()); verify(handler).logout(eq(request), eq(response), any()); @@ -276,12 +251,7 @@ public void doFilterWhenCustomSecurityContextHolderStrategyThenHandlersUsed() th MockHttpSession session = new MockHttpSession(); request.setSession(session); MockHttpServletResponse response = new MockHttpServletResponse(); - SessionRegistry registry = mock(SessionRegistry.class); - SessionInformation information = new SessionInformation("user", "sessionId", - new Date(System.currentTimeMillis() - 1000)); - information.expireNow(); - given(registry.getSessionInformation(anyString())).willReturn(information); - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry()); SecurityContextHolderStrategy securityContextHolderStrategy = spy( new MockSecurityContextHolderStrategy(new TestingAuthenticationToken("user", "password"))); filter.setSecurityContextHolderStrategy(securityContextHolderStrategy); @@ -300,5 +270,12 @@ public void setLogoutHandlersWhenEmptyThenThrowsException() { ConcurrentSessionFilter filter = new ConcurrentSessionFilter(new SessionRegistryImpl()); assertThatIllegalArgumentException().isThrownBy(() -> filter.setLogoutHandlers(new LogoutHandler[0])); } - + private SessionRegistry mockSessionRegistry(){ + SessionRegistry registry = mock(SessionRegistry.class); + SessionInformation information = new SessionInformation("user", "sessionId", + new Date(System.currentTimeMillis() - 1000)); + information.expireNow(); + given(registry.getSessionInformation(anyString())).willReturn(information); + return registry; + } } From 17e09f9a297f8b7a5cefe96886efd361977dd3fa Mon Sep 17 00:00:00 2001 From: gzhao9 <74684732+gzhao9@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:48:48 +0800 Subject: [PATCH 04/68] Merged changes from Example 4: Avoid duplicate creation of mocks in ExceptionTranslationFilterTests.java --- .../ExceptionTranslationFilterTests.java | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java index 2dc36881cb7..753adb789dd 100644 --- a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java @@ -91,9 +91,7 @@ public void testAccessDeniedWhenAnonymous() throws Exception { request.setContextPath("/mycontext"); request.setRequestURI("/mycontext/secure/page.html"); // Setup the FilterChain to thrown an access denied exception - FilterChain fc = mock(FilterChain.class); - willThrow(new AccessDeniedException("")).given(fc) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(new AccessDeniedException("")); // Setup SecurityContextHolder, as filter needs to check if user is // anonymous SecurityContextHolder.getContext() @@ -119,9 +117,7 @@ public void testAccessDeniedWithRememberMe() throws Exception { request.setContextPath("/mycontext"); request.setRequestURI("/mycontext/secure/page.html"); // Setup the FilterChain to thrown an access denied exception - FilterChain fc = mock(FilterChain.class); - willThrow(new AccessDeniedException("")).given(fc) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(new AccessDeniedException("")); // Setup SecurityContextHolder, as filter needs to check if user is remembered SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication( @@ -142,9 +138,7 @@ public void testAccessDeniedWhenNonAnonymous() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setServletPath("/secure/page.html"); // Setup the FilterChain to thrown an access denied exception - FilterChain fc = mock(FilterChain.class); - willThrow(new AccessDeniedException("")).given(fc) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(new AccessDeniedException("")); // Setup SecurityContextHolder, as filter needs to check if user is // anonymous SecurityContextHolder.clearContext(); @@ -167,9 +161,7 @@ public void testLocalizedErrorMessages() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); request.setServletPath("/secure/page.html"); // Setup the FilterChain to thrown an access denied exception - FilterChain fc = mock(FilterChain.class); - willThrow(new AccessDeniedException("")).given(fc) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(new AccessDeniedException("")); // Setup SecurityContextHolder, as filter needs to check if user is // anonymous SecurityContextHolder.getContext() @@ -198,9 +190,7 @@ public void redirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthentication request.setContextPath("/mycontext"); request.setRequestURI("/mycontext/secure/page.html"); // Setup the FilterChain to thrown an authentication failure exception - FilterChain fc = mock(FilterChain.class); - willThrow(new BadCredentialsException("")).given(fc) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(new BadCredentialsException("")); // Test RequestCache requestCache = new HttpSessionRequestCache(); ExceptionTranslationFilter filter = new ExceptionTranslationFilter(this.mockEntryPoint, requestCache); @@ -223,9 +213,7 @@ public void redirectedToLoginFormAndSessionShowsOriginalTargetWithExoticPortWhen request.setContextPath("/mycontext"); request.setRequestURI("/mycontext/secure/page.html"); // Setup the FilterChain to thrown an authentication failure exception - FilterChain fc = mock(FilterChain.class); - willThrow(new BadCredentialsException("")).given(fc) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(new BadCredentialsException("")); // Test HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); ExceptionTranslationFilter filter = new ExceptionTranslationFilter(this.mockEntryPoint, requestCache); @@ -265,8 +253,7 @@ public void thrownIOExceptionServletExceptionAndRuntimeExceptionsAreRethrown() t filter.afterPropertiesSet(); Exception[] exceptions = { new IOException(), new ServletException(), new RuntimeException() }; for (Exception exception : exceptions) { - FilterChain fc = mock(FilterChain.class); - willThrow(exception).given(fc).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + FilterChain fc = mockFilterChainWiehException(exception); assertThatExceptionOfType(Exception.class) .isThrownBy(() -> filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), fc)) .isSameAs(exception); @@ -304,7 +291,11 @@ public void setMessageSourceWhenNotNullThenCanGet() { filter.messages.getMessage(code); verify(source).getMessage(eq(code), any(), any()); } - + private FilterChain mockFilterChainWiehException(Object exception) throws ServletException, IOException { + FilterChain fc = mock(FilterChain.class); + willThrow((Throwable) exception).given(fc).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + return fc; + } private AuthenticationEntryPoint mockEntryPoint = (request, response, authException) -> response .sendRedirect(request.getContextPath() + "/login.jsp"); From e16ce57fbb30eb0b10d04706d6f8730ee72057e4 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Wed, 26 Jun 2024 14:03:44 -0300 Subject: [PATCH 05/68] Use AuthenticationFailureHandler instead of @ControllerAdvice Closes gh-15305 --- .../authentication/password-storage.adoc | 85 ++++++++++--------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc index c10967b7c44..2bb4006f502 100644 --- a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc +++ b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc @@ -602,7 +602,11 @@ To facilitate that, Spring Security provides integration with the https://haveib You can either use the `CompromisedPasswordChecker` API by yourself or, if you are using xref:servlet/authentication/passwords/dao-authentication-provider.adoc[the `DaoAuthenticationProvider]` via xref:servlet/authentication/passwords/index.adoc[Spring Security authentication mechanisms], you can provide a `CompromisedPasswordChecker` bean, and it will be automatically picked up by Spring Security configuration. -.Using CompromisedPasswordChecker as a bean +By doing that, when you try to authenticate via Form Login using a weak password, let's say `123456`, you will receive a 401 or be redirected to the `/login?error` page (depending on your user-agent). +However, just a 401 or the redirect is not so useful in that case, it will cause some confusion because the user provided the right password and still was not allowed to log in. +In such cases, you can handle the `CompromisedPasswordException` via the `AuthenticationFailureHandler` to perform your desired logic, like redirecting the user-agent to `/reset-password`, for example: + +.Using CompromisedPasswordChecker [tabs] ====== Java:: @@ -615,8 +619,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) - .formLogin(withDefaults()) - .httpBasic(withDefaults()); + .formLogin((login) -> login + .failureHandler(new CompromisedPasswordAuthenticationFailureHandler()) + ); return http.build(); } @@ -624,6 +629,25 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public CompromisedPasswordChecker compromisedPasswordChecker() { return new HaveIBeenPwnedRestApiPasswordChecker(); } + +static class CompromisedPasswordAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private final SimpleUrlAuthenticationFailureHandler defaultFailureHandler = new SimpleUrlAuthenticationFailureHandler( + "/login?error"); + + private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + if (exception instanceof CompromisedPasswordException) { + this.redirectStrategy.sendRedirect(request, response, "/reset-password"); + return; + } + this.defaultFailureHandler.onAuthenticationFailure(request, response, exception); + } + +} ---- Kotlin:: @@ -636,8 +660,9 @@ open fun filterChain(http:HttpSecurity): SecurityFilterChain { authorizeHttpRequests { authorize(anyRequest, authenticated) } - formLogin {} - httpBasic {} + formLogin { + failureHandler = CompromisedPasswordAuthenticationFailureHandler() + } } return http.build() } @@ -646,44 +671,22 @@ open fun filterChain(http:HttpSecurity): SecurityFilterChain { open fun compromisedPasswordChecker(): CompromisedPasswordChecker { return HaveIBeenPwnedRestApiPasswordChecker() } ----- -====== - -By doing that, when you try to authenticate via HTTP Basic or Form Login using a weak password, let's say `123456`, you will receive a 401 response status code. -However, just a 401 is not so useful in that case, it will cause some confusion because the user provided the right password and still was not allowed to log in. -In such cases, you can handle the `CompromisedPasswordException` to perform your desired logic, like redirecting the user-agent to `/reset-password`, for example: - -[tabs] -====== -Java:: -+ -[source,java,role="primary"] ----- -@ControllerAdvice -public class MyControllerAdvice { - - @ExceptionHandler(CompromisedPasswordException.class) - public String handleCompromisedPasswordException(CompromisedPasswordException ex, RedirectAttributes attributes) { - attributes.addFlashAttribute("error", ex.message); - return "redirect:/reset-password"; - } - -} ----- - -Kotlin:: -+ -[source,kotlin,role="secondary"] ----- -@ControllerAdvice -class MyControllerAdvice { - @ExceptionHandler(CompromisedPasswordException::class) - fun handleCompromisedPasswordException(ex: CompromisedPasswordException, attributes: RedirectAttributes): RedirectView { - attributes.addFlashAttribute("error", ex.message) - return RedirectView("/reset-password") +class CompromisedPasswordAuthenticationFailureHandler : AuthenticationFailureHandler { + private val defaultFailureHandler = SimpleUrlAuthenticationFailureHandler("/login?error") + private val redirectStrategy = DefaultRedirectStrategy() + + override fun onAuthenticationFailure( + request: HttpServletRequest, + response: HttpServletResponse, + exception: AuthenticationException + ) { + if (exception is CompromisedPasswordException) { + redirectStrategy.sendRedirect(request, response, "/reset-password") + return + } + defaultFailureHandler.onAuthenticationFailure(request, response, exception) } - } ---- ====== From e3d642ce7cc47de1fb1b45ec524064f51421657a Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Wed, 26 Jun 2024 14:04:17 -0300 Subject: [PATCH 06/68] Add Task to Verify Branch Version Matches the Project Version Closes gh-15226 --- build.gradle | 1 + buildSrc/build.gradle | 4 + .../CheckExpectedBranchVersionPlugin.java | 82 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java diff --git a/build.gradle b/build.gradle index 6cc59e0122c..785e175dbd4 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,7 @@ apply plugin: 'org.springframework.github.milestone' apply plugin: 'org.springframework.github.changelog' apply plugin: 'org.springframework.github.release' apply plugin: 'org.springframework.security.versions.verify-dependencies-versions' +apply plugin: 'org.springframework.security.check-expected-branch-version' group = 'org.springframework.security' description = 'Spring Security' diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 5a17397667e..4994bd96ac4 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -68,6 +68,10 @@ gradlePlugin { id = "org.springframework.security.versions.verify-dependencies-versions" implementationClass = "org.springframework.security.convention.versions.VerifyDependenciesVersionsPlugin" } + checkExpectedBranchVersion { + id = "org.springframework.security.check-expected-branch-version" + implementationClass = "org.springframework.security.CheckExpectedBranchVersionPlugin" + } } } diff --git a/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java b/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java new file mode 100644 index 00000000000..40b943856cc --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; + +/** + * @author Marcus da Coregio + */ +public class CheckExpectedBranchVersionPlugin implements Plugin { + + @Override + public void apply(Project project) { + TaskProvider checkExpectedBranchVersionTask = project.getTasks().register("checkExpectedBranchVersion", CheckExpectedBranchVersionTask.class, (task) -> { + task.setGroup("Build"); + task.setDescription("Check if the project version matches the branch version"); + }); + project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(checkExpectedBranchVersionTask)); + } + + public static class CheckExpectedBranchVersionTask extends DefaultTask { + + @TaskAction + public void run() throws IOException { + Project project = getProject(); + String version = (String) project.getVersion(); + String branchVersion = getBranchVersion(project); + if (!branchVersion.matches("^[0-9]+\\.[0-9]+\\.x$")) { + System.out.println("Branch version does not match *.x, ignoring"); + return; + } + if (!versionsMatch(version, branchVersion)) { + throw new IllegalStateException(String.format("Project version [%s] does not match branch version [%s]. " + + "Please verify that the branch contains the right version.", version, branchVersion)); + } + } + + private static String getBranchVersion(Project project) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + project.exec((exec) -> { + exec.commandLine("git", "symbolic-ref", "--short", "HEAD"); + exec.setErrorOutput(System.err); + exec.setStandardOutput(baos); + }); + return baos.toString(); + } + } + + private boolean versionsMatch(String projectVersion, String branchVersion) { + String[] projectVersionParts = projectVersion.split("\\."); + String[] branchVersionParts = branchVersion.split("\\."); + if (projectVersionParts.length < 2 || branchVersionParts.length < 2) { + return false; + } + return projectVersionParts[0].equals(branchVersionParts[0]) && projectVersionParts[1].equals(branchVersionParts[1]); + } + + } + +} From 1135ad5a5a17537bc15acc13fc94c31aa4f797de Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Fri, 28 Jun 2024 09:31:48 -0300 Subject: [PATCH 07/68] Skip checkExpectedBranchVersion task on PR Build workflow Issue gh-15226 --- .github/workflows/pr-build-workflow.yml | 2 +- .../security/CheckExpectedBranchVersionPlugin.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-build-workflow.yml b/.github/workflows/pr-build-workflow.yml index daa66c9b17a..c6f0f9ba78b 100644 --- a/.github/workflows/pr-build-workflow.yml +++ b/.github/workflows/pr-build-workflow.yml @@ -21,7 +21,7 @@ jobs: java-version: '11' distribution: 'adopt' - name: Build with Gradle - run: ./gradlew clean build --continue + run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue generate-docs: name: Generate Docs runs-on: ubuntu-latest diff --git a/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java b/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java index 40b943856cc..9266f7c4d5b 100644 --- a/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java +++ b/buildSrc/src/main/java/org/springframework/security/CheckExpectedBranchVersionPlugin.java @@ -45,6 +45,9 @@ public static class CheckExpectedBranchVersionTask extends DefaultTask { @TaskAction public void run() throws IOException { Project project = getProject(); + if (project.hasProperty("skipCheckExpectedBranchVersion")) { + return; + } String version = (String) project.getVersion(); String branchVersion = getBranchVersion(project); if (!branchVersion.matches("^[0-9]+\\.[0-9]+\\.x$")) { From ef85ed5460ab823f132680541b5faf066802bc7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:33:58 +0000 Subject: [PATCH 08/68] Bump org.junit:junit-bom from 5.10.2 to 5.10.3 Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 85ad7db3f03..db2cfda41a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ org-hsqldb = "org.hsqldb:hsqldb:2.7.3" org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24" org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" } -org-junit-junit-bom = "org.junit:junit-bom:5.10.2" +org-junit-junit-bom = "org.junit:junit-bom:5.10.3" org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" } org-opensaml-opensaml-core = { module = "org.opensaml:opensaml-core", version.ref = "org-opensaml" } org-opensaml-opensaml-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml" } From 71906b306b7d420a8ad45a72758792b9c4e60d47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:33:45 +0000 Subject: [PATCH 09/68] Bump org.junit:junit-bom from 5.10.2 to 5.10.3 Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d734d88579..c24606865b0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ org-hsqldb = "org.hsqldb:hsqldb:2.7.3" org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24" org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" } -org-junit-junit-bom = "org.junit:junit-bom:5.10.2" +org-junit-junit-bom = "org.junit:junit-bom:5.10.3" org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" } org-opensaml-opensaml-core = { module = "org.opensaml:opensaml-core", version.ref = "org-opensaml" } org-opensaml-opensaml-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml" } From 694bc038d2a7e3b30c69800dd1a669c80e42a8c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:33:54 +0000 Subject: [PATCH 10/68] Bump org.junit:junit-bom from 5.10.2 to 5.10.3 Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec10a508b99..889c145898f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -73,7 +73,7 @@ org-hsqldb = "org.hsqldb:hsqldb:2.7.3" org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" } org-jetbrains-kotlin-kotlin-gradle-plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24" org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" } -org-junit-junit-bom = "org.junit:junit-bom:5.10.2" +org-junit-junit-bom = "org.junit:junit-bom:5.10.3" org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" } org-opensaml-opensaml-core = { module = "org.opensaml:opensaml-core", version.ref = "org-opensaml" } org-opensaml-opensaml-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml" } From 779030b6cdb6fa286578f01e24996ce24414b2fd Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Fri, 28 Jun 2024 15:33:34 -0300 Subject: [PATCH 11/68] Document the role of CredentialsContainer Closes gh-15319 --- docs/modules/ROOT/nav.adoc | 1 + .../passwords/credentials-container.adoc | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index a602b320ee9..86eea6652d8 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -44,6 +44,7 @@ ***** xref:servlet/authentication/passwords/in-memory.adoc[In Memory] ***** xref:servlet/authentication/passwords/jdbc.adoc[JDBC] ***** xref:servlet/authentication/passwords/user-details.adoc[UserDetails] +***** xref:servlet/authentication/passwords/credentials-container.adoc[CredentialsContainer] ***** xref:servlet/authentication/passwords/user-details-service.adoc[UserDetailsService] ***** xref:servlet/authentication/passwords/password-encoder.adoc[PasswordEncoder] ***** xref:servlet/authentication/passwords/dao-authentication-provider.adoc[DaoAuthenticationProvider] diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc new file mode 100644 index 00000000000..b35795edc01 --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc @@ -0,0 +1,12 @@ +[[servlet-authentication-credentialscontainer]] += CredentialsContainer + +{security-api-url}org/springframework/security/core/CredentialsContainer.html[The `CredentialsContainer`] interface indicates that the implementing object contains sensitive data, and is used internally by Spring Security to erase the authentication credentials after a successful authentication. +This interface is implemented by most of Spring Security internal domain classes, like {security-api-url}org/springframework/security/core/userdetails/User.html[User] and {security-api-url}org/springframework/security/authentication/UsernamePasswordAuthenticationToken.html[UsernamePasswordAuthenticationToken]. + +The `ProviderManager` manager checks whether the returned `Authentication` implements this interface. +If so, xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[it calls the `eraseCredentials` method] to remove the credentials from the object. + +If you want your custom authentication objects to have their credentials erased after authentication, you should ensure that the classes implement the `CredentialsContainer` interface. + +Users who are writing their own `AuthenticationProvider` implementations should create and return an appropriate `Authentication` object there, minus any sensitive data, rather than using this interface. From 260411acacf342fa1fa065ba21bda42291e34f8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:46:40 +0000 Subject: [PATCH 12/68] Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.5 in /docs Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.5. - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.5) --- updated-dependencies: - dependency-name: antora dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index d4fa935fa78..0e1b0a600e8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "antora": "3.2.0-alpha.4", + "antora": "3.2.0-alpha.5", "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.0-alpha.4", "@asciidoctor/tabs": "1.0.0-beta.6", From e646f10b87d4dd378b6b23ac66a5c1fa98b7d851 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:53:57 +0000 Subject: [PATCH 13/68] Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.5 in /docs Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.5. - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.5) --- updated-dependencies: - dependency-name: antora dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index d4fa935fa78..0e1b0a600e8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "antora": "3.2.0-alpha.4", + "antora": "3.2.0-alpha.5", "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.0-alpha.4", "@asciidoctor/tabs": "1.0.0-beta.6", From d16f2655c97dc2988068898b13e9811cfe332e04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:12:14 +0000 Subject: [PATCH 14/68] Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.5 in /docs Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.5. - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.5) --- updated-dependencies: - dependency-name: antora dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index d4fa935fa78..0e1b0a600e8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "antora": "3.2.0-alpha.4", + "antora": "3.2.0-alpha.5", "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.0-alpha.4", "@asciidoctor/tabs": "1.0.0-beta.6", From 97e6ac76afd0a0384159d32a160d193ad4b59bc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 01:04:33 +0000 Subject: [PATCH 15/68] Bump antora from 3.2.0-alpha.4 to 3.2.0-alpha.5 in /docs Bumps [antora](https://gitlab.com/antora/antora) from 3.2.0-alpha.4 to 3.2.0-alpha.5. - [Changelog](https://gitlab.com/antora/antora/blob/main/CHANGELOG.adoc) - [Commits](https://gitlab.com/antora/antora/compare/v3.2.0-alpha.4...v3.2.0-alpha.5) --- updated-dependencies: - dependency-name: antora dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/package.json b/docs/package.json index d4fa935fa78..0e1b0a600e8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "antora": "3.2.0-alpha.4", + "antora": "3.2.0-alpha.5", "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.0-alpha.4", "@asciidoctor/tabs": "1.0.0-beta.6", From f564385004594802f06a242dc2ba392107063fbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 03:35:02 +0000 Subject: [PATCH 16/68] Bump org.skyscreamer:jsonassert from 1.5.1 to 1.5.3 Bumps [org.skyscreamer:jsonassert](https://github.com/skyscreamer/JSONassert) from 1.5.1 to 1.5.3. - [Release notes](https://github.com/skyscreamer/JSONassert/releases) - [Changelog](https://github.com/skyscreamer/JSONassert/blob/master/CHANGELOG.md) - [Commits](https://github.com/skyscreamer/JSONassert/compare/jsonassert-1.5.1...jsonassert-1.5.3) --- updated-dependencies: - dependency-name: org.skyscreamer:jsonassert dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db2cfda41a9..1bf557c338e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -81,7 +81,7 @@ org-python-jython = { module = "org.python:jython", version = "2.5.3" } org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit-driver:2.70.0" org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3.141.59" org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59" -org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1" +org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.13" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2023.1.7" From d159a2e8fe9dee94a0b1142a624d2c99b0dac626 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 03:42:14 +0000 Subject: [PATCH 17/68] Bump org.skyscreamer:jsonassert from 1.5.1 to 1.5.3 Bumps [org.skyscreamer:jsonassert](https://github.com/skyscreamer/JSONassert) from 1.5.1 to 1.5.3. - [Release notes](https://github.com/skyscreamer/JSONassert/releases) - [Changelog](https://github.com/skyscreamer/JSONassert/blob/master/CHANGELOG.md) - [Commits](https://github.com/skyscreamer/JSONassert/compare/jsonassert-1.5.1...jsonassert-1.5.3) --- updated-dependencies: - dependency-name: org.skyscreamer:jsonassert dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 889c145898f..c5a181ef6de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -82,7 +82,7 @@ org-python-jython = { module = "org.python:jython", version = "2.5.3" } org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.20.0" org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.20.0" org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59" -org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1" +org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.13" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.1" From 7b416d8992aa7ebf3323997c9f175ba18672a404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 04:03:15 +0000 Subject: [PATCH 18/68] Bump org.skyscreamer:jsonassert from 1.5.1 to 1.5.3 Bumps [org.skyscreamer:jsonassert](https://github.com/skyscreamer/JSONassert) from 1.5.1 to 1.5.3. - [Release notes](https://github.com/skyscreamer/JSONassert/releases) - [Changelog](https://github.com/skyscreamer/JSONassert/blob/master/CHANGELOG.md) - [Commits](https://github.com/skyscreamer/JSONassert/compare/jsonassert-1.5.1...jsonassert-1.5.3) --- updated-dependencies: - dependency-name: org.skyscreamer:jsonassert dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c24606865b0..e6e1dcf0a2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -81,7 +81,7 @@ org-python-jython = { module = "org.python:jython", version = "2.5.3" } org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit-driver:2.70.0" org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3.141.59" org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59" -org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1" +org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36" org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.13" org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2024.0.1" From 9189916287917efd7f2876a86f087e7d1b736c2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 04:00:30 +0000 Subject: [PATCH 19/68] Bump org.skyscreamer:jsonassert from 1.5.1 to 1.5.3 Bumps [org.skyscreamer:jsonassert](https://github.com/skyscreamer/JSONassert) from 1.5.1 to 1.5.3. - [Release notes](https://github.com/skyscreamer/JSONassert/releases) - [Changelog](https://github.com/skyscreamer/JSONassert/blob/master/CHANGELOG.md) - [Commits](https://github.com/skyscreamer/JSONassert/compare/jsonassert-1.5.1...jsonassert-1.5.3) --- updated-dependencies: - dependency-name: org.skyscreamer:jsonassert dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dee81f60ced..1829bd4bf74 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -87,7 +87,7 @@ org-python-jython = "org.python:jython:2.5.3" org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit-driver:2.65.0" org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:3.141.59" org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59" -org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.1" +org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3" org-slf4j-log4j-over-slf4j = { module = "org.slf4j:log4j-over-slf4j", version.ref = "org-slf4j" } org-slf4j-slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "org-slf4j" } org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2021.2.18" From 8917cdb404979ec23694ffe081ac891aa2e82bbd Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 1 Jul 2024 11:34:56 -0600 Subject: [PATCH 20/68] Improve Performance of IPv4 Check Closes gh-15324 --- .../web/util/matcher/IpAddressMatcher.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java index c2f547e48b2..e7a4fdab037 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/IpAddressMatcher.java @@ -18,7 +18,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.Scanner; +import java.util.regex.Pattern; import jakarta.servlet.http.HttpServletRequest; @@ -37,6 +37,8 @@ */ public final class IpAddressMatcher implements RequestMatcher { + private static Pattern IPV4 = Pattern.compile("\\d{0,3}.\\d{0,3}.\\d{0,3}.\\d{0,3}(/\\d{0,3})?"); + private final int nMaskBits; private final InetAddress requiredAddress; @@ -93,16 +95,13 @@ public boolean matches(String address) { } private void assertNotHostName(String ipAddress) { + boolean isIpv4 = IPV4.matcher(ipAddress).matches(); + if (isIpv4) { + return; + } String error = "ipAddress " + ipAddress + " doesn't look like an IP Address. Is it a host name?"; Assert.isTrue(ipAddress.charAt(0) == '[' || ipAddress.charAt(0) == ':' - || Character.digit(ipAddress.charAt(0), 16) != -1, error); - if (!ipAddress.contains(":")) { - Scanner parts = new Scanner(ipAddress); - parts.useDelimiter("[./]"); - while (parts.hasNext()) { - Assert.isTrue(parts.hasNextInt() && parts.nextInt() >> 8 == 0, error); - } - } + || (Character.digit(ipAddress.charAt(0), 16) != -1 && ipAddress.contains(":")), error); } private InetAddress parseAddress(String address) { From a9aefafb767464b8579fd098063ad940ee9e0570 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 30 Jun 2024 01:40:20 +0200 Subject: [PATCH 21/68] Fix malformed list in "Using Method Parameters" documentation --- .../ROOT/pages/servlet/authorization/method-security.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index d6c6e0320fc..fdd3a7dae38 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -1804,7 +1804,7 @@ The intention of this expression is to require that the current `Authentication` + Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation. -* If xref:servlet/integrations/data.adoc[Spring Data's] `@Param` annotation is present on at least one parameter for the method, the value is used. +2. If xref:servlet/integrations/data.adoc[Spring Data's] `@Param` annotation is present on at least one parameter for the method, the value is used. The following example uses the `@Param` annotation: + [tabs] @@ -1838,10 +1838,10 @@ The intention of this expression is to require that `name` be equal to `Authenti + Behind the scenes, this is implemented by using `AnnotationParameterNameDiscoverer`, which you can customize to support the value attribute of any specified annotation. -* If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names. +3. If you compile your code with the `-parameters` argument, the standard JDK reflection API is used to discover the parameter names. This works on both classes and interfaces. -* Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols. +4. Finally, if you compile your code with debug symbols, the parameter names are discovered by using the debug symbols. This does not work for interfaces, since they do not have debug information about the parameter names. For interfaces, either annotations or the `-parameters` approach must be used. From e7212b37f7dde9e2ae4f9ed1e568ad613f72948b Mon Sep 17 00:00:00 2001 From: Stefan Ganzer <10470489+stefanganzer@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:46:35 +0200 Subject: [PATCH 22/68] Update events.adoc Changes type to DefaultAuthenticationEventPublisher Only DefaultAuthenticationEventPublisher has the method setDefaultAuthenticationFailureEvent, but not the interface AuthenticationEventPublisher. --- docs/modules/ROOT/pages/servlet/authentication/events.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/events.adoc b/docs/modules/ROOT/pages/servlet/authentication/events.adoc index edb8b5146bd..f41ac3ef3e5 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/events.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/events.adoc @@ -143,7 +143,7 @@ Java:: @Bean public AuthenticationEventPublisher authenticationEventPublisher (ApplicationEventPublisher applicationEventPublisher) { - AuthenticationEventPublisher authenticationEventPublisher = + DefaultAuthenticationEventPublisher authenticationEventPublisher = new DefaultAuthenticationEventPublisher(applicationEventPublisher); authenticationEventPublisher.setDefaultAuthenticationFailureEvent (GenericAuthenticationFailureEvent.class); From ceb278c908d444fbca2c9ec94055403525a1d6af Mon Sep 17 00:00:00 2001 From: Stefan Ganzer <10470489+stefanganzer@users.noreply.github.com> Date: Sun, 30 Jun 2024 21:48:56 +0200 Subject: [PATCH 23/68] Update events.adoc Changes GenericAuthenticationFailureEvent to AbstractAuthenticationFailureEvent The class GenericAuthenticationFailureEvent does not exist. --- docs/modules/ROOT/pages/servlet/authentication/events.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/events.adoc b/docs/modules/ROOT/pages/servlet/authentication/events.adoc index f41ac3ef3e5..59ad12f0d93 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/events.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/events.adoc @@ -146,7 +146,7 @@ public AuthenticationEventPublisher authenticationEventPublisher DefaultAuthenticationEventPublisher authenticationEventPublisher = new DefaultAuthenticationEventPublisher(applicationEventPublisher); authenticationEventPublisher.setDefaultAuthenticationFailureEvent - (GenericAuthenticationFailureEvent.class); + (AbstractAuthenticationFailureEvent.class); return authenticationEventPublisher; } ---- @@ -159,7 +159,7 @@ Kotlin:: fun authenticationEventPublisher (applicationEventPublisher: ApplicationEventPublisher?): AuthenticationEventPublisher { val authenticationEventPublisher = DefaultAuthenticationEventPublisher(applicationEventPublisher) - authenticationEventPublisher.setDefaultAuthenticationFailureEvent(GenericAuthenticationFailureEvent::class.java) + authenticationEventPublisher.setDefaultAuthenticationFailureEvent(AbstractAuthenticationFailureEvent::class.java) return authenticationEventPublisher } ---- From 48826201b12a9a7bd1dbabcfb1eef66efa537b6a Mon Sep 17 00:00:00 2001 From: Dumitru Boldureanu Date: Fri, 28 Jun 2024 18:16:45 +0300 Subject: [PATCH 24/68] Update architecture.adoc The list of filters is printed at DEBUG level on the application startup and not INFO level, see DefaultSecurityFilterChain --- docs/modules/ROOT/pages/servlet/architecture.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/architecture.adoc b/docs/modules/ROOT/pages/servlet/architecture.adoc index 01139c110d9..71b421e6d4b 100644 --- a/docs/modules/ROOT/pages/servlet/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/architecture.adoc @@ -250,11 +250,11 @@ If you want to see the list of filters invoked for a particular request, you can Often times, it is useful to see the list of security ``Filter``s that are invoked for a particular request. For example, you want to make sure that the <> is in the list of the security filters. -The list of filters is printed at INFO level on the application startup, so you can see something like the following on the console output for example: +The list of filters is printed at DEBUG level on the application startup, so you can see something like the following on the console output for example: [source,text,role="terminal"] ---- -2023-06-14T08:55:22.321-03:00 INFO 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [ +2023-06-14T08:55:22.321-03:00 DEBUG 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [ org.springframework.security.web.session.DisableEncodeUrlFilter@404db674, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5, org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7, From 99cda3157952564e1e41baaa05c17634f62848e9 Mon Sep 17 00:00:00 2001 From: Antoine Rey Date: Sat, 29 Jun 2024 10:08:31 +0200 Subject: [PATCH 25/68] Update prerequisites documentation Raises the minimum version of the Java runtime for Spring Security from 8 to 17 Closes gh-15323 --- docs/modules/ROOT/pages/prerequisites.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/prerequisites.adoc b/docs/modules/ROOT/pages/prerequisites.adoc index 641271d465f..b1e3993c730 100644 --- a/docs/modules/ROOT/pages/prerequisites.adoc +++ b/docs/modules/ROOT/pages/prerequisites.adoc @@ -1,7 +1,7 @@ [[prerequisites]] = Prerequisites -Spring Security requires a Java 8 or higher Runtime Environment. +Spring Security requires a Java 17 or higher Runtime Environment. As Spring Security aims to operate in a self-contained manner, you do not need to place any special configuration files in your Java Runtime Environment. In particular, you need not configure a special Java Authentication and Authorization Service (JAAS) policy file or place Spring Security into common classpath locations. From 850c0a46907f11efc6ebe7af9a32a89c55ff357f Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 1 Jul 2024 18:08:45 -0600 Subject: [PATCH 26/68] Fix Spring Framework Reference Link --- docs/spring-security-docs.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/spring-security-docs.gradle b/docs/spring-security-docs.gradle index 2f7cf9c7941..09879391bd7 100644 --- a/docs/spring-security-docs.gradle +++ b/docs/spring-security-docs.gradle @@ -44,7 +44,7 @@ def generateAttributes() { def securityApiUrl = "$securityDocsUrl/api/" def securityReferenceUrl = "$securityDocsUrl/reference/html5/" def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/" - def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/" + def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/reference/$springFrameworkVersion/" def springBootReferenceUrl = "https://docs.spring.io/spring-boot/docs/$springBootVersion/reference/html/" def springBootApiUrl = "https://docs.spring.io/spring-boot/docs/$springBootVersion/api/" From 1e2900328b05b6164d29aa1148096cf7a651a3db Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 1 Jul 2024 17:20:35 -0600 Subject: [PATCH 27/68] Add IterableRelyingPartyRegistrationRepository Closes gh-15027 --- ...oryRelyingPartyRegistrationRepository.java | 5 ++- ...bleRelyingPartyRegistrationRepository.java | 31 +++++++++++++++++++ ...equestMatcherMetadataResponseResolver.java | 4 +++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/IterableRelyingPartyRegistrationRepository.java diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java index 738f00952fa..01f4778027d 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/InMemoryRelyingPartyRegistrationRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 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. @@ -36,8 +36,7 @@ * @author Josh Cummings * @since 5.2 */ -public class InMemoryRelyingPartyRegistrationRepository - implements RelyingPartyRegistrationRepository, Iterable { +public class InMemoryRelyingPartyRegistrationRepository implements IterableRelyingPartyRegistrationRepository { private final Map byRegistrationId; diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/IterableRelyingPartyRegistrationRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/IterableRelyingPartyRegistrationRepository.java new file mode 100644 index 00000000000..c41261f6a11 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/IterableRelyingPartyRegistrationRepository.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +/** + * An interface that simplifies APIs which require the + * {@link RelyingPartyRegistrationRepository} to also be {@link Iterable} + * + * @author Josh Cummings + * @since 6.4 + * @see InMemoryRelyingPartyRegistrationRepository + * @see CachingRelyingPartyRegistrationRepository + */ +public interface IterableRelyingPartyRegistrationRepository + extends RelyingPartyRegistrationRepository, Iterable { + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/metadata/RequestMatcherMetadataResponseResolver.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/metadata/RequestMatcherMetadataResponseResolver.java index 96850d36616..79dcb34dcc2 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/metadata/RequestMatcherMetadataResponseResolver.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/web/metadata/RequestMatcherMetadataResponseResolver.java @@ -30,6 +30,7 @@ import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse; import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver; +import org.springframework.security.saml2.provider.service.registration.IterableRelyingPartyRegistrationRepository; 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.RelyingPartyRegistrationPlaceholderResolvers; @@ -105,6 +106,9 @@ public Saml2MetadataResponse resolve(HttpServletRequest request) { if (response != null) { return response; } + if (this.registrations instanceof IterableRelyingPartyRegistrationRepository iterable) { + return responseByIterable(request, iterable); + } if (this.registrations instanceof Iterable) { Iterable registrations = (Iterable) this.registrations; return responseByIterable(request, registrations); From 7b398006068778ad67cd738d8a51df0765414c5f Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 1 Jul 2024 19:10:57 -0600 Subject: [PATCH 28/68] Add CachingRelyingPartyRegistrationRepository Closes gh-15341 --- .../pages/servlet/saml2/login/overview.adoc | 51 ++++++++++ ...ingRelyingPartyRegistrationRepository.java | 95 +++++++++++++++++++ ...lyingPartyRegistrationRepositoryTests.java | 81 ++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepository.java create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepositoryTests.java diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc index 2ab0b7554c2..0721d9b15eb 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc @@ -588,6 +588,57 @@ class MyCustomSecurityConfiguration { A relying party can be multi-tenant by registering more than one relying party in the `RelyingPartyRegistrationRepository`. ==== +[[servlet-saml2login-relyingpartyregistrationrepository-caching]] +If you want your metadata to be refreshable on a periodic basis, you can wrap your repository in `CachingRelyingPartyRegistrationRepository` like so: + +.Caching Relying Party Registration Repository +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Configuration +@EnableWebSecurity +public class MyCustomSecurityConfiguration { + @Bean + public RelyingPartyRegistrationRepository registrations(CacheManager cacheManager) { + Supplier delegate = () -> + new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations + .fromMetadataLocation("https://idp.example.org/ap/metadata") + .registrationId("ap").build()); + CachingRelyingPartyRegistrationRepository registrations = + new CachingRelyingPartyRegistrationRepository(delegate); + registrations.setCache(cacheManager.getCache("my-cache-name")); + return registrations; + } +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Configuration +@EnableWebSecurity +class MyCustomSecurityConfiguration { + @Bean + fun registrations(cacheManager: CacheManager): RelyingPartyRegistrationRepository { + val delegate = Supplier { + InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations + .fromMetadataLocation("https://idp.example.org/ap/metadata") + .registrationId("ap").build()) + } + val registrations = CachingRelyingPartyRegistrationRepository(delegate) + registrations.setCache(cacheManager.getCache("my-cache-name")) + return registrations + } +} +---- +====== + +In this way, the set of `RelyingPartyRegistration`s will refresh based on {spring-framework-reference-url}integration/cache/store-configuration.html[the cache's eviction schedule]. + [[servlet-saml2login-relyingpartyregistration]] == RelyingPartyRegistration A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`] diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepository.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepository.java new file mode 100644 index 00000000000..bfc39e486fb --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepository.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +import org.springframework.cache.Cache; +import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.util.Assert; + +/** + * An {@link IterableRelyingPartyRegistrationRepository} that lazily queries and caches + * metadata from a backing {@link IterableRelyingPartyRegistrationRepository}. Delegates + * caching policies to Spring Cache. + * + * @author Josh Cummings + * @since 6.4 + */ +public final class CachingRelyingPartyRegistrationRepository implements IterableRelyingPartyRegistrationRepository { + + private final Callable registrationLoader; + + private Cache cache = new ConcurrentMapCache("registrations"); + + public CachingRelyingPartyRegistrationRepository(Callable loader) { + this.registrationLoader = loader; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() { + return registrations().iterator(); + } + + /** + * {@inheritDoc} + */ + @Override + public RelyingPartyRegistration findByRegistrationId(String registrationId) { + return registrations().findByRegistrationId(registrationId); + } + + @Override + public RelyingPartyRegistration findUniqueByAssertingPartyEntityId(String entityId) { + return registrations().findUniqueByAssertingPartyEntityId(entityId); + } + + @Override + public void forEach(Consumer action) { + registrations().forEach(action); + } + + @Override + public Spliterator spliterator() { + return registrations().spliterator(); + } + + private IterableRelyingPartyRegistrationRepository registrations() { + return this.cache.get("registrations", this.registrationLoader); + } + + /** + * Use this cache for the completed {@link RelyingPartyRegistration} instances. + * + *

+ * Defaults to {@link ConcurrentMapCache}, meaning that the registrations are cached + * without expiry. To turn off the cache, use + * {@link org.springframework.cache.support.NoOpCache}. + * @param cache the {@link Cache} to use + */ + public void setCache(Cache cache) { + Assert.notNull(cache, "cache cannot be null"); + this.cache = cache; + } + +} diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepositoryTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepositoryTests.java new file mode 100644 index 00000000000..7e4d57d444e --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/CachingRelyingPartyRegistrationRepositoryTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.cache.Cache; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +/** + * Tests for {@link CachingRelyingPartyRegistrationRepository} + */ +@ExtendWith(MockitoExtension.class) +public class CachingRelyingPartyRegistrationRepositoryTests { + + @Mock + Callable> callable; + + @InjectMocks + CachingRelyingPartyRegistrationRepository registrations; + + @Test + public void iteratorWhenResolvableThenPopulatesCache() throws Exception { + given(this.callable.call()).willReturn(mock(IterableRelyingPartyRegistrationRepository.class)); + this.registrations.iterator(); + verify(this.callable).call(); + this.registrations.iterator(); + verifyNoMoreInteractions(this.callable); + } + + @Test + public void iteratorWhenExceptionThenPropagates() throws Exception { + given(this.callable.call()).willThrow(IllegalStateException.class); + assertThatExceptionOfType(Cache.ValueRetrievalException.class).isThrownBy(this.registrations::iterator) + .withCauseInstanceOf(IllegalStateException.class); + } + + @Test + public void findByRegistrationIdWhenResolvableThenPopulatesCache() throws Exception { + given(this.callable.call()).willReturn(mock(IterableRelyingPartyRegistrationRepository.class)); + this.registrations.findByRegistrationId("id"); + verify(this.callable).call(); + this.registrations.findByRegistrationId("id"); + verifyNoMoreInteractions(this.callable); + } + + @Test + public void findUniqueByAssertingPartyEntityIdWhenResolvableThenPopulatesCache() throws Exception { + given(this.callable.call()).willReturn(mock(IterableRelyingPartyRegistrationRepository.class)); + this.registrations.findUniqueByAssertingPartyEntityId("id"); + verify(this.callable).call(); + this.registrations.findUniqueByAssertingPartyEntityId("id"); + verifyNoMoreInteractions(this.callable); + } + +} From 6bd2f1ca973c4ef4a1afc4f5c39f359094770950 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 1 Jul 2024 18:53:03 -0600 Subject: [PATCH 29/68] Deprecate OpenSamlRelyingPartyRegistration Closes gh-15343 --- .../OpenSamlAssertingPartyDetails.java | 14 +++++++ .../OpenSamlRelyingPartyRegistration.java | 31 ++++++++------ .../RelyingPartyRegistration.java | 40 ++++++++----------- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java index 25d5738a6c1..0d780d0e0d5 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java @@ -65,6 +65,20 @@ public static OpenSamlAssertingPartyDetails.Builder withEntityDescriptor(EntityD return new OpenSamlAssertingPartyDetails.Builder(entity); } + @Override + public OpenSamlAssertingPartyDetails.Builder mutate() { + return new OpenSamlAssertingPartyDetails.Builder(this.descriptor).entityId(getEntityId()) + .wantAuthnRequestsSigned(getWantAuthnRequestsSigned()) + .signingAlgorithms((algorithms) -> algorithms.addAll(getSigningAlgorithms())) + .verificationX509Credentials((c) -> c.addAll(getVerificationX509Credentials())) + .encryptionX509Credentials((c) -> c.addAll(getEncryptionX509Credentials())) + .singleSignOnServiceLocation(getSingleSignOnServiceLocation()) + .singleSignOnServiceBinding(getSingleSignOnServiceBinding()) + .singleLogoutServiceLocation(getSingleLogoutServiceLocation()) + .singleLogoutServiceResponseLocation(getSingleLogoutServiceResponseLocation()) + .singleLogoutServiceBinding(getSingleLogoutServiceBinding()); + } + /** * An OpenSAML version of * {@link org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails.Builder} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistration.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistration.java index ce9061ad4a3..9b3f26dc3a9 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistration.java @@ -29,7 +29,19 @@ * * @author Josh Cummings * @since 6.1 + * @deprecated This class no longer is needed in order to transmit the + * {@link EntityDescriptor} to {@link OpenSamlAssertingPartyDetails}. Instead of doing: + *

+ * 	if (registration instanceof OpenSamlRelyingPartyRegistration openSamlRegistration) {
+ * 	    EntityDescriptor descriptor = openSamlRegistration.getAssertingPartyDetails.getEntityDescriptor();
+ * 	}
+ * 
do instead:
+ * 	if (registration.getAssertingPartyDetails() instanceof openSamlAssertingPartyDetails) {
+ * 	    EntityDescriptor descriptor = openSamlAssertingPartyDetails.getEntityDescriptor();
+ * 	}
+ * 
*/ +@Deprecated public final class OpenSamlRelyingPartyRegistration extends RelyingPartyRegistration { OpenSamlRelyingPartyRegistration(RelyingPartyRegistration registration) { @@ -47,7 +59,7 @@ public final class OpenSamlRelyingPartyRegistration extends RelyingPartyRegistra @Override public OpenSamlRelyingPartyRegistration.Builder mutate() { OpenSamlAssertingPartyDetails party = getAssertingPartyDetails(); - return withAssertingPartyEntityDescriptor(party.getEntityDescriptor()).registrationId(getRegistrationId()) + return new Builder(party).registrationId(getRegistrationId()) .entityId(getEntityId()) .signingX509Credentials((c) -> c.addAll(getSigningX509Credentials())) .decryptionX509Credentials((c) -> c.addAll(getDecryptionX509Credentials())) @@ -57,18 +69,7 @@ public OpenSamlRelyingPartyRegistration.Builder mutate() { .singleLogoutServiceResponseLocation(getSingleLogoutServiceResponseLocation()) .singleLogoutServiceBindings((c) -> c.addAll(getSingleLogoutServiceBindings())) .nameIdFormat(getNameIdFormat()) - .authnRequestsSigned(isAuthnRequestsSigned()) - .assertingPartyDetails((assertingParty) -> ((OpenSamlAssertingPartyDetails.Builder) assertingParty) - .entityId(party.getEntityId()) - .wantAuthnRequestsSigned(party.getWantAuthnRequestsSigned()) - .signingAlgorithms((algorithms) -> algorithms.addAll(party.getSigningAlgorithms())) - .verificationX509Credentials((c) -> c.addAll(party.getVerificationX509Credentials())) - .encryptionX509Credentials((c) -> c.addAll(party.getEncryptionX509Credentials())) - .singleSignOnServiceLocation(party.getSingleSignOnServiceLocation()) - .singleSignOnServiceBinding(party.getSingleSignOnServiceBinding()) - .singleLogoutServiceLocation(party.getSingleLogoutServiceLocation()) - .singleLogoutServiceResponseLocation(party.getSingleLogoutServiceResponseLocation()) - .singleLogoutServiceBinding(party.getSingleLogoutServiceBinding())); + .authnRequestsSigned(isAuthnRequestsSigned()); } /** @@ -100,6 +101,10 @@ private Builder(EntityDescriptor entityDescriptor) { super(entityDescriptor.getEntityID(), OpenSamlAssertingPartyDetails.withEntityDescriptor(entityDescriptor)); } + Builder(OpenSamlAssertingPartyDetails details) { + super(details.getEntityDescriptor().getEntityID(), details.mutate()); + } + @Override public Builder registrationId(String id) { return (Builder) super.registrationId(id); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java index 9e6f1b75332..87cfea754ec 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java @@ -139,8 +139,7 @@ protected RelyingPartyRegistration(String registrationId, String entityId, Strin * @since 6.1 */ public Builder mutate() { - AssertingPartyDetails party = this.assertingPartyDetails; - return withRegistrationId(this.registrationId).entityId(this.entityId) + return new Builder(this.registrationId, this.assertingPartyDetails.mutate()).entityId(this.entityId) .signingX509Credentials((c) -> c.addAll(this.signingX509Credentials)) .decryptionX509Credentials((c) -> c.addAll(this.decryptionX509Credentials)) .assertionConsumerServiceLocation(this.assertionConsumerServiceLocation) @@ -149,17 +148,7 @@ public Builder mutate() { .singleLogoutServiceResponseLocation(this.singleLogoutServiceResponseLocation) .singleLogoutServiceBindings((c) -> c.addAll(this.singleLogoutServiceBindings)) .nameIdFormat(this.nameIdFormat) - .authnRequestsSigned(this.authnRequestsSigned) - .assertingPartyDetails((assertingParty) -> assertingParty.entityId(party.getEntityId()) - .wantAuthnRequestsSigned(party.getWantAuthnRequestsSigned()) - .signingAlgorithms((algorithms) -> algorithms.addAll(party.getSigningAlgorithms())) - .verificationX509Credentials((c) -> c.addAll(party.getVerificationX509Credentials())) - .encryptionX509Credentials((c) -> c.addAll(party.getEncryptionX509Credentials())) - .singleSignOnServiceLocation(party.getSingleSignOnServiceLocation()) - .singleSignOnServiceBinding(party.getSingleSignOnServiceBinding()) - .singleLogoutServiceLocation(party.getSingleLogoutServiceLocation()) - .singleLogoutServiceResponseLocation(party.getSingleLogoutServiceResponseLocation()) - .singleLogoutServiceBinding(party.getSingleLogoutServiceBinding())); + .authnRequestsSigned(this.authnRequestsSigned); } /** @@ -346,17 +335,7 @@ public static Builder withRegistrationId(String registrationId) { public static Builder withAssertingPartyDetails(AssertingPartyDetails assertingPartyDetails) { Assert.notNull(assertingPartyDetails, "assertingPartyDetails cannot be null"); - return withRegistrationId(assertingPartyDetails.getEntityId()) - .assertingPartyDetails((party) -> party.entityId(assertingPartyDetails.getEntityId()) - .wantAuthnRequestsSigned(assertingPartyDetails.getWantAuthnRequestsSigned()) - .signingAlgorithms((algorithms) -> algorithms.addAll(assertingPartyDetails.getSigningAlgorithms())) - .verificationX509Credentials((c) -> c.addAll(assertingPartyDetails.getVerificationX509Credentials())) - .encryptionX509Credentials((c) -> c.addAll(assertingPartyDetails.getEncryptionX509Credentials())) - .singleSignOnServiceLocation(assertingPartyDetails.getSingleSignOnServiceLocation()) - .singleSignOnServiceBinding(assertingPartyDetails.getSingleSignOnServiceBinding()) - .singleLogoutServiceLocation(assertingPartyDetails.getSingleLogoutServiceLocation()) - .singleLogoutServiceResponseLocation(assertingPartyDetails.getSingleLogoutServiceResponseLocation()) - .singleLogoutServiceBinding(assertingPartyDetails.getSingleLogoutServiceBinding())); + return new Builder(assertingPartyDetails.getEntityId(), assertingPartyDetails.mutate()); } /** @@ -592,6 +571,19 @@ public Saml2MessageBinding getSingleLogoutServiceBinding() { return this.singleLogoutServiceBinding; } + public AssertingPartyDetails.Builder mutate() { + return new AssertingPartyDetails.Builder().entityId(this.entityId) + .wantAuthnRequestsSigned(this.wantAuthnRequestsSigned) + .signingAlgorithms((algorithms) -> algorithms.addAll(this.signingAlgorithms)) + .verificationX509Credentials((c) -> c.addAll(this.verificationX509Credentials)) + .encryptionX509Credentials((c) -> c.addAll(this.encryptionX509Credentials)) + .singleSignOnServiceLocation(this.singleSignOnServiceLocation) + .singleSignOnServiceBinding(this.singleSignOnServiceBinding) + .singleLogoutServiceLocation(this.singleLogoutServiceLocation) + .singleLogoutServiceResponseLocation(this.singleLogoutServiceResponseLocation) + .singleLogoutServiceBinding(this.singleLogoutServiceBinding); + } + public static class Builder { private String entityId; From 5b44972e142f41d20f24573e635e61e1d60f0cc4 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Tue, 2 Jul 2024 10:18:47 -0300 Subject: [PATCH 30/68] Revert "Disable check-samples temporarily" This reverts commit fd1246a0b00db3d2ead4116e22d0bf03251b382e. --- .../continuous-integration-workflow.yml | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index f7f680f56c7..fd77d0a5978 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -41,29 +41,29 @@ jobs: java-version: ${{ matrix.java-version }} test-args: --refresh-dependencies -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=6.2.+ -PreactorVersion=2023.0.+ -PspringDataVersion=2024.0.+ --stacktrace secrets: inherit -# check-samples: -# name: Check Samples -# runs-on: ubuntu-latest -# if: ${{ github.repository_owner == 'spring-projects' }} -# steps: -# - uses: actions/checkout@v4 -# - name: Set up gradle -# uses: spring-io/spring-gradle-build-action@v2 -# with: -# java-version: 17 -# distribution: temurin -# - name: Check samples project -# env: -# LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos -# SAMPLES_DIR: ../spring-security-samples -# run: | -# # Extract version from gradle.properties -# version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}') -# # Extract samplesBranch from gradle.properties -# samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}') -# ./gradlew publishMavenJavaPublicationToLocalRepository -# ./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR" -# ./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests + check-samples: + name: Check Samples + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'spring-projects' }} + steps: + - uses: actions/checkout@v4 + - name: Set up gradle + uses: spring-io/spring-gradle-build-action@v2 + with: + java-version: 17 + distribution: temurin + - name: Check samples project + env: + LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos + SAMPLES_DIR: ../spring-security-samples + run: | + # Extract version from gradle.properties + version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}') + # Extract samplesBranch from gradle.properties + samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}') + ./gradlew publishMavenJavaPublicationToLocalRepository + ./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR" + ./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests check-tangles: name: Check for Package Tangles runs-on: ubuntu-latest @@ -82,21 +82,21 @@ jobs: ./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace deploy-artifacts: name: Deploy Artifacts - needs: [ build, test, check-tangles ] + needs: [ build, test, check-samples, check-tangles ] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1 with: should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} secrets: inherit deploy-docs: name: Deploy Docs - needs: [ build, test, check-tangles ] + needs: [ build, test, check-samples, check-tangles ] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1 with: should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }} secrets: inherit deploy-schema: name: Deploy Schema - needs: [ build, test, check-tangles ] + needs: [ build, test, check-samples, check-tangles ] uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1 with: should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} From 207680ba023555397d8c95e741f03e8a8b120cbd Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Tue, 2 Jul 2024 12:07:31 -0300 Subject: [PATCH 31/68] Try check task Issue gh-15265 --- .github/workflows/continuous-integration-workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index fd77d0a5978..cc4f6c94f90 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -63,7 +63,7 @@ jobs: samples_branch=$(cat gradle.properties | grep "samplesBranch=" | awk -F'=' '{print $2}') ./gradlew publishMavenJavaPublicationToLocalRepository ./gradlew cloneRepository -PrepositoryName="spring-projects/spring-security-samples" -Pref="$samples_branch" -PcloneOutputDirectory="$SAMPLES_DIR" - ./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" :runAllTests + ./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$version" check check-tangles: name: Check for Package Tangles runs-on: ubuntu-latest From 773e86701edcd6f2179ad66204f55e24c3ae3d51 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 21 Jun 2024 18:49:12 -0600 Subject: [PATCH 32/68] Add ParameterRequestMatcher Closes gh-15342 --- .../saml2/Saml2LogoutConfigurer.java | 22 +---- .../http/Saml2LogoutBeanDefinitionParser.java | 22 +---- .../util/matcher/ParameterRequestMatcher.java | 91 +++++++++++++++++++ .../matcher/ParameterRequestMatcherTests.java | 64 +++++++++++++ 4 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java create mode 100644 web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index 914d46f8bae..960e9e1d71c 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -18,8 +18,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.function.Predicate; import jakarta.servlet.http.HttpServletRequest; @@ -60,6 +58,7 @@ import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.ParameterRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; /** @@ -508,23 +507,6 @@ public boolean matches(HttpServletRequest request) { } - private static class ParameterRequestMatcher implements RequestMatcher { - - Predicate test = Objects::nonNull; - - String name; - - ParameterRequestMatcher(String name) { - this.name = name; - } - - @Override - public boolean matches(HttpServletRequest request) { - return this.test.test(request.getParameter(this.name)); - } - - } - private static class Saml2RelyingPartyInitiatedLogoutFilter extends LogoutFilter { Saml2RelyingPartyInitiatedLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java index 5f894cf8d8f..860ed9fc551 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -18,8 +18,6 @@ import java.util.Arrays; import java.util.List; -import java.util.Objects; -import java.util.function.Predicate; import jakarta.servlet.http.HttpServletRequest; import org.w3c.dom.Element; @@ -44,6 +42,7 @@ import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.ParameterRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -228,23 +227,6 @@ BeanDefinition getLogoutFilter() { return this.logoutFilter; } - private static class ParameterRequestMatcher implements RequestMatcher { - - Predicate test = Objects::nonNull; - - String name; - - ParameterRequestMatcher(String name) { - this.name = name; - } - - @Override - public boolean matches(HttpServletRequest request) { - return this.test.test(request.getParameter(this.name)); - } - - } - public static class Saml2RequestMatcher implements RequestMatcher { private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java new file mode 100644 index 00000000000..a81976de3a6 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.util.matcher; + +import java.util.Map; +import java.util.Objects; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * A {@link RequestMatcher} for matching on a request parameter and its value. + * + *

+ * The value may also be specified as a placeholder in order to match on any value, + * returning the value as part of the {@link MatchResult}. + * + * @author Josh Cummings + * @since 6.4 + */ +public final class ParameterRequestMatcher implements RequestMatcher { + + private static final MatchesValueMatcher NON_NULL = Objects::nonNull; + + private final String name; + + private final ValueMatcher matcher; + + public ParameterRequestMatcher(String name) { + this.name = name; + this.matcher = NON_NULL; + } + + public ParameterRequestMatcher(String name, String value) { + this.name = name; + MatchesValueMatcher matcher = value::equals; + if (value.startsWith("{") && value.endsWith("}")) { + String key = value.substring(1, value.length() - 1); + this.matcher = (v) -> (v != null) ? MatchResult.match(Map.of(key, v)) : MatchResult.notMatch(); + } + else { + this.matcher = matcher; + } + } + + @Override + public boolean matches(HttpServletRequest request) { + return matcher(request).isMatch(); + } + + @Override + public MatchResult matcher(HttpServletRequest request) { + String parameterValue = request.getParameter(this.name); + return this.matcher.matcher(parameterValue); + } + + private interface ValueMatcher { + + MatchResult matcher(String value); + + } + + private interface MatchesValueMatcher extends ValueMatcher { + + default MatchResult matcher(String value) { + if (matches(value)) { + return MatchResult.match(); + } + else { + return MatchResult.notMatch(); + } + } + + boolean matches(String value); + + } + +} diff --git a/web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java b/web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java new file mode 100644 index 00000000000..6da461ed4ce --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.util.matcher; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ParameterRequestMatcher} + * + * @author Josh Cummings + */ +@ExtendWith(MockitoExtension.class) +public class ParameterRequestMatcherTests { + + @Test + public void matchesWhenNameThenMatchesOnParameterName() { + ParameterRequestMatcher matcher = new ParameterRequestMatcher("name"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/bar"); + assertThat(matcher.matches(request)).isFalse(); + request.setParameter("name", "value"); + assertThat(matcher.matches(request)).isTrue(); + } + + @Test + public void matchesWhenNameAndValueThenMatchesOnBoth() { + ParameterRequestMatcher matcher = new ParameterRequestMatcher("name", "value"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/bar"); + request.setParameter("name", "value"); + assertThat(matcher.matches(request)).isTrue(); + request.setParameter("name", "wrong"); + assertThat(matcher.matches(request)).isFalse(); + } + + @Test + public void matchesWhenValuePlaceholderThenMatchesOnName() { + ParameterRequestMatcher matcher = new ParameterRequestMatcher("name", "{placeholder}"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/bar"); + request.setParameter("name", "value"); + RequestMatcher.MatchResult result = matcher.matcher(request); + assertThat(result.isMatch()).isTrue(); + assertThat(result.getVariables().get("placeholder")).isEqualTo("value"); + } + +} From a3095527a002bcb710cf0934ae44e6841ea0eff9 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Wed, 3 Jul 2024 13:47:28 -0300 Subject: [PATCH 33/68] Label superseded dependabot updates as duplicates Issue gh-14484 --- .../mark-duplicate-dependabot-prs.yml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/mark-duplicate-dependabot-prs.yml diff --git a/.github/workflows/mark-duplicate-dependabot-prs.yml b/.github/workflows/mark-duplicate-dependabot-prs.yml new file mode 100644 index 00000000000..8dcb5ffd19d --- /dev/null +++ b/.github/workflows/mark-duplicate-dependabot-prs.yml @@ -0,0 +1,46 @@ +name: Mark Duplicate PRs + +on: + pull_request: + types: [closed] + +jobs: + check_duplicate_prs: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == 'true' && github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Extract Dependency Name from PR Title + id: extract + run: | + PR_TITLE="${{ github.event.pull_request.title }}" + DEPENDENCY_NAME=$(echo "$PR_TITLE" | awk -F ' from ' '{print $1}') + echo "dependency_name=$DEPENDENCY_NAME" >> $GITHUB_OUTPUT + + - name: Find PRs + id: find_duplicates + env: + DEPENDENCY_NAME: ${{ steps.extract.outputs.dependency_name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PRS=$(gh pr list --search "milestone:${{ github.event.pull_request.milestone.title }} is:merged $DEPENDENCY_NAME" --json number --jq 'map(.number) | join(",")') + echo "prs=$PRS" >> $GITHUB_OUTPUT + + - name: Label Duplicate PRs + if: steps.find_duplicates.outputs.prs != '' + env: + PRS: ${{ steps.find_duplicates.outputs.prs }} + CURRENT_PR_NUMBER: ${{ github.event.pull_request.number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + for i in ${PRS//,/ } + do + if [ ! $i -eq "$CURRENT_PR_NUMBER" ]; then + echo "Marking PR $i as duplicate" + gh pr edit "$i" --add-label "status: duplicate" + gh pr comment "$i" --body "Duplicate of #$CURRENT_PR_NUMBER" + fi + done From 7e391c3c114132473cdb19c0dc7b6ab1f35d9446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 03:25:42 +0000 Subject: [PATCH 34/68] Bump org-eclipse-jetty from 11.0.21 to 11.0.22 Bumps `org-eclipse-jetty` from 11.0.21 to 11.0.22. Updates `org.eclipse.jetty:jetty-server` from 11.0.21 to 11.0.22 Updates `org.eclipse.jetty:jetty-servlet` from 11.0.21 to 11.0.22 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e6e1dcf0a2f..02bcedb6c82 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ org-apache-directory-server = "1.5.5" org-apache-maven-resolver = "1.9.20" org-aspectj = "1.9.22.1" org-bouncycastle = "1.78.1" -org-eclipse-jetty = "11.0.21" +org-eclipse-jetty = "11.0.22" org-jetbrains-kotlin = "1.9.24" org-jetbrains-kotlinx = "1.8.1" org-mockito = "5.11.0" From 298a75ebbd214e1df6ef16efe0f5d8b069cf80a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 03:26:56 +0000 Subject: [PATCH 35/68] Bump org-eclipse-jetty from 11.0.21 to 11.0.22 Bumps `org-eclipse-jetty` from 11.0.21 to 11.0.22. Updates `org.eclipse.jetty:jetty-server` from 11.0.21 to 11.0.22 Updates `org.eclipse.jetty:jetty-servlet` from 11.0.21 to 11.0.22 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c5a181ef6de..fb595c10be0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ org-apache-directory-server = "1.5.5" org-apache-maven-resolver = "1.9.20" org-aspectj = "1.9.22.1" org-bouncycastle = "1.78.1" -org-eclipse-jetty = "11.0.21" +org-eclipse-jetty = "11.0.22" org-jetbrains-kotlin = "1.9.24" org-jetbrains-kotlinx = "1.8.1" org-mockito = "5.11.0" From 138daec14c6f2d95adef1630cd2e81d876f8f40f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 03:29:55 +0000 Subject: [PATCH 36/68] Bump org-eclipse-jetty from 11.0.21 to 11.0.22 Bumps `org-eclipse-jetty` from 11.0.21 to 11.0.22. Updates `org.eclipse.jetty:jetty-server` from 11.0.21 to 11.0.22 Updates `org.eclipse.jetty:jetty-servlet` from 11.0.21 to 11.0.22 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1bf557c338e..e8ce8d130f3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ org-apache-directory-server = "1.5.5" org-apache-maven-resolver = "1.9.20" org-aspectj = "1.9.22.1" org-bouncycastle = "1.70" -org-eclipse-jetty = "11.0.21" +org-eclipse-jetty = "11.0.22" org-jetbrains-kotlin = "1.9.24" org-jetbrains-kotlinx = "1.7.3" org-mockito = "5.5.0" From 2bdb57a8a87dada984f57e162aa51fb806bd33f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Jul 2024 03:53:02 +0000 Subject: [PATCH 37/68] Bump org-eclipse-jetty from 9.4.54.v20240208 to 9.4.55.v20240627 Bumps `org-eclipse-jetty` from 9.4.54.v20240208 to 9.4.55.v20240627. Updates `org.eclipse.jetty:jetty-server` from 9.4.54.v20240208 to 9.4.55.v20240627 Updates `org.eclipse.jetty:jetty-servlet` from 9.4.54.v20240208 to 9.4.55.v20240627 --- updated-dependencies: - dependency-name: org.eclipse.jetty:jetty-server dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.jetty:jetty-servlet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1829bd4bf74..ca69bd974f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ io-spring-nohttp = "0.0.11" org-apache-directory-server = "1.5.5" org-aspectj = "1.9.20.1" org-bouncycastle = "1.70" -org-eclipse-jetty = "9.4.54.v20240208" +org-eclipse-jetty = "9.4.55.v20240627" org-jetbrains-kotlin = "1.7.22" org-jetbrains-kotlinx = "1.6.4" org-junit-jupiter = "5.9.3" From ba38cdad0e27e63c45543273afcce81698d68125 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:12:27 +0000 Subject: [PATCH 38/68] Bump org-apache-maven-resolver from 1.9.20 to 1.9.21 Bumps `org-apache-maven-resolver` from 1.9.20 to 1.9.21. Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21) Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21) Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.20 to 1.9.21 --- updated-dependencies: - dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-impl dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-transport-http dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fb595c10be0..73b7eb4bbce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ io-spring-javaformat = "0.0.42" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.1.1" org-apache-directory-server = "1.5.5" -org-apache-maven-resolver = "1.9.20" +org-apache-maven-resolver = "1.9.21" org-aspectj = "1.9.22.1" org-bouncycastle = "1.78.1" org-eclipse-jetty = "11.0.22" From 41f1706b06ea99ee00be3863f34f9a44219a7cce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:12:40 +0000 Subject: [PATCH 39/68] Bump com.github.spullara.mustache.java:compiler from 0.9.13 to 0.9.14 Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.13 to 0.9.14. - [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.13...mustache.java-0.9.14) --- updated-dependencies: - dependency-name: com.github.spullara.mustache.java:compiler dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73b7eb4bbce..175a11985ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -98,7 +98,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2" io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.3.0" org-gretty-gretty = "org.gretty:gretty:4.1.4" com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.51.0" -com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13" +com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.14" org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1" org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.33.20" org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969" From ebb842e6541b5e39e89664af46401491cb2a1553 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:12:56 +0000 Subject: [PATCH 40/68] Bump com.fasterxml.jackson:jackson-bom from 2.17.1 to 2.17.2 Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.17.1 to 2.17.2. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.17.1...jackson-bom-2.17.2) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 175a11985ad..8deae714118 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ org-springframework = "6.2.0-M4" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.6" -com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.17.1" +com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.17.2" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.3" From 76f6693e735f7c31166a2f37780fc48613f939f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:43:01 +0000 Subject: [PATCH 41/68] Bump org-apache-maven-resolver from 1.9.20 to 1.9.21 Bumps `org-apache-maven-resolver` from 1.9.20 to 1.9.21. Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21) Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21) Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.20 to 1.9.21 --- updated-dependencies: - dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-impl dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-transport-http dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02bcedb6c82..109b74950ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ io-spring-javaformat = "0.0.42" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.1.1" org-apache-directory-server = "1.5.5" -org-apache-maven-resolver = "1.9.20" +org-apache-maven-resolver = "1.9.21" org-aspectj = "1.9.22.1" org-bouncycastle = "1.78.1" org-eclipse-jetty = "11.0.22" From 78e56f933ea8f3f817d252fcfc4a3e164ccffbf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:43:10 +0000 Subject: [PATCH 42/68] Bump com.github.spullara.mustache.java:compiler from 0.9.13 to 0.9.14 Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.13 to 0.9.14. - [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.13...mustache.java-0.9.14) --- updated-dependencies: - dependency-name: com.github.spullara.mustache.java:compiler dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 109b74950ae..2eb3317992c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -97,7 +97,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2" io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.3.0" org-gretty-gretty = "org.gretty:gretty:4.1.4" com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.51.0" -com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13" +com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.14" org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1" org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.33.20" org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969" From debfd4007998aad46bf7818d50c1e1ef5161f1fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:43:18 +0000 Subject: [PATCH 43/68] Bump com.fasterxml.jackson:jackson-bom from 2.17.1 to 2.17.2 Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.17.1 to 2.17.2. - [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.17.1...jackson-bom-2.17.2) --- updated-dependencies: - dependency-name: com.fasterxml.jackson:jackson-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2eb3317992c..42a80c64599 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ org-springframework = "6.1.10" [libraries] ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.6" -com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.17.1" +com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.17.2" com-google-inject-guice = "com.google.inject:guice:3.0" com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0" com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:9.37.3" From 05256da801efb8c01343f07ae2b38c0189d9162b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:44:50 +0000 Subject: [PATCH 44/68] Bump com.github.spullara.mustache.java:compiler from 0.9.13 to 0.9.14 Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.13 to 0.9.14. - [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.13...mustache.java-0.9.14) --- updated-dependencies: - dependency-name: com.github.spullara.mustache.java:compiler dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca69bd974f0..8b3c8cb2b68 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -103,7 +103,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2" io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0" org-gretty-gretty = "org.gretty:gretty:3.0.9" com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0" -com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13" +com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.14" org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1" org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4" org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1" From 36f31949e0d1c2f742fee83707243c8b9f447e25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:53:38 +0000 Subject: [PATCH 45/68] Bump org-apache-maven-resolver from 1.9.20 to 1.9.21 Bumps `org-apache-maven-resolver` from 1.9.20 to 1.9.21. Updates `org.apache.maven.resolver:maven-resolver-connector-basic` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21) Updates `org.apache.maven.resolver:maven-resolver-impl` from 1.9.20 to 1.9.21 - [Release notes](https://github.com/apache/maven-resolver/releases) - [Commits](https://github.com/apache/maven-resolver/compare/maven-resolver-1.9.20...maven-resolver-1.9.21) Updates `org.apache.maven.resolver:maven-resolver-transport-http` from 1.9.20 to 1.9.21 --- updated-dependencies: - dependency-name: org.apache.maven.resolver:maven-resolver-connector-basic dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-impl dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.maven.resolver:maven-resolver-transport-http dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e8ce8d130f3..9a7ca008be5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ io-spring-javaformat = "0.0.42" io-spring-nohttp = "0.0.11" jakarta-websocket = "2.1.1" org-apache-directory-server = "1.5.5" -org-apache-maven-resolver = "1.9.20" +org-apache-maven-resolver = "1.9.21" org-aspectj = "1.9.22.1" org-bouncycastle = "1.70" org-eclipse-jetty = "11.0.22" From 7fceda4d7be420fcc50c191ff045b200f2b4bef6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 03:53:18 +0000 Subject: [PATCH 46/68] Bump com.github.spullara.mustache.java:compiler from 0.9.13 to 0.9.14 Bumps [com.github.spullara.mustache.java:compiler](https://github.com/spullara/mustache.java) from 0.9.13 to 0.9.14. - [Commits](https://github.com/spullara/mustache.java/compare/mustache.java-0.9.13...mustache.java-0.9.14) --- updated-dependencies: - dependency-name: com.github.spullara.mustache.java:compiler dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9a7ca008be5..fca0ecf74a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -97,7 +97,7 @@ org-apache-commons-commons-io = "org.apache.commons:commons-io:1.3.2" io-github-gradle-nexus-publish-plugin = "io.github.gradle-nexus:publish-plugin:1.1.0" org-gretty-gretty = "org.gretty:gretty:4.0.3" com-github-ben-manes-gradle-versions-plugin = "com.github.ben-manes:gradle-versions-plugin:0.38.0" -com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.13" +com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:compiler:0.9.14" org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1" org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4" org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1" From 65c037853e9ceef72687aa99f029e5dd715adcf5 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Mon, 8 Jul 2024 11:08:36 -0300 Subject: [PATCH 47/68] Debug PR event payload Issue gh-14484 --- .github/workflows/mark-duplicate-dependabot-prs.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/mark-duplicate-dependabot-prs.yml b/.github/workflows/mark-duplicate-dependabot-prs.yml index 8dcb5ffd19d..95d1417612a 100644 --- a/.github/workflows/mark-duplicate-dependabot-prs.yml +++ b/.github/workflows/mark-duplicate-dependabot-prs.yml @@ -5,6 +5,14 @@ on: types: [closed] jobs: + debug: + runs-on: ubuntu-latest + steps: + - name: Debug Event Payload + run: | + echo "Merged: ${{ github.event.pull_request.merged }}" + echo "User Login: ${{ github.event.pull_request.user.login }}" + check_duplicate_prs: runs-on: ubuntu-latest if: github.event.pull_request.merged == 'true' && github.event.pull_request.user.login == 'dependabot[bot]' From 148e7843bffe4f5990d3ce9277331d19b3bcae0e Mon Sep 17 00:00:00 2001 From: Seungrae Date: Wed, 3 Jul 2024 22:16:11 +0900 Subject: [PATCH 48/68] Fix typos and formatting in documentation --- .../ROOT/pages/servlet/authorization/method-security.adoc | 2 +- .../pages/servlet/saml2/login/authentication-requests.adoc | 6 +++--- docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index fdd3a7dae38..1a37ae93a9e 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -240,7 +240,7 @@ static RoleHierarchy roleHierarchy() { Kotlin:: + -[source,java,role="secondary"] +[source,kotlin,role="secondary"] ---- companion object { @Bean diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc index 76f4a236146..4e0ec21d32e 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc @@ -87,7 +87,7 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit Kotlin:: + -[source,java,role="secondary"] +[source,kotlin,role="secondary"] ---- var relyingPartyRegistration: RelyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta") @@ -96,7 +96,7 @@ var relyingPartyRegistration: RelyingPartyRegistration = // ... .wantAuthnRequestsSigned(false) } - .build(); + .build() ---- ====== @@ -141,7 +141,7 @@ var relyingPartyRegistration: RelyingPartyRegistration = ) } } - .build(); + .build() ---- ====== diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc index 0721d9b15eb..c29f7a35a7f 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc @@ -1000,12 +1000,12 @@ Collection registrations = RelyingPartyRegistrations .entityId("https://example.org/saml2/sp") .build() ) - .collect(Collectors.toList())); + .collect(Collectors.toList()); ---- Kotlin:: + -[source,java,role="secondary"] +[source,kotlin,role="secondary"] ---- var registrations: Collection = RelyingPartyRegistrations .collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml") @@ -1015,7 +1015,7 @@ var registrations: Collection = RelyingPartyRegistrati .assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso") .build() } - .collect(Collectors.toList())); + .collect(Collectors.toList()) ---- ====== From f4cbaaa2dd14bedb8d1d9ca6c11e047bf4a36f05 Mon Sep 17 00:00:00 2001 From: Seungrae Date: Wed, 3 Jul 2024 22:16:11 +0900 Subject: [PATCH 49/68] Fix typos and formatting in documentation Closes gh-15353 --- .../ROOT/pages/servlet/authorization/method-security.adoc | 2 +- .../pages/servlet/saml2/login/authentication-requests.adoc | 6 +++--- docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 5a3c49171e3..23dcbc5a2de 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -239,7 +239,7 @@ static RoleHierarchy roleHierarchy() { Kotlin:: + -[source,java,role="secondary"] +[source,kotlin,role="secondary"] ---- companion object { @Bean diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc index 76f4a236146..4e0ec21d32e 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc @@ -87,7 +87,7 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit Kotlin:: + -[source,java,role="secondary"] +[source,kotlin,role="secondary"] ---- var relyingPartyRegistration: RelyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta") @@ -96,7 +96,7 @@ var relyingPartyRegistration: RelyingPartyRegistration = // ... .wantAuthnRequestsSigned(false) } - .build(); + .build() ---- ====== @@ -141,7 +141,7 @@ var relyingPartyRegistration: RelyingPartyRegistration = ) } } - .build(); + .build() ---- ====== diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc index 2ab0b7554c2..7b44f2ed8c1 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc @@ -949,12 +949,12 @@ Collection registrations = RelyingPartyRegistrations .entityId("https://example.org/saml2/sp") .build() ) - .collect(Collectors.toList())); + .collect(Collectors.toList()); ---- Kotlin:: + -[source,java,role="secondary"] +[source,kotlin,role="secondary"] ---- var registrations: Collection = RelyingPartyRegistrations .collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml") @@ -964,7 +964,7 @@ var registrations: Collection = RelyingPartyRegistrati .assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso") .build() } - .collect(Collectors.toList())); + .collect(Collectors.toList()) ---- ====== From 527f816ff84696b0fc3736eb94e0cf7a94e70fcb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 04:01:17 +0000 Subject: [PATCH 50/68] Bump io.micrometer:micrometer-observation from 1.12.7 to 1.12.8 Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.7 to 1.12.8. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.7...v1.12.8) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-observation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8deae714118..4caaa768311 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebser com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" } com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" commons-collections = "commons-collections:commons-collections:3.2.2" -io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.7" +io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.8" io-mockk = "io.mockk:mockk:1.13.11" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } From b4f3966c79bc9e566f854f9647a238319fe9eb7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 03:58:44 +0000 Subject: [PATCH 51/68] Bump io.micrometer:micrometer-observation from 1.12.7 to 1.12.8 Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.7 to 1.12.8. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.7...v1.12.8) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-observation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fca0ecf74a4..63e3ccfdc62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebser com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" } com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" commons-collections = "commons-collections:commons-collections:3.2.2" -io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.7" +io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.8" io-mockk = "io.mockk:mockk:1.13.11" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } From 8f850f53e65710962e00df0bd9005d62631f9334 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 04:08:01 +0000 Subject: [PATCH 52/68] Bump io.micrometer:micrometer-observation from 1.12.7 to 1.12.8 Bumps [io.micrometer:micrometer-observation](https://github.com/micrometer-metrics/micrometer) from 1.12.7 to 1.12.8. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.12.7...v1.12.8) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-observation dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 42a80c64599..5a68a05166c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebser com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" } com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" commons-collections = "commons-collections:commons-collections:3.2.2" -io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.7" +io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.8" io-mockk = "io.mockk:mockk:1.13.11" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } From 01e1813020854c72fddcd0fd4d8a4a239d83d392 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Tue, 9 Jul 2024 08:16:06 -0300 Subject: [PATCH 53/68] Remove single quotes from boolean value Issue gh-14484 --- .github/workflows/mark-duplicate-dependabot-prs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mark-duplicate-dependabot-prs.yml b/.github/workflows/mark-duplicate-dependabot-prs.yml index 95d1417612a..ff4217a8ab7 100644 --- a/.github/workflows/mark-duplicate-dependabot-prs.yml +++ b/.github/workflows/mark-duplicate-dependabot-prs.yml @@ -15,7 +15,7 @@ jobs: check_duplicate_prs: runs-on: ubuntu-latest - if: github.event.pull_request.merged == 'true' && github.event.pull_request.user.login == 'dependabot[bot]' + if: github.event.pull_request.merged == true && github.event.pull_request.user.login == 'dependabot[bot]' steps: - name: Checkout Repository uses: actions/checkout@v4 From f184d130968d6532464cdd63fc5b6bc4e6305165 Mon Sep 17 00:00:00 2001 From: Antoine Rey Date: Thu, 4 Jul 2024 08:17:46 +0200 Subject: [PATCH 54/68] Update the OAuth2 jwt and opaque resource server documentation with the Lambda DSL The OAuth2ResourceServerConfigurer::opaqueToken() and ::jwt() methods are deprecated since Spring Security 6.1 --- .../ROOT/pages/servlet/oauth2/resource-server/jwt.adoc | 4 +++- .../servlet/oauth2/resource-server/opaque-token.adoc | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc index 87dc8458ec2..4296127a351 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc @@ -879,7 +879,9 @@ public class DirectlyConfiguredJwkSetUri { .requestMatchers("/messages/**").access(hasScope("messages")) .anyRequest().authenticated() ) - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt(Customizer.withDefaults()) + ); return http.build(); } } diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc index fcd3b4f2c4c..551ac360fd1 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc @@ -204,7 +204,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken); + .oauth2ResourceServer(oauth2 -> oauth2 + .opaqueToken(Customizer.withDefaults()) + ); return http.build(); } ---- @@ -564,7 +566,9 @@ public class MappedAuthorities { .requestMatchers("/messages/**").access(hasScope("messages")) .anyRequest().authenticated() ) - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken); + .oauth2ResourceServer(oauth2 -> oauth2 + .opaqueToken(Customizer.withDefaults()) + ); return http.build(); } } From d6874d90481a3dc0ba81c6c2af4082576603a9e8 Mon Sep 17 00:00:00 2001 From: Mateus Scheper Date: Tue, 2 Jul 2024 20:21:06 -0300 Subject: [PATCH 55/68] Fixing URL on README Changing URL from https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/ to https://docs.spring.io/spring-security/reference/ since the first one doesn't exist. --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 73d95c29f40..37fa14d836d 100644 --- a/README.adoc +++ b/README.adoc @@ -18,7 +18,7 @@ Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_COND See https://docs.spring.io/spring-security/reference/getting-spring-security.html[Getting Spring Security] for how to obtain Spring Security. == Documentation -Be sure to read the https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference]. +Be sure to read the https://docs.spring.io/spring-security/reference/[Spring Security Reference]. Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation]. == Quick Start From 5bd4db1a13011e50011b58c412dc1b566ae8988b Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 9 Jul 2024 13:23:24 -0500 Subject: [PATCH 56/68] Use javadoc macro Closes gh-15386 --- docs/antora-playbook.yml | 1 + docs/antora.yml | 2 +- docs/modules/ROOT/nav.adoc | 1 + .../authentication/password-storage.adoc | 2 +- .../features/integrations/cryptography.adoc | 12 ++--- .../concurrent-sessions-control.adoc | 12 ++--- .../oauth2/resource-server/bearer-tokens.adoc | 4 +- .../reactive/oauth2/resource-server/jwt.adoc | 2 +- .../oauth2/resource-server/opaque-token.adoc | 2 +- .../ROOT/pages/reactive/test/web/oauth2.adoc | 2 +- .../ROOT/pages/servlet/architecture.adoc | 10 ++--- .../servlet/authentication/architecture.adoc | 32 +++++++------- .../pages/servlet/authentication/jaas.adoc | 2 +- .../pages/servlet/authentication/logout.adoc | 32 +++++++------- .../authentication/passwords/basic.adoc | 10 ++--- .../passwords/credentials-container.adoc | 4 +- .../dao-authentication-provider.adoc | 2 +- .../authentication/passwords/digest.adoc | 2 +- .../authentication/passwords/form.adoc | 12 ++--- .../passwords/user-details-service.adoc | 2 +- .../passwords/user-details.adoc | 2 +- .../servlet/authentication/persistence.adoc | 16 +++---- .../servlet/authentication/rememberme.adoc | 2 +- .../authentication/session-management.adoc | 12 ++--- .../servlet/authorization/architecture.adoc | 4 +- .../authorize-http-requests.adoc | 26 +++++------ .../authorization/method-security.adoc | 44 +++++++++---------- .../ROOT/pages/servlet/exploits/csrf.adoc | 18 ++++---- .../ROOT/pages/servlet/exploits/firewall.adoc | 2 +- .../servlet/integrations/concurrency.adoc | 18 ++++---- .../pages/servlet/integrations/jackson.adoc | 8 ++-- .../oauth2/resource-server/bearer-tokens.adoc | 8 ++-- .../servlet/oauth2/resource-server/index.adoc | 2 +- .../servlet/oauth2/resource-server/jwt.adoc | 4 +- .../oauth2/resource-server/opaque-token.adoc | 4 +- .../pages/servlet/saml2/login/overview.adoc | 6 +-- .../ROOT/pages/servlet/saml2/logout.adoc | 10 ++--- .../pages/servlet/test/mockmvc/oauth2.adoc | 2 +- docs/package.json | 4 +- docs/spring-security-docs.gradle | 11 +++++ 40 files changed, 182 insertions(+), 169 deletions(-) diff --git a/docs/antora-playbook.yml b/docs/antora-playbook.yml index dbc3514c70f..78a6605fae3 100644 --- a/docs/antora-playbook.yml +++ b/docs/antora-playbook.yml @@ -24,6 +24,7 @@ asciidoc: extensions: - '@asciidoctor/tabs' - '@springio/asciidoctor-extensions' + - '@springio/asciidoctor-extensions/javadoc-extension' urls: latest_version_segment_strategy: redirect:to latest_version_segment: '' diff --git a/docs/antora.yml b/docs/antora.yml index 084191a21ee..571640b91d5 100644 --- a/docs/antora.yml +++ b/docs/antora.yml @@ -6,7 +6,7 @@ nav: ext: collector: run: - command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntoraYml + command: gradlew -q -PbuildSrc.skipTests=true :spring-security-docs:generateAntoraResources local: true scan: dir: ./build/generated-antora-resources diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 7b9aa9df149..7ba36aa76b5 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -8,6 +8,7 @@ * xref:migration/index.adoc[Migrating to 6.2] ** xref:migration/authorization.adoc[Authorization Changes] * xref:getting-spring-security.adoc[Getting Spring Security] +* xref:attachment$api/java/index.html[Javadoc] * xref:features/index.adoc[Features] ** xref:features/authentication/index.adoc[Authentication] *** xref:features/authentication/password-storage.adoc[Password Storage] diff --git a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc index 2bb4006f502..b6d217dd854 100644 --- a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc +++ b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc @@ -598,7 +598,7 @@ With the above configuration, when a password manager navigates to `/.well-known There are some scenarios where you need to check whether a password has been compromised, for example, if you are creating an application that deals with sensitive data, it is often needed that you perform some check on user's passwords in order to assert its reliability. One of these checks can be if the password has been compromised, usually because it has been found in a https://wikipedia.org/wiki/Data_breach[data breach]. -To facilitate that, Spring Security provides integration with the https://haveibeenpwned.com/API/v3#PwnedPasswords[Have I Been Pwned API] via the {security-api-url}org/springframework/security/core/password/HaveIBeenPwnedRestApiPasswordChecker.html[`HaveIBeenPwnedRestApiPasswordChecker` implementation] of the {security-api-url}org/springframework/security/core/password/CompromisedPasswordChecker.html[`CompromisedPasswordChecker` interface]. +To facilitate that, Spring Security provides integration with the https://haveibeenpwned.com/API/v3#PwnedPasswords[Have I Been Pwned API] via the javadoc:org.springframework.security.web.authentication.password.HaveIBeenPwnedRestApiPasswordChecker[] implementation of the javadoc:org.springframework.security.authentication.password.CompromisedPasswordChecker[] interface. You can either use the `CompromisedPasswordChecker` API by yourself or, if you are using xref:servlet/authentication/passwords/dao-authentication-provider.adoc[the `DaoAuthenticationProvider]` via xref:servlet/authentication/passwords/index.adoc[Spring Security authentication mechanisms], you can provide a `CompromisedPasswordChecker` bean, and it will be automatically picked up by Spring Security configuration. diff --git a/docs/modules/ROOT/pages/features/integrations/cryptography.adoc b/docs/modules/ROOT/pages/features/integrations/cryptography.adoc index 01c78af0bc2..01c71c36f08 100644 --- a/docs/modules/ROOT/pages/features/integrations/cryptography.adoc +++ b/docs/modules/ROOT/pages/features/integrations/cryptography.adoc @@ -8,9 +8,9 @@ The code is distributed as part of the core module but has no dependencies on an [[spring-security-crypto-encryption]] == Encryptors -The {security-api-url}org/springframework/security/crypto/encrypt/Encryptors.html[`Encryptors`] class provides factory methods for constructing symmetric encryptors. -This class lets you create {security-api-url}org/springframework/security/crypto/encrypt/BytesEncryptor.html[`BytesEncryptor`] instances to encrypt data in raw `byte[]` form. -You can also construct {security-api-url}org/springframework/security/crypto/encrypt/TextEncryptor.html[TextEncryptor] instances to encrypt text strings. +The javadoc:org.springframework.security.crypto.encrypt.Encryptors[] class provides factory methods for constructing symmetric encryptors. +This class lets you create javadoc:org.springframework.security.crypto.encrypt.BytesEncryptor[] instances to encrypt data in raw `byte[]` form. +You can also construct javadoc:org.springframework.security.crypto.encrypt.TextEncryptor[] instances to encrypt text strings. Encryptors are thread-safe. [NOTE] @@ -101,9 +101,9 @@ Encrypted results are returned as hex-encoded strings for easy storage on the fi [[spring-security-crypto-keygenerators]] == Key Generators -The {security-api-url}org/springframework/security/crypto/keygen/KeyGenerators.html[`KeyGenerators`] class provides a number of convenience factory methods for constructing different types of key generators. -By using this class, you can create a {security-api-url}org/springframework/security/crypto/keygen/BytesKeyGenerator.html[`BytesKeyGenerator`] to generate `byte[]` keys. -You can also construct a {security-api-url}org/springframework/security/crypto/keygen/StringKeyGenerator.html`[StringKeyGenerator]` to generate string keys. +The javadoc:org.springframework.security.crypto.keygen.KeyGenerators[] class provides a number of convenience factory methods for constructing different types of key generators. +By using this class, you can create a javadoc:org.springframework.security.crypto.keygen.BytesKeyGenerator[] to generate `byte[]` keys. +You can also construct a javadoc:org.springframework.security.crypto.keygen.StringKeyGenerator[] to generate string keys. `KeyGenerators` is a thread-safe class. === BytesKeyGenerator diff --git a/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc b/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc index 7490c354fc4..3e4e436d34a 100644 --- a/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc +++ b/docs/modules/ROOT/pages/reactive/authentication/concurrent-sessions-control.adoc @@ -4,7 +4,7 @@ Similar to xref:servlet/authentication/session-management.adoc#ns-concurrent-sessions[Servlet's Concurrent Sessions Control], Spring Security also provides support to limit the number of concurrent sessions a user can have in a Reactive application. When you set up Concurrent Sessions Control in Spring Security, it monitors authentications carried out through Form Login, xref:reactive/oauth2/login/index.adoc[OAuth 2.0 Login], and HTTP Basic authentication by hooking into the way those authentication mechanisms handle authentication success. -More specifically, the session management DSL will add the {security-api-url}org/springframework/security/web/server/authentication/ConcurrentSessionControlServerAuthenticationSuccessHandler.html[ConcurrentSessionControlServerAuthenticationSuccessHandler] and the {security-api-url}org/springframework/security/web/server/authentication/RegisterSessionServerAuthenticationSuccessHandler.html[RegisterSessionServerAuthenticationSuccessHandler] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter. +More specifically, the session management DSL will add the javadoc:org.springframework.security.web.server.authentication.ConcurrentSessionControlServerAuthenticationSuccessHandler[] and the javadoc:org.springframework.security.web.server.authentication.RegisterSessionServerAuthenticationSuccessHandler[] to the list of `ServerAuthenticationSuccessHandler` used by the authentication filter. The following sections contains examples of how to configure Concurrent Sessions Control. @@ -197,9 +197,9 @@ If you also need to invalidate the session against the Identity Provider you mus [[concurrent-sessions-control-custom-strategy]] == Handling Maximum Number of Sessions Exceeded -By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the {security-api-url}org/springframework/security/web/server/authentication/session/InvalidateLeastUsedMaximumSessionsExceededHandler.html[InvalidateLeastUsedMaximumSessionsExceededHandler]. -Spring Security also provides another implementation that prevents the user from creating new sessions by using the {security-api-url}org/springframework/security/web/server/authentication/session/PreventLoginMaximumSessionsExceededHandler.html[PreventLoginMaximumSessionsExceededHandler]. -If you want to use your own strategy, you can provide a different implementation of {security-api-url}org/springframework/security/web/server/authentication/session/ServerMaximumSessionsExceededHandler.html[ServerMaximumSessionsExceededHandler]. +By default, when the maximum number of sessions is exceeded, the least recently used session(s) will be expired by using the javadoc:org.springframework.security.web.server.authentication.InvalidateLeastUsedServerMaximumSessionsExceededHandler[]. +Spring Security also provides another implementation that prevents the user from creating new sessions by using the javadoc:org.springframework.security.web.server.authentication.PreventLoginServerMaximumSessionsExceededHandler[]. +If you want to use your own strategy, you can provide a different implementation of javadoc:org.springframework.security.web.server.authentication.ServerMaximumSessionsExceededHandler[]. .Configuring maximumSessionsExceededHandler [tabs] @@ -254,9 +254,9 @@ open fun reactiveSessionRegistry(): ReactiveSessionRegistry { [[reactive-concurrent-sessions-control-specify-session-registry]] == Specifying a `ReactiveSessionRegistry` -In order to keep track of the user's sessions, Spring Security uses a {security-api-url}org/springframework/security/core/session/ReactiveSessionRegistry.html[ReactiveSessionRegistry], and, every time a user logs in, their session information is saved. +In order to keep track of the user's sessions, Spring Security uses a javadoc:org.springframework.security.core.session.ReactiveSessionRegistry[], and, every time a user logs in, their session information is saved. -Spring Security ships with {security-api-url}org/springframework/security/core/session/InMemoryReactiveSessionRegistry.html[InMemoryReactiveSessionRegistry] implementation of `ReactiveSessionRegistry`. +Spring Security ships with javadoc:org.springframework.security.core.session.InMemoryReactiveSessionRegistry[] implementation of `ReactiveSessionRegistry`. To specify a `ReactiveSessionRegistry` implementation you can either declare it as a bean: diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc index 33362f61a2c..47905d6eb03 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc @@ -41,7 +41,7 @@ return http { == Bearer Token Propagation Now that you have a bearer token, you can pass that to downstream services. -This is possible with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`: +This is possible with javadoc:org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServerBearerExchangeFilterFunction[]: [tabs] ====== @@ -70,7 +70,7 @@ fun rest(): WebClient { ---- ====== -When the `WebClient` shown in the preceding example performs requests, Spring Security looks up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential. +When the `WebClient` shown in the preceding example performs requests, Spring Security looks up the current `Authentication` and extract any javadoc:org.springframework.security.oauth2.core.AbstractOAuth2Token[] credential. Then, it propagates that token in the `Authorization` header -- for example: [tabs] diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc index 3b291ed4bcb..b3d14fdb393 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc @@ -234,7 +234,7 @@ fun jwtDecoder(): ReactiveJwtDecoder { [NOTE] ==== -Calling `{security-api-url}org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.html#fromIssuerLocation-java.lang.String-[ReactiveJwtDecoders#fromIssuerLocation]` invokes the Provider Configuration or Authorization Server Metadata endpoint to derive the JWK Set URI. +Calling javadoc:org.springframework.security.oauth2.jwt.ReactiveJwtDecoders#fromIssuerLocation-java.lang.String-[ReactiveJwtDecoders#fromIssuerLocation] invokes the Provider Configuration or Authorization Server Metadata endpoint to derive the JWK Set URI. If the application does not expose a `ReactiveJwtDecoder` bean, Spring Boot exposes the above default one. ==== diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc index 8cab012346d..ccdcbf5e9a6 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc @@ -68,7 +68,7 @@ Given an Opaque Token, Resource Server: . Inspects the response for an `{ 'active' : true }` attribute. . Maps each scope to an authority with a prefix of `SCOPE_`. -By default, the resulting `Authentication#getPrincipal` is a Spring Security `{security-api-url}org/springframework/security/oauth2/core/OAuth2AuthenticatedPrincipal.html[OAuth2AuthenticatedPrincipal]` object, and `Authentication#getName` maps to the token's `sub` property, if one is present. +By default, the resulting `Authentication#getPrincipal` is a Spring Security javadoc:org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal[] object, and `Authentication#getName` maps to the token's `sub` property, if one is present. From here, you may want to jump to: diff --git a/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc b/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc index c43838c5469..2548d96cf50 100644 --- a/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc +++ b/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc @@ -956,7 +956,7 @@ client ---- ====== -You can also specify a complete `Jwt`, for which `{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder]` is quite handy: +You can also specify a complete `Jwt`, for which javadoc:org.springframework.security.oauth2.jwt.Jwt$Builder[] is quite handy: [tabs] ====== diff --git a/docs/modules/ROOT/pages/servlet/architecture.adoc b/docs/modules/ROOT/pages/servlet/architecture.adoc index 71b421e6d4b..657cbc552c1 100644 --- a/docs/modules/ROOT/pages/servlet/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/architecture.adoc @@ -118,7 +118,7 @@ image::{figures}/filterchainproxy.png[] [[servlet-securityfilterchain]] == SecurityFilterChain -{security-api-url}org/springframework/security/web/SecurityFilterChain.html[`SecurityFilterChain`] is used by <> to determine which Spring Security `Filter` instances should be invoked for the current request. +javadoc:org.springframework.security.web.SecurityFilterChain[] is used by <> to determine which Spring Security `Filter` instances should be invoked for the current request. The following image shows the role of `SecurityFilterChain`. @@ -392,7 +392,7 @@ public FilterRegistrationBean tenantFilterRegistration(TenantFilte == Handling Security Exceptions -The {security-api-url}org/springframework/security/web/access/ExceptionTranslationFilter.html[`ExceptionTranslationFilter`] allows translation of {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`] and {security-api-url}/org/springframework/security/core/AuthenticationException.html[`AuthenticationException`] into HTTP responses. +The javadoc:org.springframework.security.web.access.ExceptionTranslationFilter[] allows translation of javadoc:org.springframework.security.access.AccessDeniedException[] and javadoc:org.springframework.security.core.AuthenticationException[] into HTTP responses. `ExceptionTranslationFilter` is inserted into the <> as one of the <>. @@ -447,7 +447,7 @@ In Spring Security this is done by saving the `HttpServletRequest` using a <> uses the `RequestCache` to get the saved `HttpServletRequest` after the user authenticates, while the `ExceptionTranslationFilter` uses the `RequestCache` to save the `HttpServletRequest` after it detects `AuthenticationException`, before redirecting the user to the login endpoint. @@ -463,7 +463,7 @@ There are a number of reasons you may want to not store the user's unauthenticat You may want to offload that storage onto the user's browser or store it in a database. Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login. -To do that, you can use {security-api-url}org/springframework/security/web/savedrequest/NullRequestCache.html[the `NullRequestCache` implementation]. +To do that, you can use the javadoc:org.springframework.security.web.savedrequest.NullRequestCache[NullRequestCache] implementation. .Prevent the Request From Being Saved [tabs] @@ -517,7 +517,7 @@ XML:: [[requestcacheawarefilter]] === RequestCacheAwareFilter -The {security-api-url}org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html[`RequestCacheAwareFilter`] uses the <> to replay the original request. +The javadoc:org.springframework.security.web.savedrequest.RequestCacheAwareFilter[] uses the <> to replay the original request. [[servlet-logging]] == Logging diff --git a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc index 725fd46d7ec..4757b78ea8d 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc @@ -117,13 +117,13 @@ However, if you do, take a look at the JavaDoc for `SecurityContextHolder` to le [[servlet-authentication-securitycontext]] == SecurityContext -The {security-api-url}org/springframework/security/core/context/SecurityContext.html[`SecurityContext`] is obtained from the <>. +The javadoc:org.springframework.security.core.context.SecurityContext[] is obtained from the <>. The `SecurityContext` contains an <> object. [[servlet-authentication-authentication]] == Authentication -The {security-api-url}org/springframework/security/core/Authentication.html[`Authentication`] interface serves two main purposes within Spring Security: +The javadoc:org.springframework.security.core.Authentication[] interface serves two main purposes within Spring Security: * An input to <> to provide the credentials a user has provided to authenticate. When used in this scenario, `isAuthenticated()` returns `false`. @@ -141,7 +141,7 @@ Two examples are roles and scopes. [[servlet-authentication-granted-authority]] == GrantedAuthority -{security-api-url}org/springframework/security/core/GrantedAuthority.html[`GrantedAuthority`] instances are high-level permissions that the user is granted. +javadoc:org.springframework.security.core.GrantedAuthority[] instances are high-level permissions that the user is granted. Two examples are roles and scopes. You can obtain `GrantedAuthority` instances from the <> method. @@ -160,7 +160,7 @@ Of course, Spring Security is expressly designed to handle this common requireme [[servlet-authentication-authenticationmanager]] == AuthenticationManager -{security-api-url}org/springframework/security/authentication/AuthenticationManager.html[`AuthenticationManager`] is the API that defines how Spring Security's Filters perform xref:features/authentication/index.adoc#authentication[authentication]. +javadoc:org.springframework.security.authentication.AuthenticationManager[] is the API that defines how Spring Security's Filters perform xref:features/authentication/index.adoc#authentication[authentication]. The <> that is returned is then set on the <> by the controller (that is, by xref:servlet/architecture.adoc#servlet-security-filters[Spring Security's `Filters` instances]) that invoked the `AuthenticationManager`. If you are not integrating with Spring Security's `Filters` instances, you can set the `SecurityContextHolder` directly and are not required to use an `AuthenticationManager`. @@ -170,7 +170,7 @@ While the implementation of `AuthenticationManager` could be anything, the most [[servlet-authentication-providermanager]] == ProviderManager -{security-api-url}org/springframework/security/authentication/ProviderManager.html[`ProviderManager`] is the most commonly used implementation of <>. +javadoc:org.springframework.security.authentication.ProviderManager[] is the most commonly used implementation of <>. `ProviderManager` delegates to a `List` of <> instances. Each `AuthenticationProvider` has an opportunity to indicate that authentication should be successful, fail, or indicate it cannot make a decision and allow a downstream `AuthenticationProvider` to decide. If none of the configured `AuthenticationProvider` instances can authenticate, authentication fails with a `ProviderNotFoundException`, which is a special `AuthenticationException` that indicates that the `ProviderManager` was not configured to support the type of `Authentication` that was passed into it. @@ -200,19 +200,19 @@ If the `Authentication` contains a reference to an object in the cache (such as You need to take this into account if you use a cache. An obvious solution is to first make a copy of the object, either in the cache implementation or in the `AuthenticationProvider` that creates the returned `Authentication` object. Alternatively, you can disable the `eraseCredentialsAfterAuthentication` property on `ProviderManager`. -See the Javadoc for the {security-api-url}org/springframework/security/authentication/ProviderManager.html[ProviderManager] class. +See the Javadoc for the javadoc:org.springframework.security.authentication.ProviderManager[] class. [[servlet-authentication-authenticationprovider]] == AuthenticationProvider -You can inject multiple {security-api-url}org/springframework/security/authentication/AuthenticationProvider.html[``AuthenticationProvider``s] instances into <>. +You can inject multiple javadoc:org.springframework.security.authentication.AuthenticationProvider[] instances into <>. Each `AuthenticationProvider` performs a specific type of authentication. For example, xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] supports username/password-based authentication, while `JwtAuthenticationProvider` supports authenticating a JWT token. [[servlet-authentication-authenticationentrypoint]] == Request Credentials with `AuthenticationEntryPoint` -{security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`] is used to send an HTTP response that requests credentials from a client. +javadoc:org.springframework.security.web.AuthenticationEntryPoint[] is used to send an HTTP response that requests credentials from a client. Sometimes, a client proactively includes credentials (such as a username and password) to request a resource. In these cases, Spring Security does not need to provide an HTTP response that requests credentials from the client, since they are already included. @@ -229,7 +229,7 @@ The `AuthenticationEntryPoint` implementation might perform a xref:servlet/authe [[servlet-authentication-abstractprocessingfilter]] == AbstractAuthenticationProcessingFilter -{security-api-url}org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html[`AbstractAuthenticationProcessingFilter`] is used as a base `Filter` for authenticating a user's credentials. +javadoc:org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter[] is used as a base `Filter` for authenticating a user's credentials. Before the credentials can be authenticated, Spring Security typically requests the credentials by using <>. Next, the `AbstractAuthenticationProcessingFilter` can authenticate any authentication requests that are submitted to it. @@ -245,26 +245,26 @@ image:{icondir}/number_2.png[] Next, the <> is cleared out. -* `RememberMeServices.loginFail` is invoked. +* `RememberMeServices.loginFail` is invoked.ƒ If remember me is not configured, this is a no-op. -See the {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[`rememberme`] package. +See the javadoc:org.springframework.security.web.authentication.rememberme.package-summary[rememberme] package. * `AuthenticationFailureHandler` is invoked. -See the {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] interface. +See the javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] interface. image:{icondir}/number_4.png[] If authentication is successful, then __Success__. * `SessionAuthenticationStrategy` is notified of a new login. -See the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface. +See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface. * The <> is set on the <>. Later, if you need to save the `SecurityContext` so that it can be automatically set on future requests, `SecurityContextRepository#saveContext` must be explicitly invoked. -See the {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[`SecurityContextHolderFilter`] class. +See the javadoc:org.springframework.security.web.context.SecurityContextHolderFilter[] class. * `RememberMeServices.loginSuccess` is invoked. If remember me is not configured, this is a no-op. -See the {security-api-url}org/springframework/security/web/authentication/rememberme/package-frame.html[`rememberme`] package. +See the javadoc:org.springframework.security.web.authentication.rememberme.package-summary[rememberme] package. * `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`. * `AuthenticationSuccessHandler` is invoked. -See the {security-api-url}org/springframework/security/web/authentication/AuthenticationSuccessHandler.html[`AuthenticationSuccessHandler`] interface. +See the javadoc:org.springframework.security.web.authentication.AuthenticationSuccessHandler[] interface. // daoauthenticationprovider (goes in username/password) diff --git a/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc b/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc index 44d02db427b..bb2e7ced1b2 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc @@ -58,7 +58,7 @@ This means that `DefaultJaasAuthenticationProvider` is not bound to any particul To make it easy to inject a `Configuration` into `DefaultJaasAuthenticationProvider`, a default in-memory implementation named `InMemoryConfiguration` is provided. The implementation constructor accepts a `Map` where each key represents a login configuration name, and the value represents an `Array` of `AppConfigurationEntry` instances. `InMemoryConfiguration` also supports a default `Array` of `AppConfigurationEntry` objects that is used if no mapping is found within the provided `Map`. -For details, see the {security-api-url}org/springframework/security/authentication/jaas/memory/InMemoryConfiguration.html[Javadoc of `InMemoryConfiguration`]. +For details, see the Javadoc of javadoc:org.springframework.security.authentication.jaas.memory.InMemoryConfiguration[]. [[jaas-djap-config]] diff --git a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc index 91d669ce938..45289c2ed53 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc @@ -30,16 +30,16 @@ Please note that if xref:servlet/exploits/csrf.adoc[CSRF protection] is disabled In your application it is not necessary to use `GET /logout` to perform a logout. So long as xref:servlet/exploits/csrf.adoc[the needed CSRF token] is present in the request, your application can simply `POST /logout` to induce a logout. -If you request `POST /logout`, then it will perform the following default operations using a series of {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s: +If you request `POST /logout`, then it will perform the following default operations using a series of javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] instances: -- Invalidate the HTTP session ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`]) -- Clear the xref:servlet/authentication/session-management.adoc#use-securitycontextholderstrategy[`SecurityContextHolderStrategy`] ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`]) -- Clear the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] ({security-api-url}org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`]) +- Invalidate the HTTP session (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[]) +- Clear the xref:servlet/authentication/session-management.adoc#use-securitycontextholderstrategy[`SecurityContextHolderStrategy`] (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[]) +- Clear the xref:servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`] (javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[]) - Clean up any xref:servlet/authentication/rememberme.adoc[RememberMe authentication] (`TokenRememberMeServices` / `PersistentTokenRememberMeServices`) -- Clear out any saved xref:servlet/exploits/csrf.adoc[CSRF token] ({security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`]) -- xref:servlet/authentication/events.adoc[Fire] a `LogoutSuccessEvent` ({security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessEventPublishingLogoutHandler.html[`LogoutSuccessEventPublishingLogoutHandler`]) +- Clear out any saved xref:servlet/exploits/csrf.adoc[CSRF token] (javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[]) +- xref:servlet/authentication/events.adoc[Fire] a `LogoutSuccessEvent` (javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler[]) -Once completed, then it will exercise its default {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] which redirects to `/login?logout`. +Once completed, then it will exercise its default javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] which redirects to `/login?logout`. [[customizing-logout-uris]] == Customizing Logout URIs @@ -197,15 +197,15 @@ http { ====== [NOTE] -Because {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s are for the purposes of cleanup, they should not throw exceptions. +Because javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] instances are for the purposes of cleanup, they should not throw exceptions. [TIP] -Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[`LogoutHandler`] is a functional interface, you can provide a custom one as a lambda. +Since javadoc:org.springframework.security.web.authentication.logout.LogoutHandler[] is a functional interface, you can provide a custom one as a lambda. Some logout handler configurations are common enough that they are exposed directly in the `logout` DSL and `` element. One example is configuring session invalidation and another is which additional cookies should be deleted. -For example, you can configure the {security-api-url}org/springframework/security/web/authentication/logout/CookieClearingLogoutHandler.html[`CookieClearingLogoutHandler`] as seen above. +For example, you can configure the javadoc:org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler[] as seen above. [[delete-cookies]] Or you can instead set the appropriate configuration value like so: @@ -242,7 +242,7 @@ Xml:: ====== [NOTE] -Specifying that the `JSESSIONID` cookie is not necessary since {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] removes it by virtue of invalidating the session. +Specifying that the `JSESSIONID` cookie is not necessary since javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] removes it by virtue of invalidating the session. [[clear-all-site-data]] === Using Clear-Site-Data to Log Out the User @@ -310,7 +310,7 @@ http { == Customizing Logout Success While using `logoutSuccessUrl` will suffice for most cases, you may need to do something different from redirecting to a URL once logout is complete. -{security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is the Spring Security component for customizing logout success actions. +javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] is the Spring Security component for customizing logout success actions. For example, instead of redirecting, you may want to only return a status code. In this case, you can provide a success handler instance, like so: @@ -349,7 +349,7 @@ Xml:: ====== [TIP] -Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is a functional interface, you can provide a custom one as a lambda. +Since javadoc:org.springframework.security.web.authentication.logout.LogoutSuccessHandler[] is a functional interface, you can provide a custom one as a lambda. [[creating-custom-logout-endpoint]] == Creating a Custom Logout Endpoint @@ -387,7 +387,7 @@ fun performLogout(): String { ---- ====== -then you will need to have that endpoint invoke Spring Security's {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] to ensure a secure and complete logout. +then you will need to have that endpoint invoke Spring Security's javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] to ensure a secure and complete logout. Something like the following is needed at a minimum: .Custom Logout Endpoint @@ -422,12 +422,12 @@ fun performLogout(val authentication: Authentication, val request: HttpServletRe ---- ====== -Such will clear out the {security-api-url}/org/springframework/security/core/context/SecurityContextHolderStrategy.html[`SecurityContextHolderStrategy`] and {security-api-url}/org/springframework/security/web/context/SecurityContextRepository.html[`SecurityContextRepository`] as needed. +Such will clear out the javadoc:org.springframework.security.core.context.SecurityContextHolderStrategy[] and javadoc:org.springframework.security.web.context.SecurityContextRepository[] as needed. Also, you'll need to <>. [WARNING] -Failing to call {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] means that xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[the `SecurityContext`] could still be available on subsequent requests, meaning that the user is not actually logged out. +Failing to call javadoc:org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler[] means that xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[the `SecurityContext`] could still be available on subsequent requests, meaning that the user is not actually logged out. [[testing-logout]] == Testing Logout diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc index aeecd5e60b6..a3d2a6a9f84 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc @@ -18,7 +18,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`. image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__. -The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/www/BasicAuthenticationEntryPoint.html[`BasicAuthenticationEntryPoint`], which sends a WWW-Authenticate header. +The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of javadoc:org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint[], which sends a WWW-Authenticate header. The `RequestCache` is typically a `NullRequestCache` that does not save the request since the client is capable of replaying the requests it originally requested. When a client receives the `WWW-Authenticate` header, it knows it should retry with a username and password. @@ -41,18 +41,18 @@ image:{icondir}/number_3.png[] If authentication fails, then __Failure__. . The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out. . `RememberMeServices.loginFail` is invoked. If remember me is not configured, this is a no-op. -See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc. +See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc. . `AuthenticationEntryPoint` is invoked to trigger the WWW-Authenticate to be sent again. -See the {security-api-url}org/springframework/security/web/AuthenticationEntryPoint.html[`AuthenticationEntryPoint`] interface in the Javadoc. +See the javadoc:org.springframework.security.web.AuthenticationEntryPoint[] interface in the Javadoc. image:{icondir}/number_4.png[] If authentication is successful, then __Success__. . The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder]. . `RememberMeServices.loginSuccess` is invoked. If remember me is not configured, this is a no-op. -See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc. +See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc. . The `BasicAuthenticationFilter` invokes `FilterChain.doFilter(request,response)` to continue with the rest of the application logic. -See the {security-api-url}org/springframework/security/web/authentication/www/BasicAuthenticationFilter.html[`BasicAuthenticationFilter`] Class in the Javadoc +See the javadoc:org.springframework.security.web.authentication.www.BasicAuthenticationFilter[] Class in the Javadoc By default, Spring Security's HTTP Basic Authentication support is enabled. However, as soon as any servlet based configuration is provided, HTTP Basic must be explicitly provided. diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc index b35795edc01..3f8d22da0ac 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/credentials-container.adoc @@ -1,8 +1,8 @@ [[servlet-authentication-credentialscontainer]] = CredentialsContainer -{security-api-url}org/springframework/security/core/CredentialsContainer.html[The `CredentialsContainer`] interface indicates that the implementing object contains sensitive data, and is used internally by Spring Security to erase the authentication credentials after a successful authentication. -This interface is implemented by most of Spring Security internal domain classes, like {security-api-url}org/springframework/security/core/userdetails/User.html[User] and {security-api-url}org/springframework/security/authentication/UsernamePasswordAuthenticationToken.html[UsernamePasswordAuthenticationToken]. +The javadoc:org.springframework.security.core.CredentialsContainer[] interface indicates that the implementing object contains sensitive data, and is used internally by Spring Security to erase the authentication credentials after a successful authentication. +This interface is implemented by most of Spring Security internal domain classes, like javadoc:org.springframework.security.core.userdetails.User[] and javadoc:org.springframework.security.authentication.UsernamePasswordAuthenticationToken[]. The `ProviderManager` manager checks whether the returned `Authentication` implements this interface. If so, xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager-erasing-credentials[it calls the `eraseCredentials` method] to remove the credentials from the object. diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc index 1366308c7c6..ba27f541a2e 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc @@ -2,7 +2,7 @@ = DaoAuthenticationProvider :figures: servlet/authentication/unpwd -{security-api-url}org/springframework/security/authentication/dao/DaoAuthenticationProvider.html[`DaoAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that uses a xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] and xref:servlet/authentication/passwords/password-encoder.adoc#servlet-authentication-password-storage[`PasswordEncoder`] to authenticate a username and password. +javadoc:org.springframework.security.authentication.dao.DaoAuthenticationProvider[] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that uses a xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] and xref:servlet/authentication/passwords/password-encoder.adoc#servlet-authentication-password-storage[`PasswordEncoder`] to authenticate a username and password. This section examines how `DaoAuthenticationProvider` works within Spring Security. The following figure explains the workings of the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from the xref:servlet/authentication/passwords/index.adoc#servlet-authentication-unpwd-input[Reading the Username & Password] section. diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc index 962a7523da0..e4af5ebeaf8 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc @@ -33,7 +33,7 @@ key: A private key to prevent modification of the nonce token ---- You need to ensure that you xref:features/authentication/password-storage.adoc#authentication-password-storage-configuration[configure] insecure plain text xref:features/authentication/password-storage.adoc#authentication-password-storage[Password Storage] using `NoOpPasswordEncoder`. -(See the {security-api-url}org/springframework/security/crypto/password/NoOpPasswordEncoder.html[`NoOpPasswordEncoder`] class in the Javadoc.) +(See the javadoc:org.springframework.security.crypto.password.NoOpPasswordEncoder[] class in the Javadoc.) The following provides an example of configuring Digest Authentication with Java Configuration: .Digest Authentication diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc index 91f211e3e64..207ddb76549 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc @@ -19,7 +19,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`. image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__ and sends a redirect to the login page with the configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`]. -In most cases, the `AuthenticationEntryPoint` is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`]. +In most cases, the `AuthenticationEntryPoint` is an instance of javadoc:org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint[]. image:{icondir}/number_4.png[] The browser requests the login page to which it was redirected. @@ -45,19 +45,19 @@ image:{icondir}/number_3.png[] If authentication fails, then __Failure__. . The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out. . `RememberMeServices.loginFail` is invoked. If remember me is not configured, this is a no-op. -See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc. +See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc. . `AuthenticationFailureHandler` is invoked. -See the {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] class in the Javadoc +See the javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] class in the Javadoc image:{icondir}/number_4.png[] If authentication is successful, then __Success__. . `SessionAuthenticationStrategy` is notified of a new login. -See the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface in the Javadoc. +See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface in the Javadoc. . The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder]. -See the {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] class in the Javadoc. +See the javadoc:org.springframework.security.web.context.SecurityContextPersistenceFilter[] class in the Javadoc. . `RememberMeServices.loginSuccess` is invoked. If remember me is not configured, this is a no-op. -See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc. +See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc. . `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`. . The `AuthenticationSuccessHandler` is invoked. Typically, this is a `SimpleUrlAuthenticationSuccessHandler`, which redirects to a request saved by xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] when we redirect to the login page. diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc index af6ed15daa5..fcfd20c7ece 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc @@ -1,7 +1,7 @@ [[servlet-authentication-userdetailsservice]] = UserDetailsService -{security-api-url}org/springframework/security/core/userdetails/UserDetailsService.html[`UserDetailsService`] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password. +javadoc:org.springframework.security.core.userdetails.UserDetailsService[] is used by xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] for retrieving a username, a password, and other attributes for authenticating with a username and password. Spring Security provides xref:servlet/authentication/passwords/in-memory.adoc#servlet-authentication-inmemory[in-memory], xref:servlet/authentication/passwords/jdbc.adoc#servlet-authentication-jdbc[JDBC], and xref:servlet/authentication/passwords/caching.adoc#servlet-authentication-caching-user-details[caching] implementations of `UserDetailsService`. You can define custom authentication by exposing a custom `UserDetailsService` as a bean. diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details.adoc index 34918cb455a..292f119018a 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details.adoc @@ -1,5 +1,5 @@ [[servlet-authentication-userdetails]] = UserDetails -{security-api-url}org/springframework/security/core/userdetails/UserDetails.html[`UserDetails`] is returned by the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`]. +javadoc:org.springframework.security.core.userdetails.UserDetails[] is returned by the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`]. The xref:servlet/authentication/passwords/dao-authentication-provider.adoc#servlet-authentication-daoauthenticationprovider[`DaoAuthenticationProvider`] validates the `UserDetails` and then returns an xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that has a principal that is the `UserDetails` returned by the configured `UserDetailsService`. diff --git a/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc b/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc index 1528d66787b..0a43f504664 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc @@ -59,8 +59,8 @@ Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8 == SecurityContextRepository // FIXME: api documentation -In Spring Security the association of the user to future requests is made using {security-api-url}org/springframework/security/web/context/SecurityContextRepository.html[`SecurityContextRepository`]. -The default implementation of `SecurityContextRepository` is {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[`DelegatingSecurityContextRepository`] which delegates to the following: +In Spring Security the association of the user to future requests is made using javadoc:org.springframework.security.web.context.SecurityContextRepository[]. +The default implementation of `SecurityContextRepository` is javadoc:org.springframework.security.web.context.DelegatingSecurityContextRepository[] which delegates to the following: * <> * <> @@ -68,18 +68,18 @@ The default implementation of `SecurityContextRepository` is {security-api-url}o [[httpsecuritycontextrepository]] === HttpSessionSecurityContextRepository -The {security-api-url}org/springframework/security/web/context/HttpSessionSecurityContextRepository.html[`HttpSessionSecurityContextRepository`] associates the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to the `HttpSession`. +The javadoc:org.springframework.security.web.context.HttpSessionSecurityContextRepository[] associates the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontext[`SecurityContext`] to the `HttpSession`. Users can replace `HttpSessionSecurityContextRepository` with another implementation of `SecurityContextRepository` if they wish to associate the user with subsequent requests in another way or not at all. [[nullsecuritycontextrepository]] === NullSecurityContextRepository -If it is not desirable to associate the `SecurityContext` to an `HttpSession` (i.e. when authenticating with OAuth) the {security-api-url}org/springframework/security/web/context/NullSecurityContextRepository.html[`NullSecurityContextRepository`] is an implementation of `SecurityContextRepository` that does nothing. +If it is not desirable to associate the `SecurityContext` to an `HttpSession` (i.e. when authenticating with OAuth) the javadoc:org.springframework.security.web.context.NullSecurityContextRepository[] is an implementation of `SecurityContextRepository` that does nothing. [[requestattributesecuritycontextrepository]] === RequestAttributeSecurityContextRepository -The {security-api-url}org/springframework/security/web/context/RequestAttributeSecurityContextRepository.html[`RequestAttributeSecurityContextRepository`] saves the `SecurityContext` as a request attribute to make sure the `SecurityContext` is available for a single request that occurs across dispatch types that may clear out the `SecurityContext`. +The javadoc:org.springframework.security.web.context.RequestAttributeSecurityContextRepository[] saves the `SecurityContext` as a request attribute to make sure the `SecurityContext` is available for a single request that occurs across dispatch types that may clear out the `SecurityContext`. For example, assume that a client makes a request, is authenticated, and then an error occurs. Depending on the servlet container implementation, the error means that any `SecurityContext` that was established is cleared out and then the error dispatch is made. @@ -118,7 +118,7 @@ XML:: [[delegatingsecuritycontextrepository]] === DelegatingSecurityContextRepository -The {security-api-url}org/springframework/security/web/context/DelegatingSecurityContextRepository.html[`DelegatingSecurityContextRepository`] saves the `SecurityContext` to multiple `SecurityContextRepository` delegates and allows retrieval from any of the delegates in a specified order. +The javadoc:org.springframework.security.web.context.DelegatingSecurityContextRepository[] saves the `SecurityContext` to multiple `SecurityContextRepository` delegates and allows retrieval from any of the delegates in a specified order. The most useful arrangement for this is configured with the following example, which allows the use of both xref:requestattributesecuritycontextrepository[`RequestAttributeSecurityContextRepository`] and xref:httpsecuritycontextrepository[`HttpSessionSecurityContextRepository`] simultaneously. @@ -189,7 +189,7 @@ In Spring Security 6, the example shown above is the default configuration. [[securitycontextpersistencefilter]] == SecurityContextPersistenceFilter -The {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] is responsible for persisting the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`]. +The javadoc:org.springframework.security.web.context.SecurityContextPersistenceFilter[] is responsible for persisting the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`]. image::{figures}/securitycontextpersistencefilter.png[] @@ -210,7 +210,7 @@ To avoid these problems, the `SecurityContextPersistenceFilter` wraps both the ` [[securitycontextholderfilter]] == SecurityContextHolderFilter -The {security-api-url}org/springframework/security/web/context/SecurityContextHolderFilter.html[`SecurityContextHolderFilter`] is responsible for loading the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`]. +The javadoc:org.springframework.security.web.context.SecurityContextHolderFilter[] is responsible for loading the `SecurityContext` between requests using the xref::servlet/authentication/persistence.adoc#securitycontextrepository[`SecurityContextRepository`]. image::{figures}/securitycontextholderfilter.png[] diff --git a/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc b/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc index 4b5ec52830d..3e8e24199f7 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc @@ -89,7 +89,7 @@ void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication); ---- -See the Javadoc for {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] for a fuller discussion on what the methods do, although note that, at this stage, `AbstractAuthenticationProcessingFilter` calls only the `loginFail()` and `loginSuccess()` methods. +See the Javadoc for javadoc:org.springframework.security.web.authentication.RememberMeServices[] for a fuller discussion on what the methods do, although note that, at this stage, `AbstractAuthenticationProcessingFilter` calls only the `loginFail()` and `loginSuccess()` methods. The `autoLogin()` method is called by `RememberMeAuthenticationFilter` whenever the `SecurityContextHolder` does not contain an `Authentication`. This interface, therefore, provides the underlying remember-me implementation with sufficient notification of authentication-related events and delegates to the implementation whenever a candidate web request might contain a cookie and wish to be remembered. This design allows any number of remember-me implementation strategies. diff --git a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc index f269c960bc6..fe0821a31df 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc @@ -49,7 +49,7 @@ The latter is also used when configuring an invalid session URL through the name [[moving-away-from-sessionmanagementfilter]] ==== Moving Away From `SessionManagementFilter` -In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[the `SessionAuthenticationStrategy`]. +In Spring Security 5, the default configuration relies on `SessionManagementFilter` to detect if a user just authenticated and invoke the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[SessionAuthenticationStrategy]. The problem with this is that it means that in a typical setup, the `HttpSession` must be read for every request. In Spring Security 6, the default is that authentication mechanisms themselves must invoke the `SessionAuthenticationStrategy`. @@ -63,10 +63,10 @@ In Spring Security 6, the `SessionManagementFilter` is not used by default, ther |Method |Replacement |`sessionAuthenticationErrorUrl` -|Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism +|Configure an javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] in your authentication mechanism |`sessionAuthenticationFailureHandler` -|Configure an {security-api-url}/org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] in your authentication mechanism +|Configure an javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] in your authentication mechanism |`sessionAuthenticationStrategy` |Configure an `SessionAuthenticationStrategy` in your authentication mechanism as <> @@ -589,8 +589,8 @@ If that is your case, you might want to < Allow access to URLs that start with `/admin/` to users with the `ADMIN` role <4> Any other request that doesn't match the rules above, will require authentication -The `securityMatcher(s)` and `requestMatcher(s)` methods will decide which `RequestMatcher` implementation fits best for your application: If {spring-framework-reference-url}web.html#spring-web[Spring MVC] is in the classpath, then {security-api-url}org/springframework/security/web/servlet/util/matcher/MvcRequestMatcher.html[`MvcRequestMatcher`] will be used, otherwise, {security-api-url}org/springframework/security/web/servlet/util/matcher/AntPathRequestMatcher.html[`AntPathRequestMatcher`] will be used. +The `securityMatcher(s)` and `requestMatcher(s)` methods will decide which `RequestMatcher` implementation fits best for your application: If {spring-framework-reference-url}web.html#spring-web[Spring MVC] is in the classpath, then javadoc:org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher[] will be used, otherwise, javadoc:org.springframework.security.web.util.matcher.AntPathRequestMatcher[] will be used. You can read more about the Spring MVC integration xref:servlet/integrations/mvc.adoc[here]. If you want to use a specific `RequestMatcher`, just pass an implementation to the `securityMatcher` and/or `requestMatcher` methods: diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 1a37ae93a9e..ff74f7ef2ef 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -117,15 +117,15 @@ A given invocation to `MyCustomerService#readCustomer` may look something like t image::{figures}/methodsecurity.png[] -1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor`] that matches <> -2. The interceptor invokes {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager#check`] +1. Spring AOP invokes its proxy method for `readCustomer`. Among the proxy's other advisors, it invokes an javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[] that matches <> +2. The interceptor invokes javadoc:org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager[`PreAuthorizeAuthorizationManager#check`] 3. The authorization manager uses a `MethodSecurityExpressionHandler` to parse the annotation's <> and constructs a corresponding `EvaluationContext` from a `MethodSecurityExpressionRoot` containing xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[a `Supplier`] and `MethodInvocation`. 4. The interceptor uses this context to evaluate the expression; specifically, it reads xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[the `Authentication`] from the `Supplier` and checks whether it has `permission:read` in its collection of xref:servlet/authorization/architecture.adoc#authz-authorities[authorities] 5. If the evaluation passes, then Spring AOP proceeds to invoke the method. -6. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`] which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response -7. After the method returns, Spring AOP invokes an {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthorizationManagerAfterMethodInterceptor`] that matches <>, operating the same as above, but with {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`] +6. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an javadoc:org.springframework.security.access.AccessDeniedException[] which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response +7. After the method returns, Spring AOP invokes an javadoc:org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor[] that matches <>, operating the same as above, but with javadoc:org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager[] 8. If the evaluation passes (in this case, the return value belongs to the logged-in user), processing continues normally -9. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`], which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response +9. If not, the interceptor publishes an `AuthorizationDeniedEvent` and throws an javadoc:org.springframework.security.access.AccessDeniedException[], which xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[the `ExceptionTranslationFilter`] catches and returns a 403 status code to the response [NOTE] If the method is not being called in the context of an HTTP request, you will likely need to handle the `AccessDeniedException` yourself @@ -150,7 +150,7 @@ Instead, use SpEL's boolean support or its support for delegating to a separate Each annotation has its own pointcut instance that looks for that annotation or its <> counterparts across the entire object hierarchy, starting at <>. -You can see the specifics of this in {security-api-url}org/springframework/security/authorization/method/AuthorizationMethodPointcuts.html[`AuthorizationMethodPointcuts`]. +// FIXME: AuthorizationMethodPointcuts is package private and Javadoc is not published You can see the specifics of this in javadoc:org.springframework.security.authorization.method.AuthorizationMethodPointcuts[]. [[annotation-method-interceptors]] === Each Annotation Has Its Own Method Interceptor @@ -161,12 +161,12 @@ For example, if needed, you can disable the Spring Security defaults and <<_enab The method interceptors are as follows: -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.html[`PreAuthorizeAuthorizationManager`] -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptor.html[`AuthorizationManagerAfterMethodInterceptor#postAuthorize`], which in turn uses {security-api-url}org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.html[`PostAuthorizeAuthorizationManager`] -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.html[`PreFilterAuthorizationMethodInterceptor`] -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.html[`PostFilterAuthorizationMethodInterceptor`] -* For <>, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#secured`], which in turn uses {security-api-url}org/springframework/security/authorization/method/SecuredAuthorizationManager.html[`SecuredAuthorizationManager`] -* For JSR-250 annotations, Spring Security uses {security-api-url}org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptor.html[`AuthorizationManagerBeforeMethodInterceptor#jsr250`], which in turn uses {security-api-url}org/springframework/security/authorization/method/Jsr250AuthorizationManager.html[`Jsr250AuthorizationManager`] +* For <>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#preAuthorize`], which in turn uses javadoc:org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager[] +* For <>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor[`AuthorizationManagerAfterMethodInterceptor#postAuthorize`], which in turn uses javadoc:org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager[] +* For <>, Spring Security uses javadoc:org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor[] +* For <>, Spring Security uses javadoc:org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor[] +* For <>, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#secured`], which in turn uses javadoc:org.springframework.security.authorization.method.SecuredAuthorizationManager[] +* For JSR-250 annotations, Spring Security uses javadoc:org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor[`AuthorizationManagerBeforeMethodInterceptor#jsr250`], which in turn uses javadoc:org.springframework.security.authorization.method.Jsr250AuthorizationManager[] Generally speaking, you can consider the following listing as representative of what interceptors Spring Security publishes when you add `@EnableMethodSecurity`: @@ -311,7 +311,7 @@ The primary way Spring Security enables method-level authorization support is th [[use-preauthorize]] === Authorizing Method Invocation with `@PreAuthorize` -When <>, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PreAuthorize.html[`@PreAuthorize`] annotation like so: +When <>, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PreAuthorize[format=annotation] annotation like so: [tabs] ====== @@ -399,7 +399,7 @@ While `@PreAuthorize` is quite helpful for declaring needed authorities, it can [[use-postauthorize]] === Authorization Method Results with `@PostAuthorize` -When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PostAuthorize.html[`@PostAuthorize`] annotation like so: +When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PostAuthorize[format=annotation] annotation like so: [tabs] ====== @@ -546,7 +546,7 @@ If not, Spring Security will throw an `AccessDeniedException` and return a 403 s [[use-prefilter]] === Filtering Method Parameters with `@PreFilter` -When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PreFilter.html[`@PreFilter`] annotation like so: +When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PreFilter[format=annotation] annotation like so: [tabs] ====== @@ -670,7 +670,7 @@ The result is that the above method will only have the `Account` instances where [[use-postfilter]] === Filtering Method Results with `@PostFilter` -When Method Security is active, you can annotate a method with the {security-api-url}org/springframework/security/access/prepost/PostFilter.html[`@PostFilter`] annotation like so: +When Method Security is active, you can annotate a method with the javadoc:org.springframework.security.access.prepost.PostFilter[format=annotation] annotation like so: [tabs] ====== @@ -795,7 +795,7 @@ In-memory filtering can obviously be expensive, and so be considerate of whether [[use-secured]] === Authorizing Method Invocation with `@Secured` -{security-api-url}org/springframework/security/access/annotation/Secured.html[`@Secured`] is a legacy option for authorizing invocations. +javadoc:org.springframework.security.access.annotation.Secured[format=annotation] is a legacy option for authorizing invocations. <> supercedes it and is recommended instead. To use the `@Secured` annotation, you should first change your Method Security declaration to enable it like so: @@ -1475,7 +1475,7 @@ You can place your interceptor in between Spring Security method interceptors us === Customizing Expression Handling Or, third, you can customize how each SpEL expression is handled. -To do that, you can expose a custom {security-api-url}org.springframework.security.access.expression.method.MethodSecurityExpressionHandler.html[`MethodSecurityExpressionHandler`], like so: +To do that, you can expose a custom javadoc:org.springframework.security.access.expression.method.MethodSecurityExpressionHandler[], like so: .Custom MethodSecurityExpressionHandler [tabs] @@ -2357,8 +2357,8 @@ You can also add the Spring Boot property `spring.jackson.default-property-inclu There are some scenarios where you may not wish to throw an `AuthorizationDeniedException` when a method is invoked without the required permissions. Instead, you might wish to return a post-processed result, like a masked result, or a default value in cases where authorization denied happened before invoking the method. -Spring Security provides support for handling authorization denied on method invocation by using the {security-api-url}org/springframework/security/authorization/method/HandleAuthorizationDenied.html[`@HandleAuthorizationDenied`]. -The handler works for denied authorizations that happened in the <> as well as {security-api-url}org/springframework/security/authorization/AuthorizationDeniedException.html[`AuthorizationDeniedException`] thrown from the method invocation itself. +Spring Security provides support for handling authorization denied on method invocation by using the javadoc:org.springframework.security.authorization.method.HandleAuthorizationDenied[format=annotation]. +The handler works for denied authorizations that happened in the <> as well as javadoc:org.springframework.security.authorization.AuthorizationDeniedException[] thrown from the method invocation itself. Let's consider the example from the <>, but instead of creating the `AccessDeniedExceptionInterceptor` to transform an `AccessDeniedException` to a `null` return value, we will use the `handlerClass` attribute from `@HandleAuthorizationDenied`: @@ -2473,7 +2473,7 @@ fun getEmailWhenProxiedThenNullEmail() { There are some scenarios where you might want to return a secure result derived from the denied result. For example, if a user is not authorized to see email addresses, you might want to apply some masking on the original email address, i.e. _useremail@example.com_ would become _use\\******@example.com_. -For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the {security-api-url}org/springframework/security/authorization/method/MethodInvocationResult.html[`MethodInvocationResult`] as an argument. +For those scenarios, you can override the `handleDeniedInvocationResult` from the `MethodAuthorizationDeniedHandler`, which has the javadoc:org.springframework.security.authorization.method.MethodInvocationResult[] as an argument. Let's continue with the previous example, but instead of returning `null`, we will return a masked value of the email: [tabs] @@ -2818,7 +2818,7 @@ If you are using `@EnableGlobalMethodSecurity`, you should migrate to `@EnableMe [[servlet-replace-globalmethodsecurity-with-methodsecurity]] === Replace xref:servlet/authorization/method-security.adoc#jc-enable-global-method-security[global method security] with xref:servlet/authorization/method-security.adoc#jc-enable-method-security[method security] -{security-api-url}org/springframework/security/config/annotation/method/configuration/EnableGlobalMethodSecurity.html[`@EnableGlobalMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[``] are deprecated in favor of {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableMethodSecurity.html[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[``], respectively. +javadoc:org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity[format=annotation] and xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[``] are deprecated in favor of javadoc:org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity[`@EnableMethodSecurity`] and xref:servlet/appendix/namespace/method-security.adoc#nsa-method-security[``], respectively. The new annotation and XML element activate Spring's xref:servlet/authorization/method-security.adoc#jc-enable-method-security[pre-post annotations] by default and use `AuthorizationManager` internally. This means that the following two listings are functionally equivalent: diff --git a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc index 7dd0a4cea0e..68b0906addf 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc @@ -79,20 +79,20 @@ To learn more about CSRF protection for your application, consider the following [[csrf-components]] == Understanding CSRF Protection's Components -CSRF protection is provided by several components that are composed within the {security-api-url}org/springframework/security/web/csrf/CsrfFilter.html[`CsrfFilter`]: +CSRF protection is provided by several components that are composed within the javadoc:org.springframework.security.web.csrf.CsrfFilter[]: .`CsrfFilter` Components image::{figures}/csrf.png[] CSRF protection is divided into two parts: -1. Make the {security-api-url}org/springframework/security/web/csrf/CsrfToken.html[`CsrfToken`] available to the application by delegating to the <>. +1. Make the javadoc:org.springframework.security.web.csrf.CsrfToken[] available to the application by delegating to the <>. 2. Determine if the request requires CSRF protection, load and validate the token, and <>. .`CsrfFilter` Processing image::{figures}/csrf-processing.png[] -* image:{icondir}/number_1.png[] First, the {security-api-url}org/springframework/security/web/csrf/DeferredCsrfToken.html[`DeferredCsrfToken`] is loaded, which holds a reference to the <> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]). +* image:{icondir}/number_1.png[] First, the javadoc:org.springframework.security.web.csrf.DeferredCsrfToken[] is loaded, which holds a reference to the <> so that the persisted `CsrfToken` can be loaded later (in image:{icondir}/number_4.png[]). * image:{icondir}/number_2.png[] Second, a `Supplier` (created from `DeferredCsrfToken`) is given to the <>, which is responsible for populating a request attribute to make the `CsrfToken` available to the rest of the application. * image:{icondir}/number_3.png[] Next, the main CSRF protection processing begins and checks if the current request requires CSRF protection. If not required, the filter chain is continued and processing ends. * image:{icondir}/number_4.png[] If CSRF protection is required, the persisted `CsrfToken` is finally loaded from the `DeferredCsrfToken`. @@ -128,7 +128,7 @@ You can also specify <> to [[csrf-token-repository-httpsession]] === Using the `HttpSessionCsrfTokenRepository` -By default, Spring Security stores the expected CSRF token in the `HttpSession` by using {security-api-url}org/springframework/security/web/csrf/HttpSessionCsrfTokenRepository.html[`HttpSessionCsrfTokenRepository`], so no additional code is necessary. +By default, Spring Security stores the expected CSRF token in the `HttpSession` by using javadoc:org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository[], so no additional code is necessary. The `HttpSessionCsrfTokenRepository` reads the token from an HTTP request header named `X-CSRF-TOKEN` or the request parameter `_csrf` by default. @@ -197,7 +197,7 @@ XML:: [[csrf-token-repository-cookie]] === Using the `CookieCsrfTokenRepository` -You can persist the `CsrfToken` in a cookie to <> using the {security-api-url}org/springframework/security/web/csrf/CookieCsrfTokenRepository.html[`CookieCsrfTokenRepository`]. +You can persist the `CsrfToken` in a cookie to <> using the javadoc:org.springframework.security.web.csrf.CookieCsrfTokenRepository[]. The `CookieCsrfTokenRepository` writes to a cookie named `XSRF-TOKEN` and reads it from an HTTP request header named `X-XSRF-TOKEN` or the request parameter `_csrf` by default. These defaults come from Angular and its predecessor https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection[AngularJS]. @@ -280,7 +280,7 @@ If you do not need the ability to read the cookie with JavaScript directly, we _ [[csrf-token-repository-custom]] === Customizing the `CsrfTokenRepository` -There can be cases where you want to implement a custom {security-api-url}org/springframework/security/web/csrf/CsrfTokenRepository.html[`CsrfTokenRepository`]. +There can be cases where you want to implement a custom javadoc:org.springframework.security.web.csrf.CsrfTokenRepository[]. Once you've implemented the `CsrfTokenRepository` interface, you can configure Spring Security to use it with the following configuration: @@ -708,7 +708,7 @@ The following view technologies automatically include the actual CSRF token in a * https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-view-jsp-formtaglib[Spring’s form tag library] * https://www.thymeleaf.org/doc/tutorials/2.1/thymeleafspring.html#integration-with-requestdatavalueprocessor[Thymeleaf] -* Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] (via {security-api-url}org/springframework/security/web/servlet/support/csrf/CsrfRequestDataValueProcessor.html[`CsrfRequestDataValueProcessor`]) +* Any other view technology that integrates with {spring-framework-api-url}org/springframework/web/servlet/support/RequestDataValueProcessor.html[`RequestDataValueProcessor`] (via javadoc:org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor[]) * You can also include the token yourself via the xref:servlet/integrations/jsp-taglibs.adoc#taglibs-csrfinput[csrfInput] tag If these options are not available, you can take advantage of the fact that the `CsrfToken` is exposed as an <>. @@ -763,7 +763,7 @@ Spring Security defers loading a new CSRF token by default, and additional work [NOTE] ==== -Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token. +Refreshing the token after authentication success and logout success is required because the javadoc:org.springframework.security.web.csrf.CsrfAuthenticationStrategy[] and javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[] will clear the previous token. The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token. ==== @@ -1088,7 +1088,7 @@ This endpoint should be called to obtain a CSRF token when the application is la [NOTE] ==== -Refreshing the token after authentication success and logout success is required because the {security-api-url}org/springframework/security/web/csrf/CsrfAuthenticationStrategy.html[`CsrfAuthenticationStrategy`] and {security-api-url}org/springframework/security/web/csrf/CsrfLogoutHandler.html[`CsrfLogoutHandler`] will clear the previous token. +Refreshing the token after authentication success and logout success is required because the javadoc:org.springframework.security.web.csrf.CsrfAuthenticationStrategy[] and javadoc:org.springframework.security.web.csrf.CsrfLogoutHandler[] will clear the previous token. The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token. ==== diff --git a/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc b/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc index 4ab70e36695..29dd8fcf93c 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc @@ -28,7 +28,7 @@ The strategy is implemented in the class `AntPathRequestMatcher`, which uses Spr If you need a more powerful matching strategy, you can use regular expressions. The strategy implementation is then `RegexRequestMatcher`. -See the {security-api-url}/org/springframework/security/web/util/matcher/RegexRequestMatcher.html[Javadoc for this class] for more information. +See the javadoc:org.springframework.security.web.util.matcher.RegexRequestMatcher[] Javadoc for more information. In practice, we recommend that you use method security at your service layer, to control access to your application, rather than rely entirely on the use of security constraints defined at the web-application level. URLs change, and it is difficult to take into account all the possible URLs that an application might support and how requests might be manipulated. diff --git a/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc b/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc index d48b9042ab8..25753ff4bcd 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc @@ -153,12 +153,12 @@ This means that we are running our `Runnable` with the same user that was used t See the {security-api-url}index.html[Javadoc] for additional integrations with both the Java concurrent APIs and the Spring Task abstractions. They are self-explanatory once you understand the previous code. -* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextCallable.html[`DelegatingSecurityContextCallable`] -* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextExecutor.html[`DelegatingSecurityContextExecutor`] -* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextExecutorService.html[`DelegatingSecurityContextExecutorService`] -* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextRunnable.html[`DelegatingSecurityContextRunnable`] -* {security-api-url}org/springframework/security/concurrent/DelegatingSecurityContextScheduledExecutorService.html[`DelegatingSecurityContextScheduledExecutorService`] -* {security-api-url}org/springframework/security/scheduling/DelegatingSecurityContextSchedulingTaskExecutor.html[`DelegatingSecurityContextSchedulingTaskExecutor`] -* {security-api-url}org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html[`DelegatingSecurityContextAsyncTaskExecutor`] -* {security-api-url}org/springframework/security/task/DelegatingSecurityContextTaskExecutor.html[`DelegatingSecurityContextTaskExecutor`] -* {security-api-url}org/springframework/security/scheduling/DelegatingSecurityContextTaskScheduler.html[`DelegatingSecurityContextTaskScheduler`] +* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextCallable[] +* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextExecutor[] +* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextExecutorService[] +* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextRunnable[] +* javadoc:org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService[] +* javadoc:org.springframework.security.scheduling.DelegatingSecurityContextSchedulingTaskExecutor[] +* javadoc:org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor[] +* javadoc:org.springframework.security.task.DelegatingSecurityContextTaskExecutor[] +* javadoc:org.springframework.security.scheduling.DelegatingSecurityContextTaskScheduler[] diff --git a/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc b/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc index 538379459cc..d8f7eabd523 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc @@ -23,8 +23,8 @@ String json = mapper.writeValueAsString(context); ==== The following Spring Security modules provide Jackson support: -- spring-security-core ({security-api-url}org/springframework/security/jackson2/CoreJackson2Module.html[`CoreJackson2Module`]) -- spring-security-web ({security-api-url}org/springframework/security/web/jackson2/WebJackson2Module.html[`WebJackson2Module`], {security-api-url}org/springframework/security/web/jackson2/WebServletJackson2Module.html[`WebServletJackson2Module`], {security-api-url}org/springframework/security/web/server/jackson2/WebServerJackson2Module.html[`WebServerJackson2Module`]) -- <> ({security-api-url}org/springframework/security/oauth2/client/jackson2/OAuth2ClientJackson2Module.html[`OAuth2ClientJackson2Module`]) -- spring-security-cas ({security-api-url}org/springframework/security/cas/jackson2/CasJackson2Module.html[`CasJackson2Module`]) +- spring-security-core (javadoc:org.springframework.security.jackson2.CoreJackson2Module[]) +- spring-security-web (javadoc:org.springframework.security.web.jackson2.WebJackson2Module[], javadoc:org.springframework.security.web.jackson2.WebServletJackson2Module[], javadoc:org.springframework.security.web.server.jackson2.WebServerJackson2Module[]) +- <> (javadoc:org.springframework.security.oauth2.client.jackson2.OAuth2ClientJackson2Module[]) +- spring-security-cas (javadoc:org.springframework.security.cas.jackson2.CasJackson2Module[]) ==== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc index a805132c280..aea9d358fcb 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc @@ -105,7 +105,7 @@ Xml:: == Bearer Token Propagation Now that your resource server has validated the token, it might be handy to pass it to downstream services. -This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]`, which you can see in the following example: +This is quite simple with javadoc:org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServletBearerExchangeFilterFunction[], which you can see in the following example: [tabs] ====== @@ -134,7 +134,7 @@ fun rest(): WebClient { ---- ====== -When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential. +When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any javadoc:org.springframework.security.oauth2.core.AbstractOAuth2Token[] credential. Then, it will propagate that token in the `Authorization` header. For example: @@ -198,7 +198,7 @@ this.rest.get() In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain. [NOTE] -Unlike the {security-api-url}org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired. +Unlike the javadoc:org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired. To obtain this level of support, please use the OAuth 2.0 Client filter. === `RestTemplate` support @@ -259,7 +259,7 @@ fun rest(): RestTemplate { [NOTE] -Unlike the {security-api-url}org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.html[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired. +Unlike the javadoc:org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired. To obtain this level of support, please create an interceptor using the xref:servlet/oauth2/client/index.adoc#oauth2client[OAuth 2.0 Authorized Client Manager]. [[oauth2resourceserver-bearertoken-failure]] diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/index.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/index.adoc index 3a3eae8ea7a..0749008ebbc 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/index.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/index.adoc @@ -30,7 +30,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`. image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates _Start Authentication_. -The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationEntryPoint.html[`BearerTokenAuthenticationEntryPoint`], which sends a `WWW-Authenticate` header. +The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of javadoc:org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint[], which sends a `WWW-Authenticate` header. The `RequestCache` is typically a `NullRequestCache` that does not save the request, since the client is capable of replaying the requests it originally requested. When a client receives the `WWW-Authenticate: Bearer` header, it knows it should retry with a bearer token. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc index 4296127a351..8e2b765cd6e 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc @@ -86,7 +86,7 @@ From here, consider jumping to: Next, let's see the architectural components that Spring Security uses to support https://tools.ietf.org/html/rfc7519[JWT] Authentication in servlet-based applications, like the one we just saw. -{security-api-url}org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationProvider.html[`JwtAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <> and <> to authenticate a JWT. +javadoc:org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider[] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <> and <> to authenticate a JWT. Let's take a look at how `JwtAuthenticationProvider` works within Spring Security. The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from <> works. @@ -293,7 +293,7 @@ fun jwtDecoder(): JwtDecoder { ====== [NOTE] -Calling `{security-api-url}org/springframework/security/oauth2/jwt/JwtDecoders.html#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation]` is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri. +Calling javadoc:org.springframework.security.oauth2.jwt.JwtDecoders#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation] is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri. If the application doesn't expose a `JwtDecoder` bean, then Spring Boot will expose the above default one. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc index 551ac360fd1..90b64a01f46 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc @@ -68,7 +68,7 @@ Given an Opaque Token, Resource Server will 2. Inspect the response for an `{ 'active' : true }` attribute 3. Map each scope to an authority with the prefix `SCOPE_` -The resulting `Authentication#getPrincipal`, by default, is a Spring Security `{security-api-url}org/springframework/security/oauth2/core/OAuth2AuthenticatedPrincipal.html[OAuth2AuthenticatedPrincipal]` object, and `Authentication#getName` maps to the token's `sub` property, if one is present. +The resulting `Authentication#getPrincipal`, by default, is a Spring Security javadoc:org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal[] object, and `Authentication#getName` maps to the token's `sub` property, if one is present. From here, you may want to jump to: @@ -82,7 +82,7 @@ From here, you may want to jump to: Next, let's see the architectural components that Spring Security uses to support https://tools.ietf.org/html/rfc7662[opaque token] Authentication in servlet-based applications, like the one we just saw. -{security-api-url}org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.html[`OpaqueTokenAuthenticationProvider`] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <> to authenticate an opaque token. +javadoc:org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider[] is an xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationprovider[`AuthenticationProvider`] implementation that leverages a <> to authenticate an opaque token. Let's take a look at how `OpaqueTokenAuthenticationProvider` works within Spring Security. The figure explains details of how the xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationmanager[`AuthenticationManager`] in figures from <> works. diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc index c29f7a35a7f..77c43da2dc1 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc @@ -19,7 +19,7 @@ image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is _Denied_ by throwing an `AccessDeniedException`. image:{icondir}/number_3.png[] Since the user lacks authorization, the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates _Start Authentication_. -The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`], which redirects to <` generating endpoint>>, `Saml2WebSsoAuthenticationRequestFilter`. +The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of javadoc:org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint[], which redirects to <` generating endpoint>>, `Saml2WebSsoAuthenticationRequestFilter`. Alternatively, if you have <>, it first redirects to a picker page. image:{icondir}/number_4.png[] Next, the `Saml2WebSsoAuthenticationRequestFilter` creates, signs, serializes, and encodes a `` using its configured <>. @@ -418,7 +418,7 @@ class MyCustomSecurityConfiguration { The preceding example requires the role of `USER` for any URL that starts with `/messages/`. [[servlet-saml2login-relyingpartyregistrationrepository]] -The second `@Bean` Spring Boot creates is a {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationRepository.html[`RelyingPartyRegistrationRepository`], which represents the asserting party and relying party metadata. +The second `@Bean` Spring Boot creates is a javadoc:org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository[], which represents the asserting party and relying party metadata. This includes such things as the location of the SSO endpoint the relying party should use when requesting authentication from the asserting party. You can override the default by publishing your own `RelyingPartyRegistrationRepository` bean. @@ -641,7 +641,7 @@ In this way, the set of `RelyingPartyRegistration`s will refresh based on {sprin [[servlet-saml2login-relyingpartyregistration]] == RelyingPartyRegistration -A {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[`RelyingPartyRegistration`] +A javadoc:org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration[] instance represents a link between an relying party and an asserting party's metadata. In a `RelyingPartyRegistration`, you can provide relying party metadata like its `Issuer` value, where it expects SAML Responses to be sent to, and any credentials that it owns for the purposes of signing or decrypting payloads. diff --git a/docs/modules/ROOT/pages/servlet/saml2/logout.adoc b/docs/modules/ROOT/pages/servlet/saml2/logout.adoc index 34038df70c6..97e155cdd30 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/logout.adoc @@ -189,28 +189,28 @@ Next, let's see the architectural components that Spring Security uses to suppor For RP-initiated logout: image:{icondir}/number_1.png[] Spring Security executes its xref:servlet/authentication/logout.adoc#logout-architecture[logout flow], calling its ``LogoutHandler``s to invalidate the session and perform other cleanup. -It then invokes the {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2RelyingPartyInitiatedLogoutSuccessHandler.html[`Saml2RelyingPartyInitiatedLogoutSuccessHandler`]. +It then invokes the javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2RelyingPartyInitiatedLogoutSuccessHandler[]. image:{icondir}/number_2.png[] The logout success handler uses an instance of -{security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestResolver.html[`Saml2LogoutRequestResolver`] to create, sign, and serialize a ``. +javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver[] to create, sign, and serialize a ``. It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] that is associated with the current `Saml2AuthenticatedPrincipal`. Then, it redirect-POSTs the `` to the asserting party SLO endpoint The browser hands control over to the asserting party. If the asserting party redirects back (which it may not), then the application proceeds to step image:{icondir}/number_3.png[]. -image:{icondir}/number_3.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseFilter.html[`Saml2LogoutResponseFilter`] deserializes, verifies, and processes the `` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutResponseValidator.html[`Saml2LogoutResponseValidator`]. +image:{icondir}/number_3.png[] The javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter[] deserializes, verifies, and processes the `` with its javadoc:org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator[]. image:{icondir}/number_4.png[] If valid, then it completes the local logout flow by redirecting to `/login?logout`, or whatever has been configured. If invalid, then it responds with a 400. For AP-initiated logout: -image:{icondir}/number_1.png[] The {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutRequestFilter.html[`Saml2LogoutRequestFilter`] deserializes, verifies, and processes the `` with its {security-api-url}org/springframework/security/saml2/provider/service/authentication/logout/Saml2LogoutRequestValidator.html[`Saml2LogoutRequestValidator`]. +image:{icondir}/number_1.png[] The javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter[] deserializes, verifies, and processes the `` with its javadoc:org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator[]. image:{icondir}/number_2.png[] If valid, then the filter calls the configured ``LogoutHandler``s, invalidating the session and performing other cleanup. -image:{icondir}/number_3.png[] It uses a {security-api-url}org/springframework/security/saml2/provider/service/web/authentication/logout/Saml2LogoutResponseResolver.html[`Saml2LogoutResponseResolver`] to create, sign and serialize a ``. +image:{icondir}/number_3.png[] It uses a javadoc:org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver[] to create, sign and serialize a ``. It uses the keys and configuration from the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] derived from the endpoint or from the contents of the ``. Then, it redirect-POSTs the `` to the asserting party SLO endpoint. diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc index 562ee2d7c45..581f49adefa 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc @@ -964,7 +964,7 @@ mvc.get("/endpoint") { ---- ====== -You can also specify a complete `Jwt`, for which `{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder]` comes quite handy: +You can also specify a complete `Jwt`, for which javadoc:org.springframework.security.oauth2.jwt.Jwt$Builder[] comes quite handy: [tabs] ====== diff --git a/docs/package.json b/docs/package.json index 0e1b0a600e8..24797cedba8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "@antora/atlas-extension": "1.0.0-alpha.2", "@antora/collector-extension": "1.0.0-alpha.4", "@asciidoctor/tabs": "1.0.0-beta.6", - "@springio/antora-extensions": "1.11.1", - "@springio/asciidoctor-extensions": "1.0.0-alpha.10" + "@springio/antora-extensions": "1.12.0", + "@springio/asciidoctor-extensions": "1.0.0-alpha.11" } } diff --git a/docs/spring-security-docs.gradle b/docs/spring-security-docs.gradle index 09879391bd7..fdd01e80b0d 100644 --- a/docs/spring-security-docs.gradle +++ b/docs/spring-security-docs.gradle @@ -15,11 +15,22 @@ antora { ] } +tasks.register("syncAntoraAttachments", Sync) { + group = 'Documentation' + description = 'Syncs the Antora attachments' + from project.provider( { project.tasks.api.outputs } ) + into project.layout.buildDirectory.dir('generated-antora-resources/modules/ROOT/assets/attachments/api/java') +} + tasks.named("generateAntoraYml") { asciidocAttributes = project.provider( { generateAttributes() } ) asciidocAttributes.putAll(providers.provider( { resolvedVersions(project.configurations.testRuntimeClasspath) })) } +tasks.register("generateAntoraResources") { + dependsOn 'generateAntoraYml', 'syncAntoraAttachments' +} + dependencies { testImplementation platform(project(':spring-security-dependencies')) testImplementation 'com.unboundid:unboundid-ldapsdk' From 5437bf517cda714bc82ac4da26477b7d550f9d92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 03:56:46 +0000 Subject: [PATCH 57/68] Bump io.projectreactor:reactor-bom from 2023.0.7 to 2023.0.8 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.7 to 2023.0.8. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2023.0.7...2023.0.8) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4caaa768311..73a8d274216 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.8" io-mockk = "io.mockk:mockk:1.13.11" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.8" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } From 7e43a423a94e317333413c40b6e1069e4a5b9ea6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 03:58:55 +0000 Subject: [PATCH 58/68] Bump io.projectreactor:reactor-bom from 2023.0.7 to 2023.0.8 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.7 to 2023.0.8. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2023.0.7...2023.0.8) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a68a05166c..85474fbf43b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.8" io-mockk = "io.mockk:mockk:1.13.11" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.8" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } From 902d60d019d2c46f965ba84c3a368f43f01239bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 04:04:05 +0000 Subject: [PATCH 59/68] Bump io.projectreactor:reactor-bom from 2023.0.7 to 2023.0.8 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2023.0.7 to 2023.0.8. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2023.0.7...2023.0.8) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 63e3ccfdc62..534fed47f3d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:6.0.11" commons-collections = "commons-collections:commons-collections:3.2.2" io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.12.8" io-mockk = "io.mockk:mockk:1.13.11" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.7" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2023.0.8" io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" } io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" } io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" } From c00197bdf5178f277d7eb3794778e51820c625eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 04:04:36 +0000 Subject: [PATCH 60/68] Bump io.projectreactor.netty:reactor-netty from 1.0.46 to 1.0.47 Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.46 to 1.0.47. - [Release notes](https://github.com/reactor/reactor-netty/releases) - [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.46...v1.0.47) --- updated-dependencies: - dependency-name: io.projectreactor.netty:reactor-netty dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8b3c8cb2b68..8b93897c53e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:4.0.14" commons-collections = "commons-collections:commons-collections:3.2.2" io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:6.5.1" io-mockk = "io.mockk:mockk:1.13.3" -io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.46" +io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.47" io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.45" io-projectreactor-tools-blockhound = "io.projectreactor.tools:blockhound:1.0.9.RELEASE" io-r2dbc-r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "io-r2dbc" } From 341f58d4114b21ba8d1ef74868d84297daecd00f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 04:20:09 +0000 Subject: [PATCH 61/68] Bump io.projectreactor:reactor-bom from 2020.0.45 to 2020.0.46 Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.45 to 2020.0.46. - [Release notes](https://github.com/reactor/reactor/releases) - [Commits](https://github.com/reactor/reactor/compare/2020.0.45...2020.0.46) --- updated-dependencies: - dependency-name: io.projectreactor:reactor-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8b93897c53e..0742e46a197 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ commons-collections = "commons-collections:commons-collections:3.2.2" io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:6.5.1" io-mockk = "io.mockk:mockk:1.13.3" io-projectreactor-netty-reactor-netty = "io.projectreactor.netty:reactor-netty:1.0.47" -io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.45" +io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2020.0.46" io-projectreactor-tools-blockhound = "io.projectreactor.tools:blockhound:1.0.9.RELEASE" io-r2dbc-r2dbc-h2 = { module = "io.r2dbc:r2dbc-h2", version.ref = "io-r2dbc" } io-r2dbc-r2dbc-spi-test = { module = "io.r2dbc:r2dbc-spi-test", version.ref = "io-r2dbc" } From 9cfc9bb8a87ff3c125bb614ead803db40ea5a244 Mon Sep 17 00:00:00 2001 From: Marcus Hert Da Coregio Date: Wed, 10 Jul 2024 12:47:05 -0300 Subject: [PATCH 62/68] Use in:title Issue gh-14484 --- .github/workflows/mark-duplicate-dependabot-prs.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/mark-duplicate-dependabot-prs.yml b/.github/workflows/mark-duplicate-dependabot-prs.yml index ff4217a8ab7..7d1574309d9 100644 --- a/.github/workflows/mark-duplicate-dependabot-prs.yml +++ b/.github/workflows/mark-duplicate-dependabot-prs.yml @@ -1,18 +1,10 @@ -name: Mark Duplicate PRs +name: Mark Duplicate Dependabot PRs on: pull_request: types: [closed] jobs: - debug: - runs-on: ubuntu-latest - steps: - - name: Debug Event Payload - run: | - echo "Merged: ${{ github.event.pull_request.merged }}" - echo "User Login: ${{ github.event.pull_request.user.login }}" - check_duplicate_prs: runs-on: ubuntu-latest if: github.event.pull_request.merged == true && github.event.pull_request.user.login == 'dependabot[bot]' @@ -33,7 +25,7 @@ jobs: DEPENDENCY_NAME: ${{ steps.extract.outputs.dependency_name }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - PRS=$(gh pr list --search "milestone:${{ github.event.pull_request.milestone.title }} is:merged $DEPENDENCY_NAME" --json number --jq 'map(.number) | join(",")') + PRS=$(gh pr list --search 'milestone:${{ github.event.pull_request.milestone.title }} is:merged in:title "$DEPENDENCY_NAME"' --json number --jq 'map(.number) | join(",")') echo "prs=$PRS" >> $GITHUB_OUTPUT - name: Label Duplicate PRs From dcf5cc9e06f0821e2e5fe6b51d13378d3c6234bb Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 10 Jul 2024 16:56:14 -0600 Subject: [PATCH 63/68] Extract EntityDescriptor to AssertingPartyDetails Logic Closes gh-15090 --- .../OpenSamlAssertingPartyDetails.java | 119 ++++++++- ...dataRelyingPartyRegistrationConverter.java | 228 ------------------ .../registration/OpenSamlMetadataUtils.java | 79 ++++++ ...gistrationBuilderHttpMessageConverter.java | 13 +- .../RelyingPartyRegistrations.java | 20 +- ...elyingPartyRegistrationConverterTests.java | 208 ---------------- .../RelyingPartyRegistrationsTests.java | 15 ++ 7 files changed, 231 insertions(+), 451 deletions(-) delete mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java delete mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java index 0d780d0e0d5..1f447f95ff0 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java @@ -16,12 +16,25 @@ package org.springframework.security.saml2.provider.service.registration; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Consumer; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.ext.saml2alg.SigningMethod; import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.Extensions; +import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml.saml2.metadata.KeyDescriptor; +import org.opensaml.saml.saml2.metadata.SingleLogoutService; +import org.opensaml.saml.saml2.metadata.SingleSignOnService; +import org.opensaml.security.credential.UsageType; +import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; +import org.springframework.security.saml2.Saml2Exception; import org.springframework.security.saml2.core.Saml2X509Credential; /** @@ -62,7 +75,111 @@ public EntityDescriptor getEntityDescriptor() { * for further configurations */ public static OpenSamlAssertingPartyDetails.Builder withEntityDescriptor(EntityDescriptor entity) { - return new OpenSamlAssertingPartyDetails.Builder(entity); + IDPSSODescriptor idpssoDescriptor = entity.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); + if (idpssoDescriptor == null) { + throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element"); + } + List verification = new ArrayList<>(); + List encryption = new ArrayList<>(); + for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) { + if (keyDescriptor.getUse().equals(UsageType.SIGNING)) { + List certificates = certificates(keyDescriptor); + for (X509Certificate certificate : certificates) { + verification.add(Saml2X509Credential.verification(certificate)); + } + } + if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) { + List certificates = certificates(keyDescriptor); + for (X509Certificate certificate : certificates) { + encryption.add(Saml2X509Credential.encryption(certificate)); + } + } + if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) { + List certificates = certificates(keyDescriptor); + for (X509Certificate certificate : certificates) { + verification.add(Saml2X509Credential.verification(certificate)); + encryption.add(Saml2X509Credential.encryption(certificate)); + } + } + } + if (verification.isEmpty()) { + throw new Saml2Exception( + "Metadata response is missing verification certificates, necessary for verifying SAML assertions"); + } + OpenSamlAssertingPartyDetails.Builder builder = new OpenSamlAssertingPartyDetails.Builder(entity) + .entityId(entity.getEntityID()) + .wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned())) + .verificationX509Credentials((c) -> c.addAll(verification)) + .encryptionX509Credentials((c) -> c.addAll(encryption)); + + List signingMethods = signingMethods(idpssoDescriptor); + for (SigningMethod method : signingMethods) { + builder.signingAlgorithms((algorithms) -> algorithms.add(method.getAlgorithm())); + } + if (idpssoDescriptor.getSingleSignOnServices().isEmpty()) { + throw new Saml2Exception( + "Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests"); + } + for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) { + Saml2MessageBinding binding; + if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) { + binding = Saml2MessageBinding.POST; + } + else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) { + binding = Saml2MessageBinding.REDIRECT; + } + else { + continue; + } + builder.singleSignOnServiceLocation(singleSignOnService.getLocation()).singleSignOnServiceBinding(binding); + break; + } + for (SingleLogoutService singleLogoutService : idpssoDescriptor.getSingleLogoutServices()) { + Saml2MessageBinding binding; + if (singleLogoutService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) { + binding = Saml2MessageBinding.POST; + } + else if (singleLogoutService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) { + binding = Saml2MessageBinding.REDIRECT; + } + else { + continue; + } + String responseLocation = (singleLogoutService.getResponseLocation() == null) + ? singleLogoutService.getLocation() : singleLogoutService.getResponseLocation(); + builder.singleLogoutServiceLocation(singleLogoutService.getLocation()) + .singleLogoutServiceResponseLocation(responseLocation) + .singleLogoutServiceBinding(binding); + break; + } + return builder; + } + + private static List certificates(KeyDescriptor keyDescriptor) { + try { + return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo()); + } + catch (CertificateException ex) { + throw new Saml2Exception(ex); + } + } + + private static List signingMethods(IDPSSODescriptor idpssoDescriptor) { + Extensions extensions = idpssoDescriptor.getExtensions(); + List result = signingMethods(extensions); + if (!result.isEmpty()) { + return result; + } + EntityDescriptor descriptor = (EntityDescriptor) idpssoDescriptor.getParent(); + extensions = descriptor.getExtensions(); + return signingMethods(extensions); + } + + private static List signingMethods(Extensions extensions) { + if (extensions != null) { + return (List) extensions.getUnknownXMLObjects(SigningMethod.DEFAULT_ELEMENT_NAME); + } + return new ArrayList<>(); } @Override diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java deleted file mode 100644 index a3ab016d0ee..00000000000 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverter.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2002-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.saml2.provider.service.registration; - -import java.io.InputStream; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import net.shibboleth.utilities.java.support.xml.ParserPool; -import org.opensaml.core.config.ConfigurationService; -import org.opensaml.core.xml.XMLObject; -import org.opensaml.core.xml.config.XMLObjectProviderRegistry; -import org.opensaml.core.xml.io.Unmarshaller; -import org.opensaml.saml.common.xml.SAMLConstants; -import org.opensaml.saml.ext.saml2alg.SigningMethod; -import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml.saml2.metadata.Extensions; -import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml.saml2.metadata.KeyDescriptor; -import org.opensaml.saml.saml2.metadata.SingleLogoutService; -import org.opensaml.saml.saml2.metadata.SingleSignOnService; -import org.opensaml.security.credential.UsageType; -import org.opensaml.xmlsec.keyinfo.KeyInfoSupport; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import org.springframework.security.saml2.Saml2Exception; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2X509Credential; - -class OpenSamlMetadataRelyingPartyRegistrationConverter { - - static { - OpenSamlInitializationService.initialize(); - } - - private final XMLObjectProviderRegistry registry; - - private final ParserPool parserPool; - - /** - * Creates a {@link OpenSamlMetadataRelyingPartyRegistrationConverter} - */ - OpenSamlMetadataRelyingPartyRegistrationConverter() { - this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class); - this.parserPool = this.registry.getParserPool(); - } - - OpenSamlRelyingPartyRegistration.Builder convert(EntityDescriptor descriptor) { - IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); - if (idpssoDescriptor == null) { - throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element"); - } - List verification = new ArrayList<>(); - List encryption = new ArrayList<>(); - for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) { - if (keyDescriptor.getUse().equals(UsageType.SIGNING)) { - List certificates = certificates(keyDescriptor); - for (X509Certificate certificate : certificates) { - verification.add(Saml2X509Credential.verification(certificate)); - } - } - if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) { - List certificates = certificates(keyDescriptor); - for (X509Certificate certificate : certificates) { - encryption.add(Saml2X509Credential.encryption(certificate)); - } - } - if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) { - List certificates = certificates(keyDescriptor); - for (X509Certificate certificate : certificates) { - verification.add(Saml2X509Credential.verification(certificate)); - encryption.add(Saml2X509Credential.encryption(certificate)); - } - } - } - if (verification.isEmpty()) { - throw new Saml2Exception( - "Metadata response is missing verification certificates, necessary for verifying SAML assertions"); - } - OpenSamlRelyingPartyRegistration.Builder builder = OpenSamlRelyingPartyRegistration - .withAssertingPartyEntityDescriptor(descriptor) - .assertingPartyDetails((party) -> party.entityId(descriptor.getEntityID()) - .wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned())) - .verificationX509Credentials((c) -> c.addAll(verification)) - .encryptionX509Credentials((c) -> c.addAll(encryption))); - - List signingMethods = signingMethods(idpssoDescriptor); - for (SigningMethod method : signingMethods) { - builder.assertingPartyDetails( - (party) -> party.signingAlgorithms((algorithms) -> algorithms.add(method.getAlgorithm()))); - } - if (idpssoDescriptor.getSingleSignOnServices().isEmpty()) { - throw new Saml2Exception( - "Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests"); - } - for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) { - Saml2MessageBinding binding; - if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) { - binding = Saml2MessageBinding.POST; - } - else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) { - binding = Saml2MessageBinding.REDIRECT; - } - else { - continue; - } - builder - .assertingPartyDetails((party) -> party.singleSignOnServiceLocation(singleSignOnService.getLocation()) - .singleSignOnServiceBinding(binding)); - break; - } - for (SingleLogoutService singleLogoutService : idpssoDescriptor.getSingleLogoutServices()) { - Saml2MessageBinding binding; - if (singleLogoutService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) { - binding = Saml2MessageBinding.POST; - } - else if (singleLogoutService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) { - binding = Saml2MessageBinding.REDIRECT; - } - else { - continue; - } - String responseLocation = (singleLogoutService.getResponseLocation() == null) - ? singleLogoutService.getLocation() : singleLogoutService.getResponseLocation(); - builder - .assertingPartyDetails((party) -> party.singleLogoutServiceLocation(singleLogoutService.getLocation()) - .singleLogoutServiceResponseLocation(responseLocation) - .singleLogoutServiceBinding(binding)); - break; - } - - return builder; - } - - Collection convert(InputStream inputStream) { - List builders = new ArrayList<>(); - XMLObject xmlObject = xmlObject(inputStream); - if (xmlObject instanceof EntitiesDescriptor) { - EntitiesDescriptor descriptors = (EntitiesDescriptor) xmlObject; - for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) { - if (descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) != null) { - builders.add(convert(descriptor)); - } - } - if (builders.isEmpty()) { - throw new Saml2Exception("Metadata contains no IDPSSODescriptor elements"); - } - return builders; - } - if (xmlObject instanceof EntityDescriptor) { - EntityDescriptor descriptor = (EntityDescriptor) xmlObject; - return Arrays.asList(convert(descriptor)); - } - throw new Saml2Exception("Unsupported element of type " + xmlObject.getClass()); - } - - private List certificates(KeyDescriptor keyDescriptor) { - try { - return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo()); - } - catch (CertificateException ex) { - throw new Saml2Exception(ex); - } - } - - private List signingMethods(IDPSSODescriptor idpssoDescriptor) { - Extensions extensions = idpssoDescriptor.getExtensions(); - List result = signingMethods(extensions); - if (!result.isEmpty()) { - return result; - } - EntityDescriptor descriptor = (EntityDescriptor) idpssoDescriptor.getParent(); - extensions = descriptor.getExtensions(); - return signingMethods(extensions); - } - - private XMLObject xmlObject(InputStream inputStream) { - Document document = document(inputStream); - Element element = document.getDocumentElement(); - Unmarshaller unmarshaller = this.registry.getUnmarshallerFactory().getUnmarshaller(element); - if (unmarshaller == null) { - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); - } - try { - return unmarshaller.unmarshall(element); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private Document document(InputStream inputStream) { - try { - return this.parserPool.parse(inputStream); - } - catch (Exception ex) { - throw new Saml2Exception(ex); - } - } - - private List signingMethods(Extensions extensions) { - if (extensions != null) { - return (List) extensions.getUnknownXMLObjects(SigningMethod.DEFAULT_ELEMENT_NAME); - } - return new ArrayList<>(); - } - -} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java new file mode 100644 index 00000000000..b7efc22c407 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2024 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.registration; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; + +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; +import org.opensaml.core.xml.io.Unmarshaller; +import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.core.OpenSamlInitializationService; + +final class OpenSamlMetadataUtils { + + static { + OpenSamlInitializationService.initialize(); + } + + private OpenSamlMetadataUtils() { + + } + + static Collection descriptors(InputStream metadata) { + XMLObject object = xmlObject(metadata); + if (object instanceof EntityDescriptor descriptor) { + return Collections.singleton(descriptor); + } + if (object instanceof EntitiesDescriptor descriptors) { + return descriptors.getEntityDescriptors(); + } + throw new Saml2Exception("Unsupported element type: " + object.getClass().getName()); + } + + static XMLObject xmlObject(InputStream inputStream) { + Document document = document(inputStream); + Element element = document.getDocumentElement(); + Unmarshaller unmarshaller = XMLObjectProviderRegistrySupport.getUnmarshallerFactory().getUnmarshaller(element); + if (unmarshaller == null) { + throw new Saml2Exception("Unsupported element of type " + element.getTagName()); + } + try { + return unmarshaller.unmarshall(element); + } + catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + + static Document document(InputStream inputStream) { + try { + return XMLObjectProviderRegistrySupport.getParserPool().parse(inputStream); + } + catch (Exception ex) { + throw new Saml2Exception(ex); + } + } + +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java index 3d4069ab8f0..4437261e7fb 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -62,15 +62,6 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter OpenSamlInitializationService.initialize(); } - private final OpenSamlMetadataRelyingPartyRegistrationConverter converter; - - /** - * Creates a {@link OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter} - */ - public OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter() { - this.converter = new OpenSamlMetadataRelyingPartyRegistrationConverter(); - } - @Override public boolean canRead(Class clazz, MediaType mediaType) { return RelyingPartyRegistration.Builder.class.isAssignableFrom(clazz); @@ -89,7 +80,7 @@ public List getSupportedMediaTypes() { @Override public RelyingPartyRegistration.Builder read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - return this.converter.convert(inputMessage.getBody()).iterator().next(); + return RelyingPartyRegistrations.fromMetadata(inputMessage.getBody()); } @Override diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java index d7382eafd33..13b8bfeb3a9 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java @@ -18,8 +18,12 @@ import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collection; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; + import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; import org.springframework.security.saml2.Saml2Exception; @@ -34,8 +38,6 @@ */ public final class RelyingPartyRegistrations { - private static final OpenSamlMetadataRelyingPartyRegistrationConverter relyingPartyRegistrationConverter = new OpenSamlMetadataRelyingPartyRegistrationConverter(); - private static final ResourceLoader resourceLoader = new DefaultResourceLoader(); private RelyingPartyRegistrations() { @@ -213,7 +215,19 @@ public static Collection collectionFromMetadat * @since 5.7 */ public static Collection collectionFromMetadata(InputStream source) { - return relyingPartyRegistrationConverter.convert(source); + Collection builders = new ArrayList<>(); + for (EntityDescriptor descriptor : OpenSamlMetadataUtils.descriptors(source)) { + if (descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS) != null) { + OpenSamlAssertingPartyDetails assertingParty = OpenSamlAssertingPartyDetails + .withEntityDescriptor(descriptor) + .build(); + builders.add(new OpenSamlRelyingPartyRegistration.Builder(assertingParty)); + } + } + if (builders.isEmpty()) { + throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element"); + } + return builders; } } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java deleted file mode 100644 index ee270c86727..00000000000 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlMetadataRelyingPartyRegistrationConverterTests.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2002-2023 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.saml2.provider.service.registration; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Base64; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.xmlsec.signature.support.SignatureConstants; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.security.saml2.Saml2Exception; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -public class OpenSamlMetadataRelyingPartyRegistrationConverterTests { - - private static final String CERTIFICATE = "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk"; - - private static final String ENTITIES_DESCRIPTOR_TEMPLATE = "\n%s"; - - private static final String ENTITY_DESCRIPTOR_TEMPLATE = "\n%s" - + ""; - - private static final String IDP_SSO_DESCRIPTOR_TEMPLATE = "\n" - + "%s\n" + ""; - - private static final String KEY_DESCRIPTOR_TEMPLATE = "\n" - + "\n" + "\n" - + "" + CERTIFICATE + "\n" + "\n" + "\n" - + ""; - - private static final String EXTENSIONS_TEMPLATE = "" + "" + ""; - - private static final String SINGLE_SIGN_ON_SERVICE_TEMPLATE = ""; - - private OpenSamlMetadataRelyingPartyRegistrationConverter converter = new OpenSamlMetadataRelyingPartyRegistrationConverter(); - - private String metadata; - - @BeforeEach - public void setup() throws Exception { - ClassPathResource resource = new ClassPathResource("test-metadata.xml"); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { - this.metadata = reader.lines().collect(Collectors.joining()); - } - } - - // gh-12667 - @Test - public void convertWhenDefaultsThenAssertingPartyInstanceOfOpenSaml() throws Exception { - try (InputStream source = new ByteArrayInputStream(this.metadata.getBytes(StandardCharsets.UTF_8))) { - this.converter.convert(source) - .forEach((registration) -> assertThat(registration.build().getAssertingPartyDetails()) - .isInstanceOf(OpenSamlAssertingPartyDetails.class)); - } - } - - @Test - public void readWhenMissingIDPSSODescriptorThenException() { - String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, ""); - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream)) - .withMessageContaining("Metadata response is missing the necessary IDPSSODescriptor element"); - } - - @Test - public void readWhenMissingVerificationKeyThenException() { - String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, "")); - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream)) - .withMessageContaining( - "Metadata response is missing verification certificates, necessary for verifying SAML assertions"); - } - - @Test - public void readWhenMissingSingleSignOnServiceThenException() { - String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, - String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\""))); - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream)) - .withMessageContaining( - "Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests"); - } - - @Test - public void readWhenDescriptorFullySpecifiedThenConfigures() throws Exception { - String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, - String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, - String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"") - + String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") + EXTENSIONS_TEMPLATE - + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE))); - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream) - .iterator() - .next() - .build() - .getAssertingPartyDetails(); - assertThat(details.getWantAuthnRequestsSigned()).isFalse(); - assertThat(details.getSigningAlgorithms()).containsExactly(SignatureConstants.ALGO_ID_DIGEST_SHA512); - assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location"); - assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(details.getEntityId()).isEqualTo("entity-id"); - assertThat(details.getVerificationX509Credentials()).hasSize(1); - assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate()) - .isEqualTo(x509Certificate(CERTIFICATE)); - assertThat(details.getEncryptionX509Credentials()).hasSize(1); - assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate()) - .isEqualTo(x509Certificate(CERTIFICATE)); - assertThat(details).isInstanceOf(OpenSamlAssertingPartyDetails.class); - OpenSamlAssertingPartyDetails openSamlDetails = (OpenSamlAssertingPartyDetails) details; - EntityDescriptor entityDescriptor = openSamlDetails.getEntityDescriptor(); - assertThat(entityDescriptor).isNotNull(); - assertThat(entityDescriptor.getEntityID()).isEqualTo(details.getEntityId()); - } - - // gh-9051 - @Test - public void readWhenEntitiesDescriptorThenConfigures() throws Exception { - String payload = String.format(ENTITIES_DESCRIPTOR_TEMPLATE, - String.format(ENTITY_DESCRIPTOR_TEMPLATE, - String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, - String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"") - + String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") - + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)))); - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream) - .iterator() - .next() - .build() - .getAssertingPartyDetails(); - assertThat(details.getWantAuthnRequestsSigned()).isFalse(); - assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location"); - assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(details.getEntityId()).isEqualTo("entity-id"); - assertThat(details.getVerificationX509Credentials()).hasSize(1); - assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate()) - .isEqualTo(x509Certificate(CERTIFICATE)); - assertThat(details.getEncryptionX509Credentials()).hasSize(1); - assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate()) - .isEqualTo(x509Certificate(CERTIFICATE)); - } - - @Test - public void readWhenKeyDescriptorHasNoUseThenConfiguresBothKeyTypes() throws Exception { - String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, - String.format(KEY_DESCRIPTOR_TEMPLATE, "") + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE))); - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - RelyingPartyRegistration.AssertingPartyDetails details = this.converter.convert(inputStream) - .iterator() - .next() - .build() - .getAssertingPartyDetails(); - assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate()) - .isEqualTo(x509Certificate(CERTIFICATE)); - assertThat(details.getEncryptionX509Credentials()).hasSize(1); - assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate()) - .isEqualTo(x509Certificate(CERTIFICATE)); - } - - X509Certificate x509Certificate(String data) { - try { - InputStream certificate = new ByteArrayInputStream(Base64.getDecoder().decode(data.getBytes())); - return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificate); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex); - } - } - - // gh-9051 - @Test - public void readWhenUnsupportedElementThenSaml2Exception() { - String payload = ""; - InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream)) - .withMessage("Unsupported element of type saml2:Assertion"); - } - -} diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java index 7d89c998106..9ecbc0bd086 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java @@ -252,6 +252,21 @@ public void collectionFromMetadataInputStreamWhenResolvableThenPopulatesBuilder( } } + @Test + public void fromMetadataLocationWhenResolvableThenUsesEntityIdAndOpenSamlRelyingPartyRegistration() + throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.enqueue(new MockResponse().setBody(this.metadata).setResponseCode(200)); + RelyingPartyRegistration registration = RelyingPartyRegistrations + .fromMetadataLocation(server.url("/").toString()) + .entityId("rp") + .build(); + RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); + assertThat(registration.getRegistrationId()).isEqualTo(details.getEntityId()); + assertThat(registration).isInstanceOf(OpenSamlRelyingPartyRegistration.class); + } + } + @Test public void collectionFromMetadataInputStreamWhenEmptyThenSaml2Exception() throws Exception { try (InputStream source = new ByteArrayInputStream("".getBytes())) { From 32e2735f4584fd2ed77acc620f994652e30f1270 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 10 Jul 2024 16:57:33 -0600 Subject: [PATCH 64/68] Clarify Valid Metadata Locations --- .../registration/RelyingPartyRegistrations.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java index d7382eafd33..a49db16ee68 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java @@ -45,8 +45,8 @@ private RelyingPartyRegistrations() { * Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0 * Asserting Party (IDP) metadata location. * - * Valid locations can be classpath- or file-based or they can be HTTP endpoints. Some - * valid endpoints might include: + * Valid locations can be classpath- or file-based or they can be HTTPS endpoints. + * Some valid endpoints might include: * *

 	 *   metadataLocation = "classpath:asserting-party-metadata.xml";
@@ -69,8 +69,8 @@ private RelyingPartyRegistrations() {
 	 * about the asserting party. Thus, you will need to remember to still populate
 	 * anything about the relying party, like any private keys the relying party will use
 	 * for signing AuthnRequests.
-	 * @param metadataLocation The classpath- or file-based locations or HTTP endpoints of
-	 * the asserting party metadata file
+	 * @param metadataLocation The classpath- or file-based locations or HTTPS endpoints
+	 * of the asserting party metadata file
 	 * @return the {@link RelyingPartyRegistration.Builder} for further configuration
 	 */
 	public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) {
@@ -130,8 +130,8 @@ public static RelyingPartyRegistration.Builder fromMetadata(InputStream source)
 	 * Return a {@link Collection} of {@link RelyingPartyRegistration.Builder}s based off
 	 * of the given SAML 2.0 Asserting Party (IDP) metadata location.
 	 *
-	 * Valid locations can be classpath- or file-based or they can be HTTP endpoints. Some
-	 * valid endpoints might include:
+	 * Valid locations can be classpath- or file-based or they can be HTTPS endpoints.
+	 * Some valid endpoints might include:
 	 *
 	 * 
 	 *   metadataLocation = "classpath:asserting-party-metadata.xml";
@@ -155,7 +155,7 @@ public static RelyingPartyRegistration.Builder fromMetadata(InputStream source)
 	 * about the asserting party. Thus, you will need to remember to still populate
 	 * anything about the relying party, like any private keys the relying party will use
 	 * for signing AuthnRequests.
-	 * @param location The classpath- or file-based locations or HTTP endpoints of the
+	 * @param location The classpath- or file-based locations or HTTPS endpoints of the
 	 * asserting party metadata file
 	 * @return the {@link Collection} of {@link RelyingPartyRegistration.Builder}s for
 	 * further configuration

From 804439552b7d31f0d38c4f7106c333fcfe9f4f2c Mon Sep 17 00:00:00 2001
From: gzhao9 <74684732+gzhao9@users.noreply.github.com>
Date: Tue, 25 Jun 2024 11:43:22 +0800
Subject: [PATCH 65/68] Merged changes from Example 1: Avoid duplicate creation
 of mocks in ActiveDirectoryLdapAuthenticationProviderTests.java

---
 ...ectoryLdapAuthenticationProviderTests.java | 20 ++-----------------
 1 file changed, 2 insertions(+), 18 deletions(-)

diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
index 4668d371745..ced26c10862 100644
--- a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
+++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
@@ -69,10 +69,11 @@ public class ActiveDirectoryLdapAuthenticationProviderTests {
 	ActiveDirectoryLdapAuthenticationProvider provider;
 
 	UsernamePasswordAuthenticationToken joe = UsernamePasswordAuthenticationToken.unauthenticated("joe", "password");
-
+	DirContext ctx;
 	@BeforeEach
 	public void setUp() {
 		this.provider = new ActiveDirectoryLdapAuthenticationProvider("mydomain.eu", "ldap://192.168.1.200/");
+		ctx = mock(DirContext.class);
 	}
 
 	@Test
@@ -90,8 +91,6 @@ public void successfulAuthenticationProducesExpectedAuthorities() throws Excepti
 	@Test
 	public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Exception {
 		String customSearchFilter = "(&(objectClass=user)(sAMAccountName={0}))";
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		DirContextAdapter dca = new DirContextAdapter();
 		SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
 		given(ctx.search(any(Name.class), eq(customSearchFilter), any(Object[].class), any(SearchControls.class)))
@@ -107,8 +106,6 @@ public void customSearchFilterIsUsedForSuccessfulAuthentication() throws Excepti
 	@Test
 	public void defaultSearchFilter() throws Exception {
 		final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		DirContextAdapter dca = new DirContextAdapter();
 		SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
 		given(ctx.search(any(Name.class), eq(defaultSearchFilter), any(Object[].class), any(SearchControls.class)))
@@ -126,8 +123,6 @@ public void defaultSearchFilter() throws Exception {
 	public void bindPrincipalAndUsernameUsed() throws Exception {
 		final String defaultSearchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
 		ArgumentCaptor captor = ArgumentCaptor.forClass(Object[].class);
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		DirContextAdapter dca = new DirContextAdapter();
 		SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
 		given(ctx.search(any(Name.class), eq(defaultSearchFilter), captor.capture(), any(SearchControls.class)))
@@ -153,8 +148,6 @@ public void setSearchFilterEmpty() {
 	@Test
 	public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws Exception {
 		this.provider = new ActiveDirectoryLdapAuthenticationProvider(null, "ldap://192.168.1.200/");
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		DirContextAdapter dca = new DirContextAdapter();
 		SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
 		given(ctx.search(eq(LdapNameBuilder.newInstance("DC=mydomain,DC=eu").build()), any(String.class),
@@ -167,8 +160,6 @@ public void nullDomainIsSupportedIfAuthenticatingWithFullUserPrincipal() throws
 
 	@Test
 	public void failedUserSearchCausesBadCredentials() throws Exception {
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		given(ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class)))
 			.willThrow(new NameNotFoundException());
 		this.provider.contextFactory = createContextFactoryReturning(ctx);
@@ -178,8 +169,6 @@ public void failedUserSearchCausesBadCredentials() throws Exception {
 	// SEC-2017
 	@Test
 	public void noUserSearchCausesUsernameNotFound() throws Exception {
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		given(ctx.search(any(Name.class), any(String.class), any(Object[].class), any(SearchControls.class)))
 			.willReturn(new EmptyEnumeration<>());
 		this.provider.contextFactory = createContextFactoryReturning(ctx);
@@ -196,8 +185,6 @@ public void sec2500PreventAnonymousBind() {
 	@Test
 	@SuppressWarnings("unchecked")
 	public void duplicateUserSearchCausesError() throws Exception {
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		NamingEnumeration searchResults = mock(NamingEnumeration.class);
 		given(searchResults.hasMore()).willReturn(true, true, false);
 		SearchResult searchResult = mock(SearchResult.class);
@@ -209,7 +196,6 @@ public void duplicateUserSearchCausesError() throws Exception {
 		assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
 			.isThrownBy(() -> this.provider.authenticate(this.joe));
 	}
-
 	static final String msg = "[LDAP: error code 49 - 80858585: LdapErr: DSID-DECAFF0, comment: AcceptSecurityContext error, data ";
 
 	@Test
@@ -357,8 +343,6 @@ DirContext createContext(Hashtable env) {
 
 	private void checkAuthentication(String rootDn, ActiveDirectoryLdapAuthenticationProvider provider)
 			throws NamingException {
-		DirContext ctx = mock(DirContext.class);
-		given(ctx.getNameInNamespace()).willReturn("");
 		DirContextAdapter dca = new DirContextAdapter();
 		SearchResult sr = new SearchResult("CN=Joe Jannsen,CN=Users", dca, dca.getAttributes());
 		@SuppressWarnings("deprecation")

From eeb5f0bbc4aa8eec982448345c6c95240fc2bf3a Mon Sep 17 00:00:00 2001
From: gzhao9 <74684732+gzhao9@users.noreply.github.com>
Date: Tue, 25 Jun 2024 11:46:50 +0800
Subject: [PATCH 66/68] Merged changes from Example 2: Avoid duplicate creation
 of mocks in org.springframework.security.authorization.method

---
 ...izationManagerAfterMethodInterceptorTests.java |  6 ++----
 ...zationManagerBeforeMethodInterceptorTests.java |  7 +++----
 .../method/MockSecurityContextHolderStrategy.java | 15 +++++++++++++++
 ...FilterAuthorizationMethodInterceptorTests.java |  8 ++++----
 ...FilterAuthorizationMethodInterceptorTests.java |  6 ++----
 5 files changed, 26 insertions(+), 16 deletions(-)
 create mode 100644 core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java

diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java
index e34b45a33cb..00c70003bcc 100644
--- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterMethodInterceptorTests.java
@@ -82,9 +82,8 @@ public void beforeWhenMockAuthorizationManagerThenCheckAndReturnedObject() throw
 
 	@Test
 	public void afterWhenMockSecurityContextHolderStrategyThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
 		Authentication authentication = TestAuthentication.authenticatedUser();
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		MethodInvocation invocation = mock(MethodInvocation.class);
 		AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager
 			.authenticated();
@@ -98,10 +97,9 @@ public void afterWhenMockSecurityContextHolderStrategyThenUses() throws Throwabl
 	// gh-12877
 	@Test
 	public void afterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
 		Authentication authentication = new TestingAuthenticationToken("john", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		MethodInvocation invocation = mock(MethodInvocation.class);
 		AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager
 			.authenticated();
diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java
index 8022609ac7b..a39b1853980 100644
--- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeMethodInterceptorTests.java
@@ -77,10 +77,9 @@ public void beforeWhenMockAuthorizationManagerThenCheck() throws Throwable {
 
 	@Test
 	public void beforeWhenMockSecurityContextHolderStrategyThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
 		Authentication authentication = new TestingAuthenticationToken("user", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		MethodInvocation invocation = mock(MethodInvocation.class);
 		AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager.authenticated();
 		AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
@@ -93,10 +92,10 @@ public void beforeWhenMockSecurityContextHolderStrategyThenUses() throws Throwab
 	// gh-12877
 	@Test
 	public void beforeWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+
 		Authentication authentication = new TestingAuthenticationToken("john", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		MethodInvocation invocation = mock(MethodInvocation.class);
 		AuthorizationManager authorizationManager = AuthenticatedAuthorizationManager.authenticated();
 		AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
diff --git a/core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java b/core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java
new file mode 100644
index 00000000000..eec416925e3
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/authorization/method/MockSecurityContextHolderStrategy.java
@@ -0,0 +1,15 @@
+package org.springframework.security.authorization.method;
+
+import org.springframework.security.core.context.*;
+
+import static org.mockito.BDDMockito.*;
+import static org.mockito.Mockito.*;
+
+public class MockSecurityContextHolderStrategy {
+	static SecurityContextHolderStrategy getmock(SecurityContextImpl securityContextImpl){
+
+		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+		given(strategy.getContext()).willReturn(securityContextImpl);
+		return strategy;
+	}
+}
diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java
index 48eec006186..8428ab1ec59 100644
--- a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptorTests.java
@@ -129,10 +129,10 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE
 
 	@Test
 	public void postFilterWhenMockSecurityContextHolderStrategyThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+
 		Authentication authentication = new TestingAuthenticationToken("john", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		String[] array = { "john", "bob" };
 		MockMethodInvocation invocation = new MockMethodInvocation(new TestClass(), TestClass.class,
 				"doSomethingArrayAuthentication", new Class[] { String[].class }, new Object[] { array }) {
@@ -150,10 +150,10 @@ public Object proceed() {
 	// gh-12877
 	@Test
 	public void postFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+
 		Authentication authentication = new TestingAuthenticationToken("john", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		String[] array = { "john", "bob" };
 		MockMethodInvocation invocation = new MockMethodInvocation(new TestClass(), TestClass.class,
 				"doSomethingArrayAuthentication", new Class[] { String[].class }, new Object[] { array }) {
diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java
index 30d40a369fe..e4d3e74d433 100644
--- a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java
+++ b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptorTests.java
@@ -189,10 +189,9 @@ public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationE
 
 	@Test
 	public void preFilterWhenMockSecurityContextHolderStrategyThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
 		Authentication authentication = new TestingAuthenticationToken("john", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		List list = new ArrayList<>();
 		list.add("john");
 		list.add("bob");
@@ -207,10 +206,9 @@ public void preFilterWhenMockSecurityContextHolderStrategyThenUses() throws Thro
 	// gh-12877
 	@Test
 	public void preFilterWhenStaticSecurityContextHolderStrategyAfterConstructorThenUses() throws Throwable {
-		SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
 		Authentication authentication = new TestingAuthenticationToken("john", "password",
 				AuthorityUtils.createAuthorityList("authority"));
-		given(strategy.getContext()).willReturn(new SecurityContextImpl(authentication));
+		SecurityContextHolderStrategy strategy = MockSecurityContextHolderStrategy.getmock(new SecurityContextImpl(authentication));
 		List list = new ArrayList<>();
 		list.add("john");
 		list.add("bob");

From d8c95e41dfb146f779912a5a66690adca8eb622c Mon Sep 17 00:00:00 2001
From: gzhao9 <74684732+gzhao9@users.noreply.github.com>
Date: Tue, 25 Jun 2024 11:48:01 +0800
Subject: [PATCH 67/68] Merged changes from Example 3: Avoid duplicate creation
 of mocks in ConcurrentSessionFilterTests.java

---
 .../ConcurrentSessionFilterTests.java         | 51 +++++--------------
 1 file changed, 14 insertions(+), 37 deletions(-)

diff --git a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java
index 32e6702fde7..1f72e245fb3 100644
--- a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java
+++ b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java
@@ -164,13 +164,8 @@ public void doFilterWhenNoSessionThenChainIsContinued() throws Exception {
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		RedirectStrategy redirect = mock(RedirectStrategy.class);
-		SessionRegistry registry = mock(SessionRegistry.class);
-		SessionInformation information = new SessionInformation("user", "sessionId",
-				new Date(System.currentTimeMillis() - 1000));
-		information.expireNow();
-		given(registry.getSessionInformation(anyString())).willReturn(information);
 		String expiredUrl = "/expired";
-		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredUrl);
+		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry(), expiredUrl);
 		filter.setRedirectStrategy(redirect);
 		MockFilterChain chain = new MockFilterChain();
 		filter.doFilter(request, response, chain);
@@ -199,13 +194,8 @@ public void doFilterWhenCustomRedirectStrategyThenCustomRedirectStrategyUsed() t
 		request.setSession(session);
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		RedirectStrategy redirect = mock(RedirectStrategy.class);
-		SessionRegistry registry = mock(SessionRegistry.class);
-		SessionInformation information = new SessionInformation("user", "sessionId",
-				new Date(System.currentTimeMillis() - 1000));
-		information.expireNow();
-		given(registry.getSessionInformation(anyString())).willReturn(information);
 		String expiredUrl = "/expired";
-		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredUrl);
+		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry(), expiredUrl);
 		filter.setRedirectStrategy(redirect);
 		filter.doFilter(request, response, new MockFilterChain());
 		verify(redirect).sendRedirect(request, response, expiredUrl);
@@ -218,13 +208,8 @@ public void doFilterWhenOverrideThenCustomRedirectStrategyUsed() throws Exceptio
 		request.setSession(session);
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		RedirectStrategy redirect = mock(RedirectStrategy.class);
-		SessionRegistry registry = mock(SessionRegistry.class);
-		SessionInformation information = new SessionInformation("user", "sessionId",
-				new Date(System.currentTimeMillis() - 1000));
-		information.expireNow();
-		given(registry.getSessionInformation(anyString())).willReturn(information);
 		final String expiredUrl = "/expired";
-		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredUrl + "will-be-overrridden") {
+		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry(), expiredUrl + "will-be-overrridden") {
 			@Override
 			protected String determineExpiredUrl(HttpServletRequest request, SessionInformation info) {
 				return expiredUrl;
@@ -241,12 +226,7 @@ public void doFilterWhenNoExpiredUrlThenResponseWritten() throws Exception {
 		MockHttpSession session = new MockHttpSession();
 		request.setSession(session);
 		MockHttpServletResponse response = new MockHttpServletResponse();
-		SessionRegistry registry = mock(SessionRegistry.class);
-		SessionInformation information = new SessionInformation("user", "sessionId",
-				new Date(System.currentTimeMillis() - 1000));
-		information.expireNow();
-		given(registry.getSessionInformation(anyString())).willReturn(information);
-		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
+		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry());
 		filter.doFilter(request, response, new MockFilterChain());
 		assertThat(response.getContentAsString()).contains(
 				"This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).");
@@ -259,12 +239,7 @@ public void doFilterWhenCustomLogoutHandlersThenHandlersUsed() throws Exception
 		MockHttpSession session = new MockHttpSession();
 		request.setSession(session);
 		MockHttpServletResponse response = new MockHttpServletResponse();
-		SessionRegistry registry = mock(SessionRegistry.class);
-		SessionInformation information = new SessionInformation("user", "sessionId",
-				new Date(System.currentTimeMillis() - 1000));
-		information.expireNow();
-		given(registry.getSessionInformation(anyString())).willReturn(information);
-		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
+		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry());
 		filter.setLogoutHandlers(new LogoutHandler[] { handler });
 		filter.doFilter(request, response, new MockFilterChain());
 		verify(handler).logout(eq(request), eq(response), any());
@@ -276,12 +251,7 @@ public void doFilterWhenCustomSecurityContextHolderStrategyThenHandlersUsed() th
 		MockHttpSession session = new MockHttpSession();
 		request.setSession(session);
 		MockHttpServletResponse response = new MockHttpServletResponse();
-		SessionRegistry registry = mock(SessionRegistry.class);
-		SessionInformation information = new SessionInformation("user", "sessionId",
-				new Date(System.currentTimeMillis() - 1000));
-		information.expireNow();
-		given(registry.getSessionInformation(anyString())).willReturn(information);
-		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
+		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(mockSessionRegistry());
 		SecurityContextHolderStrategy securityContextHolderStrategy = spy(
 				new MockSecurityContextHolderStrategy(new TestingAuthenticationToken("user", "password")));
 		filter.setSecurityContextHolderStrategy(securityContextHolderStrategy);
@@ -300,5 +270,12 @@ public void setLogoutHandlersWhenEmptyThenThrowsException() {
 		ConcurrentSessionFilter filter = new ConcurrentSessionFilter(new SessionRegistryImpl());
 		assertThatIllegalArgumentException().isThrownBy(() -> filter.setLogoutHandlers(new LogoutHandler[0]));
 	}
-
+	private SessionRegistry mockSessionRegistry(){
+		SessionRegistry registry = mock(SessionRegistry.class);
+		SessionInformation information = new SessionInformation("user", "sessionId",
+				new Date(System.currentTimeMillis() - 1000));
+		information.expireNow();
+		given(registry.getSessionInformation(anyString())).willReturn(information);
+		return registry;
+	}
 }

From f482471dd8b1b871a1a2bbe0bf6e3884f739ca9e Mon Sep 17 00:00:00 2001
From: gzhao9 <74684732+gzhao9@users.noreply.github.com>
Date: Tue, 25 Jun 2024 11:48:48 +0800
Subject: [PATCH 68/68] Merged changes from Example 4: Avoid duplicate creation
 of mocks in ExceptionTranslationFilterTests.java

---
 .../ExceptionTranslationFilterTests.java      | 33 +++++++------------
 1 file changed, 12 insertions(+), 21 deletions(-)

diff --git a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java
index 2dc36881cb7..753adb789dd 100644
--- a/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java
+++ b/web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java
@@ -91,9 +91,7 @@ public void testAccessDeniedWhenAnonymous() throws Exception {
 		request.setContextPath("/mycontext");
 		request.setRequestURI("/mycontext/secure/page.html");
 		// Setup the FilterChain to thrown an access denied exception
-		FilterChain fc = mock(FilterChain.class);
-		willThrow(new AccessDeniedException("")).given(fc)
-			.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		FilterChain fc = mockFilterChainWiehException(new AccessDeniedException(""));
 		// Setup SecurityContextHolder, as filter needs to check if user is
 		// anonymous
 		SecurityContextHolder.getContext()
@@ -119,9 +117,7 @@ public void testAccessDeniedWithRememberMe() throws Exception {
 		request.setContextPath("/mycontext");
 		request.setRequestURI("/mycontext/secure/page.html");
 		// Setup the FilterChain to thrown an access denied exception
-		FilterChain fc = mock(FilterChain.class);
-		willThrow(new AccessDeniedException("")).given(fc)
-			.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		FilterChain fc = mockFilterChainWiehException(new AccessDeniedException(""));
 		// Setup SecurityContextHolder, as filter needs to check if user is remembered
 		SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
 		securityContext.setAuthentication(
@@ -142,9 +138,7 @@ public void testAccessDeniedWhenNonAnonymous() throws Exception {
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.setServletPath("/secure/page.html");
 		// Setup the FilterChain to thrown an access denied exception
-		FilterChain fc = mock(FilterChain.class);
-		willThrow(new AccessDeniedException("")).given(fc)
-			.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		FilterChain fc = mockFilterChainWiehException(new AccessDeniedException(""));
 		// Setup SecurityContextHolder, as filter needs to check if user is
 		// anonymous
 		SecurityContextHolder.clearContext();
@@ -167,9 +161,7 @@ public void testLocalizedErrorMessages() throws Exception {
 		MockHttpServletRequest request = new MockHttpServletRequest();
 		request.setServletPath("/secure/page.html");
 		// Setup the FilterChain to thrown an access denied exception
-		FilterChain fc = mock(FilterChain.class);
-		willThrow(new AccessDeniedException("")).given(fc)
-			.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		FilterChain fc = mockFilterChainWiehException(new AccessDeniedException(""));
 		// Setup SecurityContextHolder, as filter needs to check if user is
 		// anonymous
 		SecurityContextHolder.getContext()
@@ -198,9 +190,7 @@ public void redirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthentication
 		request.setContextPath("/mycontext");
 		request.setRequestURI("/mycontext/secure/page.html");
 		// Setup the FilterChain to thrown an authentication failure exception
-		FilterChain fc = mock(FilterChain.class);
-		willThrow(new BadCredentialsException("")).given(fc)
-			.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		FilterChain fc = mockFilterChainWiehException(new BadCredentialsException(""));
 		// Test
 		RequestCache requestCache = new HttpSessionRequestCache();
 		ExceptionTranslationFilter filter = new ExceptionTranslationFilter(this.mockEntryPoint, requestCache);
@@ -223,9 +213,7 @@ public void redirectedToLoginFormAndSessionShowsOriginalTargetWithExoticPortWhen
 		request.setContextPath("/mycontext");
 		request.setRequestURI("/mycontext/secure/page.html");
 		// Setup the FilterChain to thrown an authentication failure exception
-		FilterChain fc = mock(FilterChain.class);
-		willThrow(new BadCredentialsException("")).given(fc)
-			.doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		FilterChain fc = mockFilterChainWiehException(new BadCredentialsException(""));
 		// Test
 		HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
 		ExceptionTranslationFilter filter = new ExceptionTranslationFilter(this.mockEntryPoint, requestCache);
@@ -265,8 +253,7 @@ public void thrownIOExceptionServletExceptionAndRuntimeExceptionsAreRethrown() t
 		filter.afterPropertiesSet();
 		Exception[] exceptions = { new IOException(), new ServletException(), new RuntimeException() };
 		for (Exception exception : exceptions) {
-			FilterChain fc = mock(FilterChain.class);
-			willThrow(exception).given(fc).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+			FilterChain fc = mockFilterChainWiehException(exception);
 			assertThatExceptionOfType(Exception.class)
 				.isThrownBy(() -> filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), fc))
 				.isSameAs(exception);
@@ -304,7 +291,11 @@ public void setMessageSourceWhenNotNullThenCanGet() {
 		filter.messages.getMessage(code);
 		verify(source).getMessage(eq(code), any(), any());
 	}
-
+	private FilterChain mockFilterChainWiehException(Object exception) throws ServletException, IOException {
+		FilterChain fc = mock(FilterChain.class);
+		willThrow((Throwable) exception).given(fc).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+		return fc;
+	}
 	private AuthenticationEntryPoint mockEntryPoint = (request, response, authException) -> response
 		.sendRedirect(request.getContextPath() + "/login.jsp");