I am trying to secure my Spring Boot 3 (SNAPSHOT) application with SAML SSO to access the "index.html" (Angular frontend) and to secure the API with JWT.
I have both SSO and JWT working to secure the whole application (both access to application and to the API) but I'm not able to configure the SecurityConfig in a way that every user that hits the "/" path (https://localhost:9091/) gets redirected to the IDP and after authenticating granting full access to the application (access to the static resources - compiled angular files) and at the same time that every user that hits "/api/**" path is required to have a valid JWT token, without the requirement of authenticating against the IDP.
Below is my code, pretty mixed up. Also attached is my project on GitHub.
@Configuration
public class SecurityConfiguration {
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(groupsConverter());
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/").authenticated())
.saml2Login(saml2 -> saml2
.authenticationManager(new ProviderManager(authenticationProvider)))
.saml2Logout(withDefaults());
http.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/saml2/service-provider-metadata/**").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/test/**").permitAll()
.requestMatchers("/").permitAll()
.requestMatchers(h2ConsolePath + "/**").permitAll()
.anyRequest().authenticated());
// fix H2 database console: Refused to display ' in a frame because it set 'X-Frame-Options' to 'deny'
http.headers().frameOptions().sameOrigin();
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
private Converter<OpenSaml4AuthenticationProvider.ResponseToken, Saml2Authentication> groupsConverter() {
Converter<ResponseToken, Saml2Authentication> delegate =
OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter();
return (responseToken) -> {
Saml2Authentication authentication = delegate.convert(responseToken);
Saml2AuthenticatedPrincipal principal = (Saml2AuthenticatedPrincipal) authentication.getPrincipal();
List<String> groups = principal.getAttribute("groups");
//log.info()
Set<GrantedAuthority> authorities = new HashSet<>();
if (groups != null) {
groups.stream().map(SimpleGrantedAuthority::new).forEach(authorities::add);
} else {
authorities.addAll(authentication.getAuthorities());
}
return new Saml2Authentication(principal, authentication.getSaml2Response(), authorities);
};
}
@Value("${spring.h2.console.path}")
private String h2ConsolePath;
@Autowired
UserDetailsServiceImpl userDetailsService;
@Autowired
private AuthEntryPointJwt unauthorizedHandler;
@Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
}