I have the following controller:
#CrossOrigin
#RestController
#RequestMapping("api")
public class MyController {
#GetMapping("/principal")
public void principalEndpoint(Principal user) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(user);
System.out.println(authentication);
}
}
and the corresponding integration test whcich uses #WithMockUser as described in the docs:
/**
* implementation according to https://docs.spring.io/spring-security/reference/servlet/test/index.html
*/
#ExtendWith(SpringExtension.class)
#SpringBootTest
#WebAppConfiguration
#ContextConfiguration
public class MyControllerIT {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
#WithMockUser(value = "Dani", username = "TAATIDA3")
public void testWithPrincipal() throws Exception {
mvc.perform(get("/api/principal").principal(new PrincipalImpl()))
.andExpect(status().isOk());
}
}
PrincipalImpl is a simple implementation of Principal:
public class PrincipalImpl implements Principal {
#Override
public String getName() {
return "MOCKUSER";
}
}
I also have the following SpringBoot configuration to authorize requests under the /api path:
#Configuration
#EnableResourceServer
#EnableCaching
#EnableScheduling
#EnableMongoAuditing
public class MyApiConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").authorizeRequests()
.antMatchers("/api/**").not().anonymous();
}
}
My problem is that the request dispatched by mockMvc in MyControllerIT fails because a HTTP Status 401 is returned (not authorized). It would work if I change the HttpSecurity configuration to this
http.antMatcher("/api/**").authorizeRequests()
.antMatchers("/api/**").permitAll();
then the request succeeds (HTTP status 200), but no Principal is injected and the Authentication object from SecurityContextHolder.getContext().getAuthentication() is from an anonymous user:
null
AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
If I change the paths in MyApiConfig e.g. to this:
http.antMatcher("/someOtherApi/**").authorizeRequests()
.antMatchers("/someOtherApi/**").permitAll();
then the call from MyControllerIT succeeds and also a Principal is injected, which is what I want. However, in this case the actual api under /api/** is not secured anymore...
I'm quite new to the concepts of Spring Boot Security. Somehow I would have to override the MyApiConfig to configure HttpSecurity differently for tests (or use a separate configuration for test while at the same time excluding MyApiConfig). How do I do that, or what's the best way to make the HttpSecurity setup not interfere with MockMvc setup?
Related
I have a main app that exposes endpoint like this. I have a test like this. It simply uses TestRestTemplate, calls actuator endpoint and checks health:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = HealthDataImporterApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#TestPropertySource(locations = "classpath:application-test.properties")
public class HealthDataImporterApplicationTest {
#Autowired
private TestRestTemplate testRestTemplate;
#LocalServerPort
private int serverPort;
#Test
public void health() {
assertThat("status", read(get("health"), "$.status").equals("UP"));
}
private String get(final String resource) {
return testRestTemplate.getForObject(String.format("http://localhost:%s/actuator/%s", serverPort, resource),
String.class);
}
}
I now added SecurityConfig class to add authentication to the app. Here is SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
private static final String ACTUATOR_URL = "/actuator/**";
private static final String SCOPE_PREFIX = "SCOPE_";
private static final String PERMISSION_SCOPE = "scope:scope";
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, ACTUATOR_URL).permitAll()
.anyRequest().hasAuthority(SCOPE_PREFIX + PERMISSION_SCOPE)
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
#Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(JwtValidators.createDefaultWithIssuer(issuer),
new AudienceValidator(audience)));
return jwtDecoder;
}
}
Main app imports this SecurityConfig class so authentication is there. However, it fails the existing test because it complains it is missing auth0.audience and other value. It seems like it is trying to actually call auth0 url, which I don't want the test to. Otherwise, I have to put actual auth0 audience and domain which is sensitive data.
How can I disable spring security with TestRestTemplate? Is it worth to test this class at all?
As you are using spring-boot, do not override the JwtDecoder just to check audiences. Use spring.security.oauth2.resourceserver.jwt.audiences property instead (this is a comma separated list of acceptable audience).
Rather than deactivating security, I prefer to use mocked identities and include access-control in test coverage, using MockMvc (yes, even in #SpringBootTest with many components wired).
Refer to this other answer for details on how to do that in unit-test and integration-test.
Because I find request post-processors not that readable and I happen to unit-test secured components which are not controllers (like #Service or #Respository with method-security like #PreAuthorize, #PostFilter, etc.), I created a lib with test annotations for OAuth2:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.get("/actuator/health")
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("SCOPE_openid", "SCOPE_scope:scope")
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isOk());
}
#Test
#WithMockJwtAuth("SCOPE_openid")
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isForbidden());
}
}
As comparison, the same sample with just spring-security-test:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.perform(get("/actuator/health/liveness"))
.andExpect(status().isOk());
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"),
new SimpleGrantedAuthority("SCOPE_scope:scope"))))))
.andExpect(status().isOk());
}
#Test
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
In JHipster, we generate a TestSecurityConfiguration class that mocks the JwtDecoder bean so no identity provider is needed. You can see the code here. I've copied it below for your convenience.
package com.auth0.flickr2.config;
import static org.mockito.Mockito.mock;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
/**
* This class allows you to run unit and integration tests without an IdP.
*/
#TestConfiguration
#Import(OAuth2Configuration.class)
public class TestSecurityConfiguration {
#Bean
ClientRegistration clientRegistration() {
return clientRegistrationBuilder().build();
}
#Bean
ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration.Builder clientRegistrationBuilder() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("end_session_endpoint", "https://jhipster.org/logout");
return ClientRegistration
.withRegistrationId("oidc")
.issuerUri("{baseUrl}")
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("read:user")
.authorizationUri("https://jhipster.org/login/oauth/authorize")
.tokenUri("https://jhipster.org/login/oauth/access_token")
.jwkSetUri("https://jhipster.org/oauth/jwk")
.userInfoUri("https://api.jhipster.org/user")
.providerConfigurationMetadata(metadata)
.userNameAttributeName("id")
.clientName("Client Name")
.clientId("client-id")
.clientSecret("client-secret");
}
#Bean
JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
#Bean
OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
}
This class is then referenced in the test and overrides the SecurityConfig.
#SpringBootTest(classes = { Flickr2App.class, TestSecurityConfiguration.class })
The full project repo is at https://github.com/oktadev/auth0-full-stack-java-example.
In my app I've got a custom filter added in WebSecurityConfigurerAdapter extension:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final RequestMatcher PROTECTED_URLS = new AntPathRequestMatcher("/v1/**");
#Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
AuthenticationFilter authenticationFilter() throws Exception {
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
// filter setup...
filter.setAuthenticationManager(authenticationManager());
return filter;
}
}
The filter itself, which is responsible for validating the access token by calling an external authorization server is defined as:
public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {
AuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, OAuth2Exception {
try {
// Get Authorization header.
String token = httpServletRequest.getHeader(AUTHORIZATION);
// Check if the token is valid by calling an external authorization server.
// Returns some Authentication if successful.
} catch (OAuth2Exception exception) {
// Return 401
} catch (Exception exception) {
// All other errors are 500s
}
}
#Override
protected void successfulAuthentication(final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain chain,
final Authentication authResult)
throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
What I'm trying to do is to perform integration test on the controller defined as:
#RestController
#RequestMapping(value = "/v1", produces = "application/json")
public class SomeController {
#Autowired
private SomeService someService;
#ResponseStatus(OK)
#PostMapping(value = "/a/path")
public SomeSuccessResponse pathHandlerMethod() {
return someService.someServiceMethod();
}
}
Finally, my test setup is as following:
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
#Import(SecurityConfig.class)
#ContextConfiguration
#WebAppConfiguration
public class SomeControllerTest {
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private WebApplicationContext context;
#MockBean
private SomeService someService;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // When I comment out this line I'm getting 404 errors instead.
.build();
}
#Test
#WithMockUser
public void performIntegrationTest() throws Exception {
mockMvc.perform(post("/v1/a/path")).andExpect(status().isOk());
}
}
I'd like the authentication to be either turned off or somehow mocked out for this scenario - the actual code in AuthenticationFilter sholdn't be invoked at all. In order to achieve this, in SomeControllerTest class I've tried:
annotating test methods with #WithMockUser
setting mockMvc with MockMvcBuilders (see the setup() method above) with .apply(springSecurity()) and without it
annotating the SomeControllerTest class with #AutoConfigureMockMvc (with both secure and addFilters parameters set to false)
annotating the SomeControllerTest class with #ContextConfiguration and #WebAppConfiguration (I don't know if it changes anything)
None of these approaches disable the authentication. When I run the test, the AuthenticationFilter's attemptAuthentication() method calling external service is still invoked which I don't want to happen.
Disabling the filter sounds contradictory for an integration test, imho. Have you considered mocking the filter instead?
Create a
public class MockAuthenticationFilter implements Filter {
// return mock data for different use cases.
}
Then register this filter in your test.
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity(new MockAuthenticationFilter()))
.build();
}
This would also allow you to test different use cases where the filter acts one way or an other.
Hello I'have a web application secured with Spring security, with a login page. This is my Security Configuration
#Configuration
#ComponentScan("it.besmart")
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
#Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
#Autowired
CustomSuccessHandler customSuccessHandler;
#Autowired
CustomAuthenticationFailureHandler customAuthenticationFailureHandler;
#Autowired
DataSource dataSource;
#Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
#Autowired
private FacebookConnectionSignup facebookConnectionSignup;
private final static Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
#Autowired
public void configureGlobalService(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Webapp security configured");
http.
authorizeRequests()
.antMatchers("/", "/register", "/registrationConfirm", "/resendRegistrationToken", "/park/**")
.permitAll()
.antMatchers("/edit/**", "/payment/**", "/plate/**", "/book/**", "/home", "/stop/**",
"/notification/**", "/include/**")
.access("hasRole('USER') or hasRole('ADMIN') or hasRole('PARK')").antMatchers("/admin/**")
.access("hasRole('ADMIN') or hasRole('PARK')").antMatchers("/updatePassword")
.hasAuthority("CHANGE_PASSWORD_PRIVILEGE")
.and().formLogin().loginPage("/")
.successHandler(customSuccessHandler).failureHandler(customAuthenticationFailureHandler)
.usernameParameter("email").passwordParameter("password").and().rememberMe()
.rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(86400).and().exceptionHandling().accessDeniedPage("/Access_Denied").and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/?logout=true").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
db.setDataSource(dataSource);
return db;
}
}
This works good by securing all my web application.
In the same application I have also a Resource/Authorization Server to protect some REST api.
Some resources are protected with an authorization code grant, so the untrusted Mobile App should take the access token from my application with a login form. I would like that the application use a different login page when trying to login from the Mobile App.
This is my resourceServer configuration
#EnableResourceServer
#ComponentScan("it.besmart.easyparking")
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig {
private final Logger logger = LoggerFactory.getLogger(ResourceServerConfig.class);
#Autowired
DataSource dataSource;
private static final String RESOURCE_ID = "easyparking_api";
#Configuration
// #Order(2)
public class grantCredentialsConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
logger.debug("Api security configured");
http
.requestMatchers().antMatchers("/api/oauth/**").and().authorizeRequests()
.antMatchers("/api/oauth/**").access("hasRole('USER')").and().formLogin().loginPage("/apilogin")
.permitAll();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore()).resourceId(RESOURCE_ID);
}
}
#Configuration
// #Order(4)
public class clientCredentialsConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
logger.debug("Client security configured");
http
.requestMatchers().antMatchers("/oauth2/**", "/api/registration", "/api/park/**").and()
.authorizeRequests().antMatchers("/oauth2/**", "/api/registration", "/api/park/**").authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore()).resourceId(RESOURCE_ID);
}
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
so, grantCredentialsConfiguration should redirect the requests to /apilogin form, but it does not, i am redirected to the main web app login page... How it can be accomplished?
EDIT
Looking closer into the logs, it looks like that when i try to hit /oauth/authorize/ the normal security chain takes place and i get
2017-05-25 12:23:15 DEBUG o.s.security.web.FilterChainProxy[310] - /oauth/authorize?response_type=token&client_id=test&redirect_uri=https://www.getpostman.com/oauth2/callback reached end of additional filter chain; proceeding with original chain
2017-05-25 12:23:15 DEBUG o.s.s.o.p.e.FrameworkEndpointHandlerMapping[310] - Looking up handler method for path /oauth/authorize
2017-05-25 12:23:15 DEBUG o.s.s.o.p.e.FrameworkEndpointHandlerMapping[317] - Returning handler method [public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map<java.lang.String, java.lang.Object>,java.util.Map<java.lang.String, java.lang.String>,org.springframework.web.bind.support.SessionStatus,java.security.Principal)]
2017-05-25 12:23:15 DEBUG o.s.s.w.a.ExceptionTranslationFilter[163] - Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.InsufficientAuthenticationException: User must be authenticated with Spring Security before authorization can be completed.
So it looks like searching for a handler to manage the request, instead of redirecting to /api/apilogin as desired, he finds an Authentication exception and so i go to the standard login page... But why i get this exception?
Its happening because you haven't specified the order of the security configuration classes.
In Spring security resources protection should be mentioned from specific to generic.
Class SecurityConfiguration is more generic than grantCredentialsConfiguration. As both protect following resources.
SecurityConfiguration protects /** (Default URL)
grantCredentialsConfiguration /api/oauth/**
Since the order is not defined, SecurityConfiguration's generic configuration hides the specific configuration by grantCredentialsConfiguration
To get these to work as expected you'll have to define the order as below.
#Configuration
#Order(2)//Generic config should have larger value (lower priority)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
}
#Configuration
#Order(1)//Specific with lower value (higher priority)
public class grantCredentialsConfiguration extends ResourceServerConfigurerAdapter {
}
Note: Since these login pages are not from different applications, they share the SecurityContextHolder or the security context. So if you login from one login page and then try to go the protected resource of the other, you won't be redirected to the next login page. Instead you'll get the 403 (depending on the roles assigned by the different login pages). At a time only one login session can be maintained.
Here's a sample on Github
https://github.com/ConsciousObserver/TestMultipleLoginPages.git
Have you tried adding URL path /apilogin , to the
.antMatchers("/", "/register", "/registrationConfirm",/resendRegistrationToken", "/park/**")
.permitAll()
I am guessing the application is redirecting the /apilogin access to the common authentication login page, since it is not added to the unauthenticated access list.
Environment:
I have a spring boot based microservice architecture application consisting of multiple infrastructural services and resource services (containing the business logic). Authorization and authentication is handled by an oAuth2-Service managing the user entities and creating JWT tokens for the clients.
To test a single microservice application in its entirety i tried to build tests with testNG, spring.boot.test, org.springframework.security.test ...
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = {"spring.cloud.discovery.enabled=false", "spring.cloud.config.enabled=false", "spring.profiles.active=test"})
#AutoConfigureMockMvc
#Test
public class ArtistControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private MockMvc mvc;
#BeforeClass
#Transactional
public void setUp() {
// nothing to do
}
#AfterClass
#Transactional
public void tearDown() {
// nothing to do here
}
#Test
#WithMockUser(authorities = {"READ", "WRITE"})
public void getAllTest() throws Exception {
// EXPECT HTTP STATUS 200
// BUT GET 401
this.mvc.perform(get("/")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
}
}
where the security (resource server) config is the following
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// get the configured token store
#Autowired
TokenStore tokenStore;
// get the configured token converter
#Autowired
JwtAccessTokenConverter tokenConverter;
/**
* !!! configuration of springs http security !!!
*/
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/**").authenticated();
}
/**
* configuration of springs resource server security
*/
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// set the configured tokenStore to this resourceServer
resources.resourceId("artist").tokenStore(tokenStore);
}
}
and the following method based security check annotated inside the controller class
#PreAuthorize("hasAuthority('READ')")
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Foo> getAll(Principal user) {
List<Foo> foos = fooRepository.findAll();
return foos;
}
I thought that would work but when running the test i only get an assertion error
java.lang.AssertionError: Status
Expected :200
Actual :401
Question:
Is there something totally obvious that i am doing wrong? Or is #WithMockUser not going to work with #SpringBootTest and #AutoConfigureMockMvc in an oAuth2 environment? If this is the case... what would be the best approach for testing route and method based security configurations as part of such an (integration) test like this one?
Appendix:
I also tried different approaches like something like the following... but it led to the same result :(
this.mvc.perform(get("/")
.with(user("admin").roles("READ","WRITE").authorities(() -> "READ", () -> "WRITE"))
.accept(MediaType.APPLICATION_JSON))
see:
spring security testing
spring boot 1.4 testing
#WithMockUser creates the authentication in SecurityContext.
Same applies for with(user("username")).
By default the OAuth2AuthenticationProcessingFilter does not use the SecurityContext, but always build the authentication from the token ("stateless").
You can easily change this behavior be setting the stateless flag in the resource server security configuration to false:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration implements ResourceServerConfigurer {
#Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
security.stateless(false);
}
#Override
public void configure(HttpSecurity http) {}
}
Another option is to extend ResourceServerConfigurerAdapter, but the problem with that is that it comes with configuration that forces all requests to be authenticated. Implementing the interface leaves your main security config unchanged apart from the statelessness.
Of course, set the flag to to false in your test contexts, only.
I had de same issue, and the only way I found was creating a token and using it in the mockMvc perform
mockMvc.perform(get("/resource")
.with(oAuthHelper.bearerToken("test"))
And the OAuthHelper:
#Component
#EnableAuthorizationServer
public class OAuthHelper extends AuthorizationServerConfigurerAdapter {
#Autowired
AuthorizationServerTokenServices tokenservice;
#Autowired
ClientDetailsService clientDetailsService;
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
OAuth2AccessToken createAccessToken(final String clientId) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Collection<GrantedAuthority> authorities = client.getAuthorities();
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
User userPrincipal = new User("user", "", true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenservice.createAccessToken(auth);
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test")
.authorities("READ");
}
}
As I was specifically trying to write tests against our ResourceServerConfiguration, I worked around the issue by creating a test wrapper for it which set security.stateless to false:
#Configuration
#EnableResourceServer
public class ResourceServerTestConfiguration extends ResourceServerConfigurerAdapter {
private ResourceServerConfiguration configuration;
public ResourceServerTestConfiguration(ResourceServerConfiguration configuration) {
this.configuration = configuration;
}
#Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
configuration.configure(security);
security.stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
configuration.configure(http);
}
}
I am having a problem similar to PreAuthorize annotation doesn't work with jersey. I created a configuration class for Spring Security and the authentication works but the authorization does not.
Here is my code
SpringSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(1)
#ComponentScan({"com.foo.rest.resources.Template"})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final TokenAuthenticationService tokenAuthenticationService;
public SpringSecurityConfig() {
super(true);
this.userService = new UserService();
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.headers().cacheControl().and()
.authorizeRequests()
// Allow anonymous logins
.antMatchers("/auth/**").permitAll()
// All other request need to be authenticated
.anyRequest().authenticated().and()
// Custom Token based authentication based on the header previously given to the client
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserService userDetailsService() {
return userService;
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
and Template.java
#Component
#Path("/template")
#Produces(MediaType.APPLICATION_JSON)
public class Template {
#GET
#Secured("ROLE_EDITOR")
public User getTemplate() {
return new Template();
}
}
My guess is that the authentication is handled in the filter chain but it never comes back around after the authorization tag is reached. Any idea how to make this work?
I think your #ComponentScan is configured wrongly and doesn't pick the Template resource correctly.
According to #ComponentScan documentation the value is an alias for basePackages but you have given a Class instead of Package. Try and change it to look like following and see.
#ComponentScan({"com.foo.rest.resources.*"})
And make sure you haven't missed any steps in Jersey Spring Integration as per the documentation