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.
Related
I am using Spring Security with JWT for authenticating rest apis. On login, the JWT token is generated and shared to the mobile client. But, the token in the subsequent requests are not been validated. Is there anything wrong in the security configuration ?
Spring security version - 5.1.6.RELEASE
// Security Configuration
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JwtTokenAuthenticationFilter jwtTokenAuthenticationFilter;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.
httpBasic().disable().
csrf().disable().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).
and().
addFilterBefore(jwtTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).
and().
authorizeRequests().antMatchers("/user/login").permitAll().
antMatchers("/user/test").authenticated().
anyRequest().authenticated();
}
}
// JWT Token Auth Filter - This is never invoked
#Component
public class JwtTokenAuthenticationFilter extends GenericFilterBean {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
if (null != token && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
if (null != auth) {
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(req, res);
}
}
I was expecting that all the requests after login will be authenticated against the JWT token.
I tried putting the name of the service to be authenticated as below:
antMatchers("/user/test").authenticated().
Also, any request authenticated is also added, but neither of them worked.
anyRequest().authenticated();
Try to replace
addFilterBefore(jwtTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).
by
addFilterBefore(jwtTokenAuthenticationFilter), BasicAuthenticationFilter.class);
If not runs, change jwtTokenAuthenticationFilter class to avoid to be a Spring bean and use like this:
addFilterBefore(new JwtTokenAuthenticationFilter(jwtTokenProvider)), BasicAuthenticationFilter.class);
And add the following code on Security Class:
#Autowired
private JwtTokenProvider jwtTokenProvider;
Please consider the following configuration
Spring Boot application:
#SpringBootApplication
#EnableRedissonHttpSession
#ComponentScan(basePackages = { "com.ja.pi" })
public class PiApp {
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
Web security configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserHandler userHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//#formatter:off
.anonymous().disable() // Disable anonymous sessions
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(getLoginSuccessHandler())
.failureHandler(getLoginFailureHandler())
.loginPage("/login")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/user/logout");
//#formatter:on
}
And the following test code:
MockHttpServletRequestBuilder requestBuilder = post("/login").contentType("application/x-www-form-urlencoded").param("email", user.getEmail()).param("password", user.getPassword());
ResultActions result = mockMvc.perform(requestBuilder).andExpect(status().isOk());
MockHttpServletResponse response = result.andReturn().getResponse();
String token = response.getHeader("x-auth-token");
The problem is that token is always null and I can't perform actions that require an authenticated session!
But when I startup the Spring Boot application and use a REST client to simulate the same action of login, I find the x-auth-token header returned back in the HTTP response headers.
What should I do with the test API to allow receiving the x-auth-token ?
At first, I was creating the web-app context this way
mockMvc = webAppContextSetup(webApplicationContext).apply(springSecurity()).build();
But the solution is to obtain an instance of the SessionRepositoryFilter filter and add it to the web-app context. The filter is responsible for returning the x-auth-token header.
SessionRepositoryFilter<?> filter = webApplicationContext.getBean(SessionRepositoryFilter.class);
mockMvc = webAppContextSetup(webApplicationContext).addFilters(filter).apply(springSecurity()).build();
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 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.