I have a DispatcherServlet that has a URL mapping /api1 and subsequentially a Controller with a mapping #GetMapping("/resource1") for a controller method. So basically I have a valid URL /api1/resource1 that should be handled by the mentioned controller.
Also, the setup incorporates a Spring Security Filter that matches requests /* as it secures other URLs not handled by Spring (but Jersey for example).
The API secured by the Spring Security Filter is setup like
protected void configure(HttpSecurity http) throws Exception {
//#formatter:off
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatchers()
.antMatchers("/api1/**")
.and()
.authorizeRequests()
.antMatchers("/**")
.authenticated()
For testing I use the MockMvc* support to setup a mocked web environment including a Spring security setup
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build()
I want to test that security checks are applied and the controller method is called on successful security checks.
when:
def result = mvc.perform(
get('/api1/resource1')
.header(HttpHeaders.AUTHORIZATION, "Bearer " + apiToken))
then:
result.andExpect(status().isOk())
The code above is based on the Spock framework with the MockMvc stuff.
All of the security checks are passing so the Spring security setup is complete, but finally the controller should be invoked but fails with a 404 status i.e the resource - this is the mapped controller method - is not found.
I'm confident that it fails because the mocked setup does not incorporate a the /api dispatcher servlet mapping. To proof that assumption I can modify the controller method mapping to #GetMapping("/api1/resource1") and the test will result in a HTTP OK (200).
So, my question is, is it possible to configure a kind of URL prefix in the MockMvc setup?
There is one constraint, the code base is not using Spring Boot (and can't for a while in future)!
Edit:
I added the following to my test to have all requests set the servletPath.
static MockHttpServletRequestBuilder get(String urlTemplate, Object... uriVars) {
new MockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, uriVars)
.servletPath('/api1')
}
I think you just need to configure the contextPath for the request.
See org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder.contextPath(String) for details.
Related
I have configured my SecurityFilterChain thus:
#EnableWebSecurity
public class WebSecurityConfig {
....
#Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().disable()
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, "/api/user/*").access("hasRole('ADMIN')")
.antMatchers(HttpMethod.POST, "/api/user").access("hasRole('ADMIN')")
.antMatchers("/auth/login").anonymous()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
Yet, the URL paths are open to any authenticated user irregardless of the assigned role.
I have debugged the request filter to confirm that the Principal has the right role yet a USER role can call the protected URLs successfully.
I am using Spring Boot 2.7.5.
If the path you're calling matches the authorization rule that you've declared as the last one (i.e. anyRequest().authenticated()), that implies that your test-requests don't match any of your rules that are meant to guard URLs that should be accessible only for Admins, namely:
.antMatchers(HttpMethod.DELETE, "/api/user/*").access("hasRole('ADMIN')")
.antMatchers(HttpMethod.POST, "/api/user").access("hasRole('ADMIN')")
Reminder: the matching rule declared first always weens
So, either HTTP-method or URL don't match (or both). For instance, if you're sending GET request, these restrictions would not be applied.
Regarding the URL, it should match exactly because you're using antMatchers(). I.e. path "/api/user" would not match other existing aliases of that path like "/api/user/" (more on that see here).
That's one of the reasons why in Spring Security 6.0 antMatchers() (as well as mvcMathcers() and regexMatchers()) have been removed from the API and replaced requestMatchers().
So make sure that HTTP-method is correct and path you're calling matchers exactly, and consider updating the Spring dependencies and switching to using new request-securing methods.
If you have no planes to update soon, then you can make use of the mvcMatchers(), which use Spring MVC matching rules (i.e. they take into consideration all the existing aliases of the given path), instead of antMatchers().
Here's an example of how your configuration might be implemented with Spring Security 6.0 and Lambda DSL (if you feel more comfortable with chaining configuration options using and() this flavor of DSL is still supported as well):
#Configuration
public class SecurityConfig {
#Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.DELETE, "/api/user/*").hasRole("ADMIN") // in Spring Security 6.0 method access() has been changed, and you don't need it anyway to verify the Role
.requestMatchers(HttpMethod.POST, "/api/user").hasRole("ADMIN")
.requestMatchers("/auth/login").anonymous()
.anyRequest().authenticated()
)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
The spring security annotations are ignored on the Vaadin views; I have a #DenyAll at the class level but the view is rendered anyway.
The project combines thymeleaf and vaadin within spring boot. The first for a fast rendering of a large HTML with command and events over a websocket, the latter for ease of developing the administrative screens. Vaadin is setup under "/vdn/", spring MVC with thymeleaf under "/".
Spring security works correctly in thymeleaf; login, logout, and the sec:authorize correctly hides or shows parts of the generated HTML. But the security tag on the Vaadin view is ignored.
#Route("/")
#StyleSheet("context://../vaadin.css")
#DenyAll
public class MainView extends AppLayout {
Based on the documentation (https://vaadin.com/docs/latest/security/enabling-security) if no annotation is present the view should not be shown at all, it however is. So somehow Vaadin is not picking up the Spring security. Any suggestions what is missing?
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Autowired
private DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().passwordEncoder(new BCryptPasswordEncoder()) //
.dataSource(dataSource) //
.usersByUsernameQuery("select username, password, enabled from person where username=?") //
.authoritiesByUsernameQuery("select username, role from person where username=?") //
;
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests() //
.anyRequest().authenticated() //
.and() //
.formLogin() //
.and() //
.csrf().disable() // needed for vaadin https://tutorialmeta.com/question/server-connection-lost-after-successful-login-with-spring-security
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
;
return http.build();
}
}
As mentioned in the comments as well, in order for the Vaadin View-Based security to work, it should be enabled first by extending your security configuration class from VaadinWebSecurity (for V23.2+) or VaadinWebSecurityConfigurerAdapter (older versions). You can refer to the documentation here: https://vaadin.com/docs/latest/security/enabling-security/#security-configuration-class
When extending from either of the above classes, if you are overriding the void configure(HttpSecurity http) throws Exception method (which is needed almost always), do not forget to call super.configure(http); in the correct order mentioned that mentioned in the documentation. This is important as the viewAccessChecker bean is enabled through this call, and this is what you need to have the View-Based security work.
Note: You probably have seen the chain of calls on http.authorizeRequests() (which is of type ExpressionInterceptUrlRegistry) in many tutorials and documentations. Once the .anyRequest().PermitAll() or some similar method is called on it, it does not accept any more configuration of those pattern matching configurations, so it is important to configure your custom pattern matching configs before the call to super.configure(http); (as shown in the mentioned documentation).
Finally, setting the login form which is done in the documentation via calling setLoginView(http, LoginView.class); is an important step, as not only does it introduce your custom login view to the viewAccessChecker bean, but, it also enables Spring Security's form-based login feature which is needed for the view-based security mechanism to work properly.
If you have all the steps mentioned above in order, then those access annotations such as #DenyAll or #RolesAllowed are taken into account and you can expect them to work as documented. If you still have problems enabling it, please provide a Minimal, Reproducible Example that isolates your problem so that the community can help more effectively.
I'm configuring spring cloud api gateway to support several security chains. To do that I'm using several security filter chains which triggered on specific security header presence:
The legacy one which already use Authorization header
And new implementation, that integrated with external idp. This solution utilize resource service capabilities. And for this chain I'd like to use, lets say "New-Auth" header.
In case I tune my current setup to trigger second (idp) chain on Authorization header presence (and make call with IDP token), then everything works fine. This way security chain validates token that it expect in Authorization header against idp jwk. But this header is already reserved for legacy auth.
I guess I need a way to point spring resource server chain a new header name to look for.
My security dependencies:
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
My configuration
#EnableWebFluxSecurity
public class WebSecurityConfiguration {
// ...
#Bean
#Order(1)
public SecurityWebFilterChain iamAuthFilterChain(ServerHttpSecurity http) {
ServerWebExchangeMatcher matcher = exchange -> {
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> strings = headers.get(SurpriseHeaders.IDP_AUTH_TOKEN_HEADER_NAME);
return strings != null && strings.size() > 0
? MatchResult.match() : MatchResult.notMatch();
};
http
.securityMatcher(matcher)
.csrf().disable()
.authorizeExchange()
.pathMatchers(navigationService.getAuthFreeEndpoints()).permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt)
.oauth2ResourceServer().jwt().jwkSetUri(getJwkUri())
.and()
.and()
.addFilterAt(new LoggingFilter("idpAuthFilterChain"), SecurityWebFiltersOrder.FIRST)
.addFilterAfter(new IdpTokenExchangeFilter(authClientService), SecurityWebFiltersOrder.AUTHENTICATION)
;
return http.build();
}
}
Dirty solution:
We can add some filter to edit request and duplicate incoming "New-Auth" header as an "Authorization" header at a beginning of security filter chain.
Looks like it works, but I believe it should be a better way.
You can specify a ServerAuthenticationConverter to your oauth2ResourceServer configuration, like so:
http
.oauth2ResourceServer((resourceServer) -> resourceServer
.bearerTokenConverter(customBearerTokenAuthenticationConverter())
.jwt()
);
ServerAuthenticationConverter customBearerTokenAuthenticationConverter() {
ServerBearerTokenAuthenticationConverter tokenAuthenticationConverter = new ServerBearerTokenAuthenticationConverter();
tokenAuthenticationConverter.setBearerTokenHeaderName("New-Auth");
return tokenAuthenticationConverter;
}
thats for the servlet stack, the other reply you can see is for the reactive stack
#Bean
BearerTokenResolver bearerTokenResolver() {
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION);
return bearerTokenResolver;
}
see reference here : https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/bearer-tokens.html
I have a spring security based boot application for which I have configured an endpoint names /test for which I return a test.html page
#RequestMapping("/test")
public String test() {
return "test.html";
}
The endpoint works fine by itself, however if I set it as an OAuth2 failureUrl it becomes unavailable...
http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.authorizeRequests()
.antMatchers("/test").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login()
.failureUrl("/test");
Is this expected behavior? When I do this, spring security properly redirects to /test on authentication failure but /test is inaccessible and it ends up showing a generated page.
The behaviour of failureUrl differs based on whether or not a custom loginPage is configured.
Since you have not customized loginPage, the framework will intercept the failure URL ("/test") and generate the default error page, which is simply the default login page with an error message.
That is why you see the generated login page with accessing "/test".
Your Controller mapping for "/test" is ignored.
To tell the framework not to generate the error page, you can configure the failureHandler instead.
http
.oauth2Login((oauth2Login) -> oauth2Login
.failureHandler(new SimpleUrlAuthenticationFailureHandler("/test"))
);
Note: This may be confusing because the Javadoc for failureUrl does not describe its behaviour properly. I have created a GitHub issue in the Spring Security backlog to fix this.
I am trying to configure spring boot-Embedded Tomcat basic HTTP authentication with multiple roles, with most of the url's similar but few of them specific to each role. Here for first role the basic HTTP authentication pops up and working fine. With below code,
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class TestSecurityAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers(null, getAppAdminRolePaths()).authenticated()
.anyRequest().hasAnyRole("APPADMIN")
.and()
.httpBasic();
http.csrf().disable()
.authorizeRequests().antMatchers(null, getAppUserRolePaths()).authenticated()
.anyRequest().hasAnyRole("APPUSER")
.and()
.httpBasic();
http.authorizeRequests().antMatchers(null, new String[]{"/app/appOwnerView.html"}).authenticated()
.anyRequest().hasAnyRole("APPOWNER")
.and()
.httpBasic();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("appadminname").password("appadminpwd").roles("APPADMIN").and()
.withUser("appusername").password("appuserpwd").roles("APPUSER").and()
.withUser("appownername").password("appoownerpwd").roles("APPOWNER");
}
private static String[] getAppAdminRolePaths(){
return new String[]{"/appweb/*",
"/app/checkService.html",
"/app/index.html",
"/app/testData.html",
"/app/adminView.html",
"/app/demo.html"};
}
private static String[] getAppUserRolePaths(){
return new String[]{"/appweb/*",
"/app/checkService.html",
"/app/index.html",
"/app/testData.html",
"/app/userView.html",
"/app/demo.html"};
}
}
For HTTP username/password popup in browser with url http://localhost:8080/app/index.html say with appadminname/appadminpwd it works fine. But for same url if I enter appusername/appuserpwd it throws HTTP 403 Forbidden access error. Here why is the second role APPUSER configured is throwing this error is I am not sure. Please let know if some way to get this resolved.
Thanks
I appreciate this question is a little old now, but this may still be useful to someone.
Firstly, I'm not sure why your calls to antMatchers() supply null as the first argument; antMatchers() expects a list of strings defining the URLs to be covered by this rule, so I'm not sure what null is expected to match in this case.
Secondly, anyRequest() means that this rule will be applied to any request made to the application regardless of the URL used, and Spring will apply security rules in the order that they are defined. You would typically define URLs and their associated roles first, and then default to a rule for any other request that must be authenticated (but does not necessarily need any specific roles) with something like anyRequest().authenticated()
Your first rule says that any request made to the application must be made by users with the role APPADMIN, which denies you access when you try to log in as appusername, so the second rule to allow APPUSERs is not even processed.
Thirdly, you are making multiple calls to http.authorizeRequests() when you should probably actually be chaining them together, for example:
http.csrf().disable().authorizeRequests()
.antMatchers( getAppAdminRolePaths() ).hasRole("APPADMIN")
.antMatchers( getAppUserRolePaths() ).hasRole("APPUSER")
.anyRequest().authenticated();
Lastly, when you have just a single role to check against, you can use hasRole() instead of hasAnyRole().
You also don't need to supply authenticated() and hasRole() in the same rule because hasRole() implies that the user is already authenticated.
You can find more explanations and examples in the Spring documentation: http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#authorize-requests