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
}
}
Related
I'm trying to use spring security to secure a rest/stateless api using JWT tokens. From the research I've been seeing, it involves turning off the spring security session management and then adding some custom filters to handle the user logging in as well as checking for the jwt token.
The problem I'm having is that once i add a filter, it's run on every instead of just the endpoints I want it on. I need to open up the login endpoint as well as a few others that will facilitate enrollment and reference data that doesn't need to be secured.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/user").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(new StatelessAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
;
}
}
All StatelessAuthenticationFilter does is print "in here". I'm only expecting to see that message print when you go to localhost:8080/api/order, but i see it show up when you go to localhost:8080/api/user.
Is there a way to get this behavior?
The way you configured, the HttpSecurity will be applied to all the URLs including the user endpoint.
authorizeRequests() .antMatchers("/api/user").permitAll() line won't prevent "user" endpoint from authentication filter being called.
It just says that any authenticated user can call it.
You need to apply the filter to "order" endpoint only. Like this:
http .requestMatchers().antMatchers("/api/user") .and() .authorizeRequests().
#tsolakp's answer sorta works for me. I ended up overriding the
configure(Websecurity) method though
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/user");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated().and()
.addFilterBefore(new StatelessAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
;
}
I'm working with Spring Boot 1.4.2.RELEASE, Spring Security 4.1.3.RELEASE, and Java 8.
For the security configuration I have the following class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**", "/logout", "/login");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement() // 1
.sessionFixation() // 2
.migrateSession() // 3
.maximumSessions(1) // 4
.maxSessionsPreventsLogin(true).expiredUrl("/login").and() // 5
.invalidSessionUrl("/login") // 6
.and() // 7
.authorizeRequests()
.antMatchers("/app/**").authenticated()
.antMatchers("/about").permitAll()
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/auth").permitAll()
.defaultSuccessUrl("/app/index", true)
.failureUrl("/login?error")
.usernameParameter("uid")
.passwordParameter("pwd").and()
.logout()
.permitAll()
.invalidateHttpSession(true)
.logoutUrl("/logout").permitAll()
.logoutSuccessUrl("/login").permitAll()
.clearAuthentication(true).and()
.exceptionHandling().and()
.csrf().disable()
.headers().frameOptions().disable()
.cacheControl().and();
}
}
When try access to "/about" (permit all everyone), redirects me to "/login". I try it a second time and now it allows me to access "/about".
I have changed the authorization order of requests in the configuration class but don't works. When I delete the lines of sessionManagement (1 to 7), everything works normally.
Do I need any extra configuration?
When you are testing this, did you clear your browser's cookie with regard to your testing site? The session management filter sets a jsessionid cookie, which gets sent back. Your browser has no idea that you reset your server, so thinks the cookie is just fine, and you send back an invalid session.
You may want to take a look at your session creation policies to see whether or not it suits your purpose.
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.
I have a spring boot application, with Oauth authentication and a resource server as well in one single application. I have my frontend on a separate server, from a separate location. My frontend application doesn't seem to proceed the preflight operation to the backend, which always responds with 401. My configuration looks as the follows:
// ... annotations
public class OAuthConfig extends WebSecurityConfigurerAdapter {
// ... authencication providers
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/*.html", "layout/**", "/js/**", "/css/**", "/images/**", "/font/**",
"/signup", "/register",
"/oauth/**")
.permitAll()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/**").permitAll()
;
// #formatter:on
}
// ... beans
}
Note that I had to add exceptions for the static content as well, since it doesn't seemd to work other way, despite any documentations.
// ... annotations
public class MvcConfig extends WebMvcConfigurerAdapter {
// ... resource resolver, view resolver
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
I have tried to specify more explicitly, but nothing succeeded as well:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**").allowedMethods("GET","POST","OPTIONS","DELETE","UPDATE");
registry.addMapping("/register");
registry.addMapping("/signup");
registry.addMapping("/oauth/**").allowedMethods("GET","POST","OPTIONS");
}
//... annotations
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
// ... resource id config
#Override
public void configure(HttpSecurity http) throws Exception {
//#formatter:off
http
.anonymous().disable()
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
//#formatter:on
}
}
At this point I can't figure out if I have missed anything important to configure, to achieve CORS over the authentication endpoint (as well as the other parts of API endpoints)
If you got an HTTP 401 error, for me it doesn't mean CORS configuration is the problem. 401 is supposed to mean unauthorized, so the problem may be more in your security configuration. If it was a cross origin problem, you would have an error like "No 'Access-Control-Allow-Origin' header is present on the requested resource.".
As a matter of fact I have one of my application which got a HTTP 200 (OK) on a request that fails because of a cross origin problem.
And if you want help you should really provide details on the request/response where you got the 401 error code.
I know it's not a real answer but I don't have the reputation to comment yet, so I hope this will help.
You can also configure CORS on server. Which will work for all APIs of your application. Or you to set 'Access-Control-Allow-Origin to *' in your resquest and response so that your api can give response to any request from any server.
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?