Following the sample from Spring Boot: example code from GitHub everything seems to work fine.
But when I integrate Spring Boot Security OAuth2 in the project, my OAuth2 endpoints stop working. There's a warning in the logs:
2017-05-04 08:56:24.109 WARN 2827 --- [nio-8080-exec-1] o.glassfish.jersey.servlet.WebComponent : A servlet request to the URI http://127.0.0.1:8080/oauth/token contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using #FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
Which makes me think even though I'm not registering the endpoint, Jersey is capturing it and processing the body, making Spring MVC unable to accept the request...
My Jersey Config is:
#Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(InfoController.class);
}
}
And my info controller is very simple:
#Component
#Path("/me")
#Produces("application/json")
public class InfoController {
#GET
public String meAction() {
return "Hi";
}
}
And finally, the call I'm trying to make and it's causing the warning in the logs:
curl -X POST -u CLIENT_APPLICATION:123456789 http://127.0.0.1:8080/oauth/token -H "Accept: application/json" -d "password=aaa&username=aa&grant_type=password&client_id=CLIENT_APPLICATION"
Is there a known incompatibility between the two projects (spring-boot-starter-jersey and spring-security-oauth2 in that sense?
Removing the Jersey configuration makes it all work, but I need to use it on my controllers.
My configuration for OAuth2 is:
#Configuration
public class OAuth2ServerConfiguration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("OAuth2 Server");
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/*").authenticated();
// #formatter:on
}
}
}
Then there's the security configuration itself:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final ApiUserDetailsService userDetailsService;
#Autowired
public WebSecurityConfiguration(ApiUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Thanks in advance!
It seems that Jersey is trying the handle the OAuth endpoint, which it shouldn't be. The reason is that the default mapping for Jersey is /*, which means that it will handle requests for all URLs. You can change that in a couple of ways:
Add an #ApplicationPath on top of your ResourceConfig subclass with a different mapping
#Component
#ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig {}
You can add the mapping in your application.properties file
spring.jersey.application-path=/api
What this will do is prefix /api to all your Jersey endpoints, and also cause Jersey not to handle all request, only ones that begin with /api.
I am having following configuration in Spring Security wiht OAuth2:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
//#EnableOAuth2Sso
public class FVSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("secret")
.roles("USER", "ROLE1")
.and()
.withUser("admin")
.password("password")
.roles("ADMIN","USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("USER")
.anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler())
.and()
.formLogin();
}
Resource server configuraion:
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("sampleClient")
.authorizedGrantTypes("implicit")
.scopes("read")
.autoApprove(true)
.accessTokenValiditySeconds(30)
.and()
.withClient("user")
.secret("secret")
.scopes("read", "write")
.autoApprove(true)
.authorizedGrantTypes(
"password","authorization_code", "refresh_token")
.and()
.withClient("admin")
.secret("password")
.scopes("read", "write", "custom")
.autoApprove(true)
.authorizedGrantTypes(
"password","authorization_code", "refresh_token");
}
And the following Rest Controller
#RestController
public class OAuthTestController {
#PreAuthorize("hasRole('ROLE_ROLE1') and #oauth2.hasScope('read')")
#RequestMapping(value="/api/user/test1", method = RequestMethod.GET)
public String testGETWithRole() {
return "[GET] Needs role ROLE1";
}
#RequestMapping(value="/api/admin/test1", method = RequestMethod.GET)
public String testWithAdminRole() {
return "[GET] Needs Admin role";
}
And the resource server:
#Configuration
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "my_rest_api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.requestMatchers().antMatchers("/api/**")
.and().authorizeRequests()
.antMatchers("/api/**").access("hasRole('USER')")
.antMatchers("/api/admin/**").access("hasRole('ADMIN')")
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Basically I am expecting Spring to use OAuth2 for authentication and use spring security roles to allow resource access.
I generated and admin token and afterwards an user token. I am expecting when using admin token to be able to access /api/admin resource and when using user token to not be able.
I just noticed that the form login is not prompted always, so I suppose Spring remembers the authenticated user.
When I used the admin token I got access denied. When debugging in Spring sources I noticed that the authentication was an instance of UserNamePasswordAuthenticationPassword with the user details, not admin as expected. I suppose is because last time I used those credentials in login form.
Is it a way to force spring to use credentials for corresponding token, or to force Spring show login form each time?
P.S. I use Postman to test the API.
Thanks.
I am using spring security via spring boot.
I have two kinds of rest services.
public/** --> Every one can access and use these services
secure/** --> Only authenticated users can use.
#Slf4j
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/public/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(requestHeaderAuthenticationFilter(authenticationManager()),
BasicAuthenticationFilter.class)
.authorizeRequests().antMatchers("/secure/**").fullyAuthenticated();
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(
final AuthenticationManager authenticationManager) {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setExceptionIfHeaderMissing(true);
filter.setPrincipalRequestHeader("MY_HEADER");
filter.setInvalidateSessionOnPrincipalChange(true);
filter.setCheckForPrincipalChanges(false);
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
return filter;
}
When i want to access a resource under public i got exception.
exception: "org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException"
message: "MY_HEADER header not found in request."
Why does my filter activated under public resource while it is configured as ignored resource?
Thanks is advance
This is an issue in WebSecurity.ignoring() as discussed in Spring Security Github when using Beans as Filters.
You can work around this by removing the #Bean annotation in your Filter declaration.
// #Bean - Remove or Comment this
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(
final AuthenticationManager authenticationManager) {
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setExceptionIfHeaderMissing(true);
filter.setPrincipalRequestHeader("MY_HEADER");
filter.setInvalidateSessionOnPrincipalChange(true);
filter.setCheckForPrincipalChanges(false);
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
return filter;
}
I setting the Spring Configuration Below:
#EnableAuthorizationServer
#EnableWebSecurity
#Configuration
public class Oauth2Provider extends WebSecurityConfigurerAdapter implements
AuthorizationServerConfigurer {
/*
* #Autowired private TokenStore tokenStore;
*/
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password")
.roles("USER").and().withUser("admin").password("password")
.roles("USER", "ADMIN");
}
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
// TODO Auto-generated method stub
security.allowFormAuthenticationForClients();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
// TODO Auto-generated method stub
clients.inMemory()
.withClient("my-trusted-client")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT", "ROLE_ANONYMOUS")
.scopes("read", "write", "trust")
.secret("secret")
.accessTokenValiditySeconds(60);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
// TODO Auto-generated method stub
}
}
And Maven Setting is Below:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
I Access :
http://localhost:8080/oauth/token
Payload
grant_type=password&password=password&username=user&scope=read&client_id=my-trusted-client&client_secret=secret
But I receive error below:
{
error: "unsupported_grant_type"
error_description: "Unsupported grant type: password"
}
To use password grant you need to provide an authentication manager to the authorization server (in the empty method with the TODO in your example), so it can authenticate users. If it's a Spring Boot application there is always an AuthenticationManager available to be #Autowired.
I am trying to develop Spring Boot web application and securing it using Spring security java configuration.
After placing my static web resources in 'src/main/resources/public' as advised here in Spring blog, I am able to get the static resources. i.e hitting https://localhost/test.html in browser do serves the html content.
Problem
After I enabled Spring Security, hitting the static resource URL requires authentication.
My relevent Spring Security Java config looks like this:-
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
authorizeRequests()
.antMatchers("/","/public/**", "/resources/**","/resources/public/**")
.permitAll()
.antMatchers("/google_oauth2_login").anonymous()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/home")
.and()
.csrf().disable()
.logout()
.logoutSuccessUrl("/")
.logoutUrl("/logout") // POST only
.and()
.requiresChannel()
.anyRequest().requiresSecure()
.and()
.addFilterAfter(oAuth2ClientContextFilter(),ExceptionTranslationFilter.class)
.addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
.userDetailsService(userService);
// #formatter:on
}
How should I configure antMatchers to permit static resources placed inside src/main/resources/public ?
There are a couple of things to be aware of:
The Ant matchers match against the request path and not the path of the resource on the filesystem.
Resources placed in src/main/resources/public will be served from the root of your application.
For example src/main/resources/public/hello.jpg would be served from http://localhost:8080/hello.jpg
This is why your current matcher configuration hasn't permitted access to the static resources. For /resources/** to work, you would have to place the resources in src/main/resources/public/resources and access them at http://localhost:8080/resources/your-resource.
As you're using Spring Boot, you may want to consider using its defaults rather than adding extra configuration. Spring Boot will, by default, permit access to /css/**, /js/**, /images/**, and /**/favicon.ico. You could, for example, have a file named src/main/resources/public/images/hello.jpg and, without adding any extra configuration, it would be accessible at http://localhost:8080/images/hello.jpg without having to log in. You can see this in action in the web method security smoke test where access is permitted to the Bootstrap CSS file without any special configuration.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**"); // #3
}
Ignore any request that starts with "/resources/". This is similar to configuring http#security=none when using the XML namespace configuration.
This may be an answer (for spring boot 2) and a question at the same time.
It seems that in spring boot 2 combined with spring security everything (means every route/antmatcher) is protected by default if you use an individual security mechanism extended from
WebSecurityConfigurerAdapter
If you don´t use an individual security mechanism, everything is as it was?
In older spring boot versions (1.5 and below) as Andy Wilkinson states in his above answer places like public/** or static/** are permitted by default.
So to sum this question/answer up - if you are using spring boot 2 with spring security and have an individual security mechanism you have to exclusivley permit access to static contents placed on any route. Like so:
#Configuration
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final ThdAuthenticationProvider thdAuthenticationProvider;
private final ThdAuthenticationDetails thdAuthenticationDetails;
/**
* Overloaded constructor.
* Builds up the needed dependencies.
*
* #param thdAuthenticationProvider a given authentication provider
* #param thdAuthenticationDetails given authentication details
*/
#Autowired
public SpringSecurityConfiguration(#NonNull ThdAuthenticationProvider thdAuthenticationProvider,
#NonNull ThdAuthenticationDetails thdAuthenticationDetails) {
this.thdAuthenticationProvider = thdAuthenticationProvider;
this.thdAuthenticationDetails = thdAuthenticationDetails;
}
/**
* Creates the AuthenticationManager with the given values.
*
* #param auth the AuthenticationManagerBuilder
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(thdAuthenticationProvider);
}
/**
* Configures the http Security.
*
* #param http HttpSecurity
* #throws Exception a given exception
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers("/management/**").hasAnyAuthority(Role.Role_Engineer.getValue(),
Role.Role_Admin.getValue())
.antMatchers("/settings/**").hasAnyAuthority(Role.Role_Engineer.getValue(),
Role.Role_Admin.getValue())
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
.authenticationDetailsSource(thdAuthenticationDetails)
.loginPage("/login").permitAll()
.defaultSuccessUrl("/bundle/index", true)
.failureUrl("/denied")
.and()
.logout()
.invalidateHttpSession(true)
.logoutSuccessUrl("/login")
.logoutUrl("/logout")
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler());
}
}
Please mind this line of code, which is new:
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
If you use spring boot 1.5 and below you don´t need to permit these locations (static/public/webjars etc.) explicitly.
Here is the official note, what has changed in the new security framework as to old versions of itself:
Security changes in Spring Boot 2.0 M4
I hope this helps someone.
Thank you!
Have a nice day!
Here is the ultimate solution, after 20+ hours of research.
Step 1. Add 'MvcConfig.java' to your project.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
}
}
Step 2. Add configure(WebSecurity web) override to your SecurityConfig class
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
Step 3. Place all static resources in webapp/resources/..
If you are using webjars. You need to add this in your configure method:
http.authorizeRequests().antMatchers("/webjars/**").permitAll();
Make sure this is the first statement. For example:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/webjars/**").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.logoutSuccessUrl("/")
.permitAll()
.and()
.rememberMe();
}
You will also need to have this in order to have webjars enabled:
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
...
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
...
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
String[] resources = new String[]{
"/", "/home","/pictureCheckCode","/include/**",
"/css/**","/icons/**","/images/**","/js/**","/layer/**"
};
http.authorizeRequests()
.antMatchers(resources).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout().logoutUrl("/404")
.permitAll();
super.configure(http);
}
}
i had the same issue with my spring boot application, so I thought it will be nice if i will share with you guys my solution. I just simply configure the antMatchers to be suited to specific type of filles. In my case that was only js filles and js.map. Here is a code:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/index.html", "/", "/home",
"/login","/favicon.ico","/*.js","/*.js.map").permitAll()
.anyRequest().authenticated().and().csrf().disable();
}
}
What is interesting. I find out that resources path like "resources/myStyle.css" in antMatcher didnt work for me at all. If you will have folder inside your resoruces folder just add it in antMatcher like "/myFolder/myFille.js"* and it should work just fine.
In the latest Spring Security 6, the WebSecurityConfigurerAdapter is deprecated.
Declare a WebSecurityCustomizer bean instead.
#Bean
public WebSecurityCustomizer ignoringCustomizer() {
return (web) -> web.ignoring().requestMatchers("...");
}
It,s work for spring security 6.0.*
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers(
"/home/**",
"/login/**",
"/account/starter/**",
"/register/**",
"/plugins/**",
"/dist/**",
"/js/**",
"/**/favicon.ico").permitAll()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
"/plugins/**",
"/dist/**",
"/js/**",
... they are located in resources/
plugins, dist, js - these are the names of directories with resources