spring basic authentication plus ip filter - spring-security

I´m trying to configure a basic authentication plus ip filter, the basic auth, works fine with this configure:
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.httpBasic();
}
I would like to add ip filter, I´ve read some about hasIpAddress but I don´t know how use it.

For XML configuraton see Spring Security Reference:
26.2 Web Security Expressions
To use expressions to secure individual URLs, you would first need to set the use-expressions attribute in the <http> element to true. Spring Security will then expect the access attributes of the <intercept-url> elements to contain Spring EL expressions. The expressions should evaluate to a Boolean, defining whether access should be allowed or not. For example:
<http>
<intercept-url pattern="/admin*"
access="hasRole('admin') and hasIpAddress('192.168.1.0/24')"/>
...
</http>
Here we have defined that the "admin" area of an application (defined by the URL pattern) should only be available to users who have the granted authority "admin" and whose IP address matches a local subnet. We’ve already seen the built-in hasRole expression in the previous section. The expression hasIpAddress is an additional built-in expression which is specific to web security. It is defined by the WebSecurityExpressionRoot class, an instance of which is used as the expression root object when evaluation web-access expressions.
For Java configuration see ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#access:
Parameters:
attribute - the expression to secure the URLs (i.e. "hasRole('ROLE_USER') and hasRole('ROLE_SUPER')")
Your modified code:
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable()
.authorizeRequests()
.anyRequest().access("isFullyAuthenticated() and hasIpAddress('192.168.1.0/24')")
.and()
.httpBasic();
}

Related

How to configure Spring Security URL authorisation?

I have configured my SecurityFilterChain thus:
#EnableWebSecurity
public class WebSecurityConfig {
....
#Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, "/api/user/*").access("hasRole('ADMIN')")
.antMatchers(HttpMethod.POST, "/api/user").access("hasRole('ADMIN')")
.antMatchers("/auth/login").anonymous()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
Yet, the URL paths are open to any authenticated user irregardless of the assigned role.
I have debugged the request filter to confirm that the Principal has the right role yet a USER role can call the protected URLs successfully.
I am using Spring Boot 2.7.5.
If the path you're calling matches the authorization rule that you've declared as the last one (i.e. anyRequest().authenticated()), that implies that your test-requests don't match any of your rules that are meant to guard URLs that should be accessible only for Admins, namely:
.antMatchers(HttpMethod.DELETE, "/api/user/*").access("hasRole('ADMIN')")
.antMatchers(HttpMethod.POST, "/api/user").access("hasRole('ADMIN')")
Reminder: the matching rule declared first always weens
So, either HTTP-method or URL don't match (or both). For instance, if you're sending GET request, these restrictions would not be applied.
Regarding the URL, it should match exactly because you're using antMatchers(). I.e. path "/api/user" would not match other existing aliases of that path like "/api/user/" (more on that see here).
That's one of the reasons why in Spring Security 6.0 antMatchers() (as well as mvcMathcers() and regexMatchers()) have been removed from the API and replaced requestMatchers().
So make sure that HTTP-method is correct and path you're calling matchers exactly, and consider updating the Spring dependencies and switching to using new request-securing methods.
If you have no planes to update soon, then you can make use of the mvcMatchers(), which use Spring MVC matching rules (i.e. they take into consideration all the existing aliases of the given path), instead of antMatchers().
Here's an example of how your configuration might be implemented with Spring Security 6.0 and Lambda DSL (if you feel more comfortable with chaining configuration options using and() this flavor of DSL is still supported as well):
#Configuration
public class SecurityConfig {
#Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.DELETE, "/api/user/*").hasRole("ADMIN") // in Spring Security 6.0 method access() has been changed, and you don't need it anyway to verify the Role
.requestMatchers(HttpMethod.POST, "/api/user").hasRole("ADMIN")
.requestMatchers("/auth/login").anonymous()
.anyRequest().authenticated()
)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}

Oauth2Login for only specific urls

I'm trying to have an oauth2 configuration setup for login through Spring Security. But only for specific urls.
My security config looks as follows.
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secured/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());
}
Basically I only want the oauth2Login to be trigger for urls that start with /secured. It seems to almost work, the only issue is whenever Spring trys to re-direct my session to google for authentication it triggers a 404.
The standard redirect for an oauth authentication should fire off to http://localhost:8080/oauth2/authorization/google, which my application attempts to do, but it 404's.
I'm assuming the http://localhost:8080/oauth2/authorization/google url is being blocked by some type of security config? But I cannot for the life of me figure out why.
I'm guessing I need to find the right combination of .permitAll() for any request going to "/oauth2/authorization/**"
I've tried this below.
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secured/**")
.authorizeRequests()
.antMatchers("/oauth2/authorization/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());
}
But that won't work.....does anyone see my issue? I have no other security config conflicting with this one, I'm at a bit of a loss.
According to previous answer, the version with no lambda-style should look like this right?
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests
.antMatchers("/secured/**").authenticated()
.anyRequest().authenticated()
.and()
.oauth2Login()
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());
}
If we want oauth2Login only for "/secured/**", why have we to use authenticated() for antMatcher("/secured/**") and for anyRequest() (and not permitAll() for anyRequest())?
Thank you.
The first step to understanding the issue here is understanding the difference between http.antMatcher() and http.authorizeRequests().
Let's look at the following configuration.
(I am using the lambda-style configuration, available as of Spring Security 5.2, to make it more readable)
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secured/**")
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(oauth2Login -> oauth2Login
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService())
);
}
Here, we are specifying that HTTP security will only be invoked when matching "/secured/**".
In other words, the request will only be processed by the SecurityFilterChain if it matches "/secured/**".
This is a problem because the SecurityFilterChain is what initiates the Authorization Request from "/oauth2/authorization/google".
However, the SecurityFilterChain is not called because "/oauth2/authorization/google" does not match "/secured/**".
Consider the following configuration instead.
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
)
.oauth2Login(oauth2Login -> oauth2Login
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService())
);
}
Here, the HTTP security will be invoked for all requests.
However, only requests that match "/secured/**" will require the user to authenticate.
The answer of #eleftheria-stein-kousathana brought me to another possible solution.
As stated it is important to know that the OAuth Endpoints won't called because the security config for them is bound to the antMatcher() call directly following the HttpSecurity object.
But as stated in the docs you can also change the baseUris of the redirectionEndpoint and the authorizationEndpoint
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secured/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
//New Code Starting here
.authorizationEndpoint()
.baseUri("/secured/oauth2/authorization")
.and()
.redirectionEndpoint()
.baseUri("/secured/oauth2/code/*")
.and()
//new code ending here
.clientRegistrationRepository(clientRegistrationRepository())
.authorizedClientService(authorizedClientService());
}
Be sure to change the redirectUriTemplate of your clientRegistration and also the redirect uri configured at your AuthorizationServer
AND be sure to add the asterisk at the end of the redirectionEndpoint.baseUri ... that cost me some minutes to figure out what was wrong :-)

Spring Security java config - why does order of options matter?

I've spent the last 3-4 days working with Spring Security (4.0.2) for the first time and have been able to get it to work thanks to pouring over the Spring Security samples, numerous posts at SO and other blogs.
However, as I've been trying out different options I've was stumped for several hours with adding sessionManagement to HttpSecurity. It appears that the order of the options matters and I'm really curious why that is and why it doesn't seem to be mentioned anywhere in the Spring Security documentation, or anywhere else that I could find for that matter?
For example, if you place sessionManagement first then the next configuration (authorizeRequests in this case, but it doesn't matter which one is next) gets a syntax error noted in the code sample below
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.invalidSessionUrl("/login?invalid=1")
.maximumSessions(1)
.expiredUrl("/login?time=1")
.maxSessionsPreventsLogin(true);
.authorizeRequests() //<<< The method authorizeRequests() is undefined for the type SecurityConfig
.antMatchers("/", "/home", "/login**", "/thankyou").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?err=1")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/thankyou")
.deleteCookies( "JSESSIONID" )
.invalidateHttpSession(false);
}
It appears sessionManagement must be the last config. Why?
Also, once I put sessionManagement last it made a different where the invalidSessionUrl method is placed. I originally had it last as shown below with the syntax error:
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?time=1")
.maxSessionsPreventsLogin(true)
.invalidSessionUrl("/login?invalid=1");
//<<< The method invalidSessionUrl(String) is undefined for the type SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer
After a couple of hours I figured out that invalidSessionUrl and maximumSessions are methods of SessionManagementConfigurer and expiredUrl and maxSessionsPreventsLogin belong to SessionManagementConfigurer.ConcurrencyControlConfigurer and the only way the code will compile is if the ConcurrencyControlConfigurer methods are placed after the SessionManagementConfigurer methods.
Again, I'm would really like to know why so I can be alert to this sort of thing as I learn other Spring interfaces. In other words, I'd really like to know if there is an architectural design or programming convention involved here that I'm not yet aware of as a newbie to Spring.
BTW, the webinar by Rob Winch was super helpful! Here's the link if anyone is interested: Webinar Replay: Spring Security 3.2
Add a couple of and() and it will compile:
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.invalidSessionUrl("/login?invalid=1")
.maximumSessions(1)
.expiredUrl("/login?time=1")
.maxSessionsPreventsLogin(true)
.and()
.and()
.authorizeRequests()
...
One indentation level in indicates a new return type (only for clarity). The and() returns to the previous type.

Spring Boot, Spring Security and Thymeleaf: Apply CsrfFilter to website with form

I'm using Spring Security with Thymeleaf and want to create a login and a register form on different sites that make both use of CSRF protection. Protecting the login site is easy, as with the folloing WebSecurity configuration
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
Spring supports adding CSRF protection in general by the Security Filter Chain that is build in the configure method. This Filter Chain contains a CSRFFilter that adds/evaluates the CSRF token. This Filter Chain is then used for all matches defined in the above configuration. The mechanism of getting the Filters that are applied to a request can be found here in the method
doFilterInternal(ServletRequest, ServletResponse, FilterChain)
The problem is, if I add the "/register" site to this configuration, the user is redirected to the "/login" site first. If I don't add it to the above config, the mentioned FilterChain is not applied (and so not the CsrfFilter).
So what I want is to reuse the CsrfFilter in the Filter Chain of the "/register" site, but I don't know how to do that.
I'd prefer this approach to other ideas like writing a custom CSRF filter as suggested here or here.
From all of this i understood the problem is that you want people to access /register without needing to login first. Which is a simple fix:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.requestMatchers()
// add this line
.antMatchers("/register").permitAll().and
//
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
Turned out that the Spring Security Filter chain is applied to all endpoints mentioned in the list provided to requestMatchers().antMatchers().
So to use CSRF protection for a site that is not the login site, I just had to add it to this list and then permit all access to it, so there is no redirect to the login page. My final config looks like this
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.requestMatchers()
// consider these patterns for this config and the Security Filter Chain
.antMatchers("/login", "/register", "/oauth/authorize", "/oauth/confirm_access", "/oauth/token_key",
"/oauth/check_token", "/oauth/error")
.and()
// define authorization behaviour
.authorizeRequests()
// /register is allowed by anyone
.antMatchers("/register").permitAll()
// /oauth/authorize needs authentication; enables redirect to /login
.antMatchers("/oauth/authorize").authenticated()
// any other request in the patterns above are forbidden
.anyRequest().denyAll()
.and()
.formLogin()
// we have a custom login page at /login
// that is permitted to everyone
.loginPage("/login").permitAll();
}

Spring Security 3.2.0RC2 logout url POST only?

I am experimenting with Spring Security 3.2.0.RC2 using javaconfig and it appears that the logout url is POST only. Is this by design and is there any way to make it logout a user with a GET request?
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about", "/password").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login")
.permitAll();
}
This is intentional and is documented within the CSRF documentation. The reason is to prevent CSRF attacks that forcibly log users out of your application. If you would like to support non-POST requests you can do so with the following Java Configuration:
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
You can also find information about configuring log out on the Javadoc of the LogoutConfigurer (i.e. the object returned by the http.logout() method).

Resources