2424import jakarta .servlet .ServletException ;
2525import jakarta .servlet .http .HttpServletRequest ;
2626import jakarta .servlet .http .HttpServletResponse ;
27+ import net .minidev .json .parser .JSONParser ;
28+ import net .minidev .json .parser .ParseException ;
2729import org .slf4j .Logger ;
2830import org .slf4j .LoggerFactory ;
2931import org .springframework .context .ApplicationEventPublisher ;
3032import org .springframework .security .authentication .AuthenticationManager ;
3133import org .springframework .security .authentication .event .AuthenticationSuccessEvent ;
3234import org .springframework .security .core .Authentication ;
3335import org .springframework .security .core .AuthenticationException ;
36+ import org .springframework .security .core .GrantedAuthority ;
37+ import org .springframework .security .core .authority .SimpleGrantedAuthority ;
3438import org .springframework .security .core .context .SecurityContextHolder ;
3539import org .springframework .security .web .util .matcher .AntPathRequestMatcher ;
3640import org .springframework .security .web .util .matcher .NegatedRequestMatcher ;
4044
4145import javax .annotation .Nonnull ;
4246import java .io .IOException ;
47+ import java .util .ArrayList ;
48+ import java .util .Arrays ;
49+ import java .util .List ;
4350
4451public class CustomHeaderAuthenticationFilter extends OncePerRequestFilter {
4552
@@ -55,11 +62,13 @@ public class CustomHeaderAuthenticationFilter extends OncePerRequestFilter {
5562 );
5663
5764 private final String usernameHeaderName ;
65+ private final String groupsHeaderName ;
5866
59- public CustomHeaderAuthenticationFilter (AuthenticationManager authenticationManager , ApplicationEventPublisher eventPublisher , String usernameHeaderName ) {
67+ public CustomHeaderAuthenticationFilter (AuthenticationManager authenticationManager , ApplicationEventPublisher eventPublisher , String usernameHeaderName , String groupsHeaderName ) {
6068 this .authenticationManager = authenticationManager ;
6169 this .eventPublisher = eventPublisher ;
6270 this .usernameHeaderName = usernameHeaderName ;
71+ this .groupsHeaderName = groupsHeaderName ;
6372 }
6473
6574 @ Override
@@ -84,7 +93,7 @@ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull Ht
8493 }
8594 }
8695
87- Authentication authRequest = new CustomHeaderAuthenticationToken (remoteUser , false );
96+ Authentication authRequest = new CustomHeaderAuthenticationToken (remoteUser , parseGroups ( request , remoteUser ), false );
8897 Authentication authResult = authenticationManager .authenticate (authRequest );
8998 if (authResult == null ) {
9099 throw new CustomHeaderAuthenticationException ("No authentication" );
@@ -93,10 +102,57 @@ protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull Ht
93102 SecurityContextHolder .getContext ().setAuthentication (authResult );
94103 eventPublisher .publishEvent (new AuthenticationSuccessEvent (authResult ));
95104 } catch (CustomHeaderAuthenticationException e ) {
105+ logger .warn ("Authentication failed: {}" , e .getMessage ());
106+ SecurityContextHolder .clearContext ();
107+ } catch (Exception e ) {
96108 logger .warn ("Authentication failed" , e );
97109 SecurityContextHolder .clearContext ();
98110 }
99111 chain .doFilter (request , response );
100112 }
101113
114+ private List <GrantedAuthority > parseGroups (HttpServletRequest request , String username ) {
115+ if (groupsHeaderName == null ) {
116+ return List .of ();
117+ }
118+ String remoteGroups = request .getHeader (groupsHeaderName );
119+ if (remoteGroups == null ) {
120+ logger .warn ("Header '{}' does not contain the groups of user '{}', the proxy should always override this header. This is a security risk, users might spoof groups!" , groupsHeaderName , username );
121+ return List .of ();
122+ }
123+
124+ remoteGroups = remoteGroups .strip ();
125+
126+ List <String > roles = new ArrayList <>();
127+
128+ if (remoteGroups .startsWith ("[" )) {
129+ // this is probably json
130+ try {
131+ Object value = new JSONParser (JSONParser .MODE_PERMISSIVE ).parse (remoteGroups );
132+ if (value instanceof List <?> valueList ) {
133+ valueList .forEach (o -> roles .add (o .toString ()));
134+ logger .debug ("Parsed groups header as JSON: {} -> {}" , groupsHeaderName , roles );
135+ }
136+ } catch (ParseException e ) {
137+ // Unable to parse JSON
138+ logger .debug ("Unable to parse groups header as JSON: {} -> {}" , groupsHeaderName , remoteGroups );
139+ }
140+ } else {
141+ if (remoteGroups .contains ("," )) {
142+ Arrays .stream (remoteGroups .split ("," )).forEach (g -> roles .add (g .strip ()));
143+ } else {
144+ // assuming it's a single role
145+ roles .add (remoteGroups );
146+ }
147+ logger .debug ("Parsed groups header as comma-separated string: {} -> {}" , groupsHeaderName , roles );
148+ }
149+
150+ List <GrantedAuthority > authorities = new ArrayList <>();
151+ for (String role : roles ) {
152+ String mappedRole = role .toUpperCase ().startsWith ("ROLE_" ) ? role : "ROLE_" + role ;
153+ authorities .add (new SimpleGrantedAuthority (mappedRole .toUpperCase ()));
154+ }
155+ return authorities ;
156+ }
157+
102158}
0 commit comments