Spring HttpSecurity: Custom web security expressions - spring

I am trying to configure the security of a new spring web application to check requests done against some of my urls.
Since none of the built-in expressions were valid for my logic, I decided to write my own, but it is not working at all.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/**/actuator/**").permitAll()
.antMatchers("/**/instances/**").permitAll()
//Custom expresion to check against
.antMatchers("/(?!login|user-profiles)/**").access("#checkAccess.hasRoleSelected()")
.anyRequest().authenticated()
.and()
.httpBasic().disable()
.addFilterBefore(new JWTLoginFilter(jwtConfig.getUri(), authenticationManager(), tokenService), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtTokenAuthenticationFilter(tokenService), UsernamePasswordAuthenticationFilter.class);
}
#Service
public class CheckAccess {
public boolean hasRoleSelected() {
return true;
}
}
As you can see in the documentation, to get this done you need a bean with a method returning a boolean value. While I do have both, the method is never called and no error is thrown.
What am I missing?
Btw, I am running 5.2.2 version of spring security.

Your antMatcher is invalid.
.antMatchers("/(?!login|user-profiles)/**").
Have a look at the allowed patterns in the AntPathMatcher doc.
It is basically, "?", "*" and "**".
You might want to give the regexMatcher a try, instead.

Related

Spring Security filter chain with custom user ID check [duplicate]

This question already has answers here:
How to fix role in Spring Security?
(2 answers)
Closed 6 months ago.
I am trying to use an expression-based check for an user ID path variable, so users can only access resources that belong to them. It is pretty clearly described in the Spring documentation. But I cannot access the bean, with the error that a String is provided.
This is my security filter chain and the bean:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors()
.and()
.csrf().disable()
.authorizeHttpRequests()
.antMatchers(WHITELIST_URLS).permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/api/**/users/{userId}/**").access("#userSecurity.checkUserId(authentication,#userId)")
.and()
.oauth2Login(oauth2login ->
oauth2login.loginPage("/oauth2/authorization/api-client-oidc"))
.oauth2Client(Customizer.withDefaults())
.build();
}
public static class UserSecurityConfig extends WebSecurityConfiguration {
#Bean("userSecurity")
private boolean checkUserId(Authentication authentication, String userId) {
return authentication.getPrincipal().equals(userId);
}
}
Error:
Required type: AuthorizationManager
<org.springframework.security.web.access.intercept.RequestAuthorizationContext>
Provided: String
I also have been trying to use an AuthorizationDecision (as lambda expression) but could not access the path variable.
Is the spring documentation wrong on this one? Been searching for quiet a while, but mostly found the same thing as in the Spring documentation.
Actually, I would like to manage this globally in the config and not on each mapping in the controllers by using the #PreAuthorize annotation.
Edit:
I have been unsuccessffuly trying to solve this using something like:
.access((authentication, object) ->
new AuthorizationDecision(object.getRequest().getServletPath().contains(
authentication.get().getName())))
or
.access((authentication, object) ->
new AuthorizationDecision(authentication.get().getPrincipal().equals(
object.getVariables().get("#userId"))))
I figured it out, the following example works. The more specific matcher has to be called first, otherwise it will not work.
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors()
.and()
.csrf()
.disable()
.authorizeHttpRequests()
.antMatchers("/api/v1/users/{userId}/**")
.access((authentication, object) -> new AuthorizationDecision(
object.getRequest().getServletPath().contains(
authentication.get().getName())
))
.antMatchers(WHITELIST_URLS)
.permitAll()
.antMatchers("/api/v1/**")
.authenticated()
.and()
.oauth2Login(oauth2login ->
oauth2login.loginPage("/oauth2/authorization/api-client-oidc"))
.oauth2Client(Customizer.withDefaults())
.build();
}

Endpoint not accessible after Boot 2.6 upgrade

After upgrading the Boot version in my project to 2.6.0, my endpoint is no longer accessible, I'm automatically redirected to the login page even though I configured it with a permitAll() directive:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("presentations")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
// ...
}
It seems this is actually related to how Spring Boot processes the mvcMatchers values after the upgrade:
The default strategy for matching request paths against registered Spring MVC handler mappings has changed from AntPathMatcher to PathPatternParser.
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#deferred-openid-connect-discovery
And this new setup requires my presentations pattern to start with /:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/presentations")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
Alternatively, if I had several routes and I need a quick fix I could set up the following application property to revert this to its previous behavior:
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
BTW, path-pattern-parser is the default value, as it seems to be more efficient... here is some additional information on this and on the differences between PathPatternParser and AntPathMatcher:
https://spring.io/blog/2020/06/30/url-matching-with-pathpattern-in-spring-mvc
https://docs.spring.io/spring-framework/docs/5.3.13/reference/html/web.html#mvc-ann-requestmapping-uri-templates
EDIT: I also realized that using antMatchers() made some of my MockMvc tests fail, this was a bug that got fixed in Boot 2.6.1.

Spring security - create 2 filter chains with specific matchers

I'm in the process of implementing ADFS support to an existing spring project.
Since we already have our own JWT authentication, which we want to work in parallel to ADFS authentication, I want to implement a new filter chain that will handle only certain API request paths.
By this I mean I want to create:
ADFS filter chain that will handle all the /adfs/saml/** API calls
Leave the default filter chain that will handle all the rest API calls
I'm using the ADFS spring security lib that defines the filter chain like this:
public abstract class SAMLWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
//some code
protected final HttpSecurity samlizedConfig(final HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(samlEntryPoint())
.and()
.csrf().ignoringAntMatchers("/saml/**")
.and()
.authorizeRequests().antMatchers("/saml/**").permitAll()
.and()
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(filterChainProxy(), BasicAuthenticationFilter.class);
// store CSRF token in cookie
if (samlConfigBean().getStoreCsrfTokenInCookie()) {
http.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
return http;
}
}
And I extend this class:
#EnableWebSecurity
#Configuration
#Order(15)
#RequiredArgsConstructor
public class ADFSSecurityConfiguration extends SAMLWebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.authorizeRequests()
.antMatchers("/adfs")
.authenticated();
}
}
But when debugging I see that this new filter chain is set to match "any" request.
So I'm probably setting the matchers wrong.
Actually, after reading the official docs the answer was a simple one:
(see "Creating and Customizing Filter Chains" section)
#Override
protected void configure(final HttpSecurity http) throws Exception {
samlizedConfig(http)
.antMatcher("/adfs/**");
}
It should not be put after .authorizeRequests() but strait on the first matcher.

Spring Boot + Spring Security permitAll() and addFilter() configuration does not have effect

URL patter with /login should go through the LoginFilter where the user id and password is validated - working fine
URL pattern with /users/register should not go through any of the filer but it is always passing through the JWTAuthentication filter - not working fine
All other URL pattern should go through the JWTAuthentication filter for authorization - working fine
Below is my code for Security Configuration. Kindly help me with what I am missing in this code. How do I configure the filter such that JWT authentication happens for the URL pattern other than /login and /register
Spring-security-core:4.2.3, spring-boot:1.5.4
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/users/register").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new LoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new NoLoginAuthenticationFilter("/users/register"), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter("/**", authenticationManager()),
UsernamePasswordAuthenticationFilter.class);
}
What you want is to ignore certain URLs.
For this override the configure method that takes WebSecurity object and ignore the pattern.
Try adding below method override to your config class.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/users/register/**");
}

Combination of HTTP Filter not working with ignore() method from WebSecurity

I have made Filter which is added like that in Spring Security:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/", "/#/", "resources/static/**").permitAll().anyRequest().permitAll()
.antMatchers(HttpMethod.POST, "/api/auth").permitAll().anyRequest().permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/api/auth", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
Unfortunately request /api/auth has no controller in my Spring Boot app. It only works in filter. So the frontend hit this api to authenticate yourself.
In my app I also had to add
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/static/**").anyRequest();
}
to ignore static files because the first method doesn't exclude it properly. And the ignore() cause the problem because it calls /api/auth which has no equivalent in backend but only exists in .js files. What is more ignore() method ignore all requests from the path in which /api/auth is - this api returns 404 when I want to call when ignore() method is uncommented. So the question is how to make a work around to make it work properly?

Resources