We use the Spring MVC framework (incl. Spring Security) to implement both a web page and a REST web service. Both are supposed to support username/password authentication. However, they should implement two separate security realms with separate user databases.
The problem we're facing is that Spring security merges the two separate authentication providers into a single list of authentication providers and checks all username/password pairs against both providers. How can we have separate authentication providers for the two realms?
We have tried to separate the realms by using separate roles/authorities. However, if the credentials of the web pages are used to authenticate against the web service, it results in a 403 error (Forbidden) instead of a 401 error (Unauthorized). And since we have to keep perfect backward compatibility with pre-Spring implementation, this is a problem.
Furthermore, we've also tried to split the configuration into several non-inner classes (as proposed in https://stackoverflow.com/a/32756938/413337). However, that didn't help either.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public static class WebServiceConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new WebServiceUsernamePasswordAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/service/**")
.hasRole("SERVICE")
.and()
.anonymous().disable()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class WebPagesConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new WebPagesUsernamePasswordAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/", false);
}
}
}
Update:
We have implemented a solution by creating a copy of the BasicAuthenticationFilter and UsernamePasswordAuthenticationToken class and using them for the web service only. However, we would still prefer a solution without code duplication. Could the approach be to create separate authentication manager instances for the two realms?
Related
I'd like my spring boot application to serve a protected frontend, as well as being an API resource server for said frontend at the same time, but I can't get the oauth stuff working.
What I want is the spring boot application to return a 302 redirect to the oauth server (gitlab in my case) when the browser requests the index.html without a token, so the user is sent to the login form. But I also want that the API to return a 401 when the API is called without a token, as I think a 302 redirect to a login page is not very useful there.
In pseudo code:
if document_url == /index.html and token not valid
return 302 https//gitlab/loginpage
if document_url == /api/restcall and token not valid
return 401
server document_url
I am working with spring boot 2.1, regarding oauth my pom.xml contains
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
This is my naive try in the SecurityConfig
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/index.html").authenticated()
.and()
.oauth2Login().loginPage("/oauth2/authorization/gitlab")
.and()
.authorizeRequests().antMatchers("/api/restcall").authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
Both configurations (oauth2Login and oauth2ResourceServer) work fine for themself. But as soon as I combine them the last one wins (so in the above example there would be no 302 and the browser would also see a 401 for the index.html). I presume they share some configuration objects so the last write wins.
Is there an (easy) way to get what I want? I know spring can do almost anything, but I would very much not to end up manually configuring a gazillion beans ...
Update:
I've made a minimal example (including #dur's suggestion) of my code here
You need to create multiple configurations and restrict them only to specific URL patterns using requestMatcher. Based on your example, your configurations should look like this:
SecurityConfigHTML
public class SecurityConfigHTML extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/index.html")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login().loginPage("/oauth2/authorization/gitlab");
}
}
SecurityConfigAPI
public class SecurityConfigAPI extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/api/call")
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt();
}
}
SecurityConfigHTML
I think we should include /oauth2/** into the request matchers, otherwise the oauth2Login will not work.
404 http://localhost:8080/oauth2/authorization/gitlab
public class SecurityConfigHTML extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requestMatchers().antMatchers("/index.html", "/oauth2/**")
.and()
.authorizeRequests()
.antMatchers("/oauth2/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login();
// #formatter:on
}
}
I'm putting together a simple website using spring-boot (v2.1.3) and spring-mvc. It was working well until I added spring-security by including the spring-boot-starter-security dependency and adding an implementation of WebMvcConfigurer. Everything worked fine, including the default login and logout views, but all other forms respond with 403-Forbidden on POST's.
The bulk of the guidance I've seen involves csrf protection. Ultimately, you want to include csrf tokens in your form, but the simplest advice is to disable csrf protection completely (http.csrf.disable()). Adding that to the security configuration had no effect.
Here is my WebMvcConfigurer implementation...
#EnableWebSecurity
public class WebSecurityConfig implements WebMvcConfigurer {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build());
return manager;
}
}
Looking for any assistance on basic spring security configuration as it relates to form POST's.
Implement WebSecurityConfigurer instead of WebMvcConfigurer
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurer {
}
I have below configuration class where I would like to authorize certain requests and deny all others.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/phx-config-rest/dev/master").hasRole("DEV")
.anyRequest().authenticated()
.and()
.csrf()
.disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
inMemoryAuthentication()
.withUser("devuser")
.password("dev")
.roles("DEV");
}
}
As per this code my impression was, Spring will only allow me to access /phx-config-rest/dev/master using the user 'devuser' and If I try access /phx-config-rest/prod/master or any other url, request would considered as un-authorized access. BTW, this code piece is regarding Spring cloud config server. Any thought?
change the
.anyRequest().authenticated()
to
.anyRequest().denyAll()
You restrict only URL /phx-config-rest/dev/master to users with role DEV, but all other URLs are accessible for every logged in user (including user devuser) with any role,
see ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#authenticated:
Specify that URLs are allowed by any authenticated user.
You have to use ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#denyAll instead of authenticated:
Specify that URLs are not allowed by anyone.
Sorry to cross-post this here and the old spring forums, but I didn't get any responses and thought the original post may have gotten lost in the shuffle when the forums were deactivated. Anyhoo...
From the documentation, one can mimic multiple elements in the java config by extending WebSecurityConfigurerAdapter multiple times. I've done this, and added a couple custom filters to just one of them, but the odd thing is that the filters seem to be applied to all requests, even ones it shouldn't be matching. Here is a sample of my config:
#Configuration
class ConfigA extends WebSecurityConfigurerAdapter {
public void configure(HttpSecurity http) {
http
.antMatcher("/foo")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.addFilterAfter(myFilter, BasicAuthFilter.class)
...
}
}
#Configuration
class ConfigB extends WebSecurityConfigurerAdapter {
public void configure(HttpSecurity http) {
http
.antMatcher("/bar")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
...
}
}
What am I missing? Is this by design that that filter is applied to both/all requests? Is the filter itself supposed to figure out which requests it should apply to?
Thanks,
Justin
I've been playing around with Spring for the last few days and things are getting to be fun. I'm working with security right now and I've run into a slight snag. Basically, I want the authentication to happen via an API call rather than a form. Is there a neat way to do this?
I've extended the WebSecurityConfigurerAdapter like so -
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/openapi/**").hasRole("USER")
.anyRequest().authenticated();
http
.formLogin()
.defaultSuccessUrl("/hello")
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
authManagerBuilder.inMemoryAuthentication()
.withUser("manager").password("password").roles("MANAGER");
}
}
Is there a way to pick up the the usernames and passwords from a database and can I perform the authentication with an API call?
Spring Security 3 database authentication with Hibernate
This seems promising. It needs a custom authentication manager created.
You can use jdbc authentication with Java configuration as described in the reference http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#jc-authentication-jdbc