1717package org .springframework .security .web .authentication ;
1818
1919import java .io .IOException ;
20+ import java .util .ArrayList ;
21+ import java .util .Arrays ;
2022import java .util .LinkedHashMap ;
23+ import java .util .List ;
24+ import java .util .stream .Collectors ;
2125
2226import jakarta .servlet .ServletException ;
2327import jakarta .servlet .http .HttpServletRequest ;
2428import jakarta .servlet .http .HttpServletResponse ;
2529import org .apache .commons .logging .Log ;
2630import org .apache .commons .logging .LogFactory ;
31+ import org .jspecify .annotations .Nullable ;
2732
2833import org .springframework .beans .factory .InitializingBean ;
2934import org .springframework .core .log .LogMessage ;
3237import org .springframework .security .web .util .matcher .ELRequestMatcher ;
3338import org .springframework .security .web .util .matcher .RequestMatcher ;
3439import org .springframework .security .web .util .matcher .RequestMatcherEditor ;
40+ import org .springframework .security .web .util .matcher .RequestMatcherEntry ;
3541import org .springframework .util .Assert ;
3642
3743/**
@@ -64,22 +70,63 @@ public class DelegatingAuthenticationEntryPoint implements AuthenticationEntryPo
6470
6571 private static final Log logger = LogFactory .getLog (DelegatingAuthenticationEntryPoint .class );
6672
67- private final LinkedHashMap < RequestMatcher , AuthenticationEntryPoint > entryPoints ;
73+ private final List < RequestMatcherEntry < AuthenticationEntryPoint > > entryPoints ;
6874
6975 @ SuppressWarnings ("NullAway.Init" )
7076 private AuthenticationEntryPoint defaultEntryPoint ;
7177
72- public DelegatingAuthenticationEntryPoint (LinkedHashMap <RequestMatcher , AuthenticationEntryPoint > entryPoints ) {
78+ /**
79+ * Creates a new instance with the provided mappings.
80+ * @param entryPoints the mapping of {@link RequestMatcher} to
81+ * {@link AuthenticationEntryPoint}. Cannot be null or empty.
82+ * @param defaultEntryPoint the default {@link AuthenticationEntryPoint}. Cannot be
83+ * null.
84+ */
85+ public DelegatingAuthenticationEntryPoint (AuthenticationEntryPoint defaultEntryPoint ,
86+ RequestMatcherEntry <AuthenticationEntryPoint >... entryPoints ) {
87+ Assert .notEmpty (entryPoints , "entryPoints cannot be empty" );
88+ Assert .notNull (defaultEntryPoint , "defaultEntryPoint cannot be null" );
89+ this .entryPoints = Arrays .asList (entryPoints );
90+ this .defaultEntryPoint = defaultEntryPoint ;
91+ }
92+
93+ /**
94+ * Creates a new instance with the provided mappings.
95+ * @param defaultEntryPoint the default {@link AuthenticationEntryPoint}. Cannot be
96+ * null.
97+ * @param entryPoints the mapping of {@link RequestMatcher} to
98+ * {@link AuthenticationEntryPoint}. Cannot be null or empty.
99+ */
100+ public DelegatingAuthenticationEntryPoint (AuthenticationEntryPoint defaultEntryPoint ,
101+ List <RequestMatcherEntry <AuthenticationEntryPoint >> entryPoints ) {
102+ Assert .notEmpty (entryPoints , "entryPoints cannot be empty" );
103+ Assert .notNull (defaultEntryPoint , "defaultEntryPoint cannot be null" );
73104 this .entryPoints = entryPoints ;
105+ this .defaultEntryPoint = defaultEntryPoint ;
106+ }
107+
108+ /**
109+ * Creates a new instance.
110+ * @param entryPoints
111+ * @deprecated Use
112+ * {@link #DelegatingAuthenticationEntryPoint(AuthenticationEntryPoint, List)}
113+ */
114+ @ Deprecated (forRemoval = true )
115+ public DelegatingAuthenticationEntryPoint (LinkedHashMap <RequestMatcher , AuthenticationEntryPoint > entryPoints ) {
116+ this .entryPoints = entryPoints .entrySet ()
117+ .stream ()
118+ .map ((e ) -> new RequestMatcherEntry <>(e .getKey (), e .getValue ()))
119+ .collect (Collectors .toList ());
74120 }
75121
76122 @ Override
77123 public void commence (HttpServletRequest request , HttpServletResponse response ,
78124 AuthenticationException authException ) throws IOException , ServletException {
79- for (RequestMatcher requestMatcher : this .entryPoints .keySet ()) {
125+ for (RequestMatcherEntry <AuthenticationEntryPoint > entry : this .entryPoints ) {
126+ RequestMatcher requestMatcher = entry .getRequestMatcher ();
80127 logger .debug (LogMessage .format ("Trying to match using %s" , requestMatcher ));
81128 if (requestMatcher .matches (request )) {
82- AuthenticationEntryPoint entryPoint = this . entryPoints . get ( requestMatcher );
129+ AuthenticationEntryPoint entryPoint = entry . getEntry ( );
83130 logger .debug (LogMessage .format ("Match found! Executing %s" , entryPoint ));
84131 entryPoint .commence (request , response , authException );
85132 return ;
@@ -92,7 +139,10 @@ public void commence(HttpServletRequest request, HttpServletResponse response,
92139
93140 /**
94141 * EntryPoint which is used when no RequestMatcher returned true
142+ * @deprecated Use
143+ * {@link #DelegatingAuthenticationEntryPoint(AuthenticationEntryPoint, List)}
95144 */
145+ @ Deprecated (forRemoval = true )
96146 public void setDefaultEntryPoint (AuthenticationEntryPoint defaultEntryPoint ) {
97147 this .defaultEntryPoint = defaultEntryPoint ;
98148 }
@@ -103,4 +153,76 @@ public void afterPropertiesSet() {
103153 Assert .notNull (this .defaultEntryPoint , "defaultEntryPoint must be specified" );
104154 }
105155
156+ /**
157+ * Creates a new {@link Builder}
158+ * @return the new {@link Builder}
159+ */
160+ public static Builder builder () {
161+ return new Builder ();
162+ }
163+
164+ /**
165+ * Used to build a new instance of {@link DelegatingAuthenticationEntryPoint}.
166+ *
167+ * @author Rob Winch
168+ * @since 7.0
169+ */
170+ public static class Builder {
171+
172+ private @ Nullable AuthenticationEntryPoint defaultEntryPoint ;
173+
174+ private List <RequestMatcherEntry <AuthenticationEntryPoint >> entryPoints = new ArrayList <RequestMatcherEntry <AuthenticationEntryPoint >>();
175+
176+ /**
177+ * Set the default {@link AuthenticationEntryPoint} if none match. The default is
178+ * to use the first {@link AuthenticationEntryPoint} added in
179+ * {@link #addEntryPointFor(AuthenticationEntryPoint, RequestMatcher)}.
180+ * @param defaultEntryPoint the default {@link AuthenticationEntryPoint} to use.
181+ * @return the {@link Builder} for further customization.
182+ */
183+ public Builder defaultEntryPoint (@ Nullable AuthenticationEntryPoint defaultEntryPoint ) {
184+ this .defaultEntryPoint = defaultEntryPoint ;
185+ return this ;
186+ }
187+
188+ /**
189+ * Adds an {@link AuthenticationEntryPoint} for the provided
190+ * {@link RequestMatcher}.
191+ * @param entryPoint the {@link AuthenticationEntryPoint} to use. Cannot be null.
192+ * @param requestMatcher the {@link RequestMatcher} to use. Cannot be null.
193+ * @return the {@link Builder} for further customization.
194+ */
195+ public Builder addEntryPointFor (AuthenticationEntryPoint entryPoint , RequestMatcher requestMatcher ) {
196+ Assert .notNull (entryPoint , "entryPoint cannot be null" );
197+ Assert .notNull (requestMatcher , "requestMatcher cannot be null" );
198+ this .entryPoints .add (new RequestMatcherEntry <>(requestMatcher , entryPoint ));
199+ return this ;
200+ }
201+
202+ /**
203+ * Builds the {@link AuthenticationEntryPoint}. If the
204+ * {@link #defaultEntryPoint(AuthenticationEntryPoint)} is not set, then the first
205+ * {@link #addEntryPointFor(AuthenticationEntryPoint, RequestMatcher)} is used as
206+ * the default. If the {@link #defaultEntryPoint(AuthenticationEntryPoint)} is not
207+ * set and there is only a single
208+ * {@link #addEntryPointFor(AuthenticationEntryPoint, RequestMatcher)}, then the
209+ * {@link AuthenticationEntryPoint} is returned rather than wrapping it in
210+ * {@link DelegatingAuthenticationEntryPoint}.
211+ * @return the {@link AuthenticationEntryPoint} to use.
212+ */
213+ public AuthenticationEntryPoint build () {
214+ Assert .notEmpty (this .entryPoints , "entryPoints cannot be empty" );
215+ AuthenticationEntryPoint defaultEntryPoint = this .defaultEntryPoint ;
216+ if (defaultEntryPoint == null ) {
217+ AuthenticationEntryPoint firstAuthenticationEntryPoint = this .entryPoints .get (0 ).getEntry ();
218+ if (this .entryPoints .size () == 1 ) {
219+ return firstAuthenticationEntryPoint ;
220+ }
221+ defaultEntryPoint = firstAuthenticationEntryPoint ;
222+ }
223+ return new DelegatingAuthenticationEntryPoint (defaultEntryPoint , this .entryPoints );
224+ }
225+
226+ }
227+
106228}
0 commit comments