44import com .box .l10n .mojito .entity .security .user .User ;
55import com .box .l10n .mojito .service .security .user .UserService ;
66import jakarta .annotation .PostConstruct ;
7+ import jakarta .servlet .http .HttpServletRequest ;
78import java .util .ArrayList ;
89import java .util .List ;
910import org .slf4j .Logger ;
2021import org .springframework .security .config .http .SessionCreationPolicy ;
2122import org .springframework .security .core .authority .SimpleGrantedAuthority ;
2223import org .springframework .security .oauth2 .jwt .Jwt ;
24+ import org .springframework .security .oauth2 .server .resource .web .DefaultBearerTokenResolver ;
2325import org .springframework .security .web .SecurityFilterChain ;
2426import org .springframework .util .StringUtils ;
2527
@@ -33,14 +35,20 @@ public class WebSecurityJWTConfig {
3335
3436 UserService userService ;
3537
38+ SecurityConfig securityConfig ;
39+
3640 @ Value ("${spring.security.oauth2.resourceserver.jwt.issuer-uri:}" )
3741 String issuerUri ;
3842
3943 @ Value ("${spring.security.oauth2.resourceserver.jwt.audience:}" )
4044 String audience ;
4145
42- public WebSecurityJWTConfig (UserService userService ) {
46+ private final DefaultBearerTokenResolver defaultBearerTokenResolver ;
47+
48+ public WebSecurityJWTConfig (UserService userService , SecurityConfig securityConfig ) {
4349 this .userService = userService ;
50+ this .securityConfig = securityConfig ;
51+ this .defaultBearerTokenResolver = new DefaultBearerTokenResolver ();
4452 }
4553
4654 @ PostConstruct
@@ -59,17 +67,15 @@ public void validateStatelessConfig() {
5967 @ Order (2 )
6068 SecurityFilterChain security (HttpSecurity http ) throws Exception {
6169
62- http .securityMatcher (
63- req -> {
64- String h = req .getHeader ("Authorization" );
65- return h != null && h .startsWith ("Bearer " );
66- });
70+ http .securityMatcher (req -> StringUtils .hasText (resolveAccessToken (req )));
6771
6872 applyStatelessSharedConfig (http );
6973
7074 http .oauth2ResourceServer (
7175 oauth ->
72- oauth .jwt (
76+ oauth
77+ .bearerTokenResolver (this ::resolveAccessToken )
78+ .jwt (
7379 jwtConfigurer -> {
7480 jwtConfigurer .jwtAuthenticationConverter (
7581 jwt -> {
@@ -128,7 +134,7 @@ public static void applyStatelessSharedConfig(HttpSecurity http) throws Exceptio
128134
129135 private User ensureUserFromJwt (Jwt jwt ) {
130136 String issuer = jwt .getIssuer () != null ? jwt .getIssuer ().toString () : null ;
131- IdStrategy strategy = pickStrategy (issuer );
137+ IdentityProviderType providerType = resolveProviderType (issuer );
132138
133139 String email = jwt .getClaimAsString ("email" );
134140 String upn = jwt .getClaimAsString ("preferred_username" );
@@ -139,10 +145,13 @@ private User ensureUserFromJwt(Jwt jwt) {
139145 String family = jwt .getClaimAsString ("family_name" );
140146
141147 String username =
142- switch (strategy ) {
143- case AZURE_AD -> firstNonBlank (localPart (upn ), localPart (email ), sub , oid );
144- case CLOUDFLARE -> firstNonBlank (localPart (email ), sub , oid , localPart (upn ));
145- case AUTO -> firstNonBlank (localPart (email ), localPart (upn ), sub , oid );
148+ switch (providerType ) {
149+ case AZURE_AD ->
150+ firstNonBlank (localPart (upn ), localPart (email ), sub , oid );
151+ case CLOUDFLARE ->
152+ firstNonBlank (localPart (email ), sub , oid , localPart (upn ));
153+ case AUTO ->
154+ firstNonBlank (localPart (email ), localPart (upn ), sub , oid );
146155 };
147156
148157 if (!StringUtils .hasText (username )) {
@@ -158,17 +167,81 @@ private User ensureUserFromJwt(Jwt jwt) {
158167 return userService .getOrCreateOrUpdateBasicUser (username , given , family , name );
159168 }
160169
161- private IdStrategy pickStrategy (String issuer ) {
170+ private String resolveAccessToken (HttpServletRequest request ) {
171+ if (request == null ) {
172+ return null ;
173+ }
174+
175+ CustomTokenHeader customTokenHeader = getCustomTokenHeader ();
176+ if (customTokenHeader == null ) {
177+ String token = defaultBearerTokenResolver .resolve (request );
178+ if (StringUtils .hasText (token )) {
179+ return token ;
180+ }
181+ return null ;
182+ }
183+
184+ String headerValue = request .getHeader (customTokenHeader .name ());
185+ if (!StringUtils .hasText (headerValue )) {
186+ return null ;
187+ }
188+
189+ String candidate = headerValue .trim ();
190+ if (!StringUtils .hasText (candidate )) {
191+ return null ;
192+ }
193+
194+ String prefix = customTokenHeader .prefix ();
195+ if (!StringUtils .hasText (prefix )) {
196+ return candidate ;
197+ }
198+
199+ if (candidate .startsWith (prefix )) {
200+ String stripped = candidate .substring (prefix .length ()).trim ();
201+ return StringUtils .hasText (stripped ) ? stripped : null ;
202+ }
203+
204+ return null ;
205+ }
206+
207+ private CustomTokenHeader getCustomTokenHeader () {
208+ SecurityConfig .Jwt jwtConfig = securityConfig .getJwt ();
209+ if (jwtConfig == null ) {
210+ return null ;
211+ }
212+
213+ String headerName = jwtConfig .getTokenHeaderName ();
214+ if (!StringUtils .hasText (headerName )) {
215+ return null ;
216+ }
217+
218+ String name = headerName .trim ();
219+ if (!StringUtils .hasText (name )) {
220+ return null ;
221+ }
222+
223+ String prefix = jwtConfig .getTokenHeaderPrefix ();
224+ if (StringUtils .hasText (prefix )) {
225+ prefix = prefix .trim ();
226+ prefix = StringUtils .hasText (prefix ) ? prefix : null ;
227+ } else {
228+ prefix = null ;
229+ }
230+
231+ return new CustomTokenHeader (name , prefix );
232+ }
233+
234+ private IdentityProviderType resolveProviderType (String issuer ) {
162235 if (issuer == null ) {
163- return IdStrategy .AUTO ;
236+ return IdentityProviderType .AUTO ;
164237 }
165238 if (issuer .contains ("login.microsoftonline.com" )) {
166- return IdStrategy .AZURE_AD ;
239+ return IdentityProviderType .AZURE_AD ;
167240 }
168241 if (issuer .contains ("cloudflareaccess.com" )) {
169- return IdStrategy .CLOUDFLARE ;
242+ return IdentityProviderType .CLOUDFLARE ;
170243 }
171- return IdStrategy .AUTO ;
244+ return IdentityProviderType .AUTO ;
172245 }
173246
174247 private String firstNonBlank (String ... values ) {
@@ -194,12 +267,14 @@ private String localPart(String value) {
194267 return value .trim ();
195268 }
196269
197- private enum IdStrategy {
270+ private enum IdentityProviderType {
198271 AUTO ,
199- AZURE_AD ,
200- CLOUDFLARE
272+ AZURE_AD ,
273+ CLOUDFLARE
201274 }
202275
276+ private record CustomTokenHeader (String name , String prefix ) {}
277+
203278 static class StatelessAuthenticationToken extends AbstractAuthenticationToken {
204279
205280 UserDetailsImpl userDetails ;
0 commit comments