I'm trying to secure endpoints Actuators inside Spring Boot project. However instead using ready-to-run Spring Security configuration for Actuators:
management:
security:
enabled: true
role: ADMINISTRATOR
That too easy I need to plug Actuators with our custom security (here CAS SSO).
First try it was to add context-path for Actuators:
management:
security:
enabled: true
role: ADMINISTRATOR
context-path: /management
And update my WebSecurityConfigurerAdapter configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.authorizeRequests()..antMatchers("/management/**").hasRole(Role.ADMINISTRATOR.toString());
...
}
It works but I must hardcode Actuators context-path, so when I want to update management.context-path I have to update my security.
I know it's possible to retrieve value of management.context-path but how to manage it when value equals ""?
You can answer me to #Autowired EndpointHandlerMapping and retrieve list of Actuators endpoints... Finally I will copy-past same logic as ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter.
Furthermore ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter #ConditionalOnMissingBean is pointing itself but ManagementSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter is inner-static protected class so not possible to disable it without passing parameter management.security.enabled=false and that can be strange because your configuration said management.security.enabled=false but in reality endpoints are secured...
Conclusion
Is there a way to override (just a part of) properly Actuators security
May I miss something and I'm totally wrong?
There is already a pending Issue on Github. For the moment Dave Syer proposes:
I think copy-paste of all the code in there is actually the best
solution for now (and set management.security.enabled=false to let
Boot know you want to do it yourself).
I have not tested whether a runtime exception will be thrown but I think that you can reuse ManagementWebSecurityConfigurerAdapter and save a lot of copy-paste action. At least compiler doesn't complain.
Put your configuration class under package org.springframework.boot.actuate.autoconfigure in your project and extend from ManagementWebSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter. Don't miss all the annotations from ManagementWebSecurityConfigurerAdapter. That is the only copy-paste action here because class annotations can not be inherited by subclass.
package org.springframework.boot.actuate.autoconfigure;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
#Configuration
#ConditionalOnProperty(prefix = "management.security", name = "enabled", matchIfMissing = true)
#Order(ManagementServerProperties.BASIC_AUTH_ORDER)
public class SsoManagementWebSecurityConfigurerAdapter extends ManagementWebSecurityAutoConfiguration.ManagementWebSecurityConfigurerAdapter {
//TODO your SSO configuration
}
Don't forget to #Import your configuration in your #SpringBootApplication.
Related
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.
Link - https://github.com/spring-cloud-stream-app-starters/aggregator/tree/master/spring-cloud-starter-stream-processor-aggregator does not list property for gemfire message store
The GemfireMessageStore is configured like this:
#ConditionalOnClass(GemfireMessageStore.class)
#ConditionalOnProperty(prefix = AggregatorProperties.PREFIX,
name = "message-store-type",
havingValue = AggregatorProperties.MessageStoreType.GEMFIRE)
#Import(ClientCacheAutoConfiguration.class)
static class Gemfire {
#Bean
#ConditionalOnMissingBean
public ClientRegionFactoryBean<?, ?> gemfireRegion(GemFireCache cache, AggregatorProperties properties) {
ClientRegionFactoryBean<?, ?> clientRegionFactoryBean = new ClientRegionFactoryBean<>();
clientRegionFactoryBean.setCache(cache);
clientRegionFactoryBean.setName(properties.getMessageStoreEntity());
return clientRegionFactoryBean;
}
#Bean
public MessageGroupStore messageStore(Region<Object, Object> region) {
return new GemfireMessageStore(region);
}
}
The point is that you always can override that ClientRegionFactoryBean with your own.
Or you can take into account that ClientCacheAutoConfiguration is based on the #ClientCacheApplication, which, in turn, allows you to have a ClientCacheConfigurer bean and provide whatever is sufficient for your client cache configuration. Including config and pool. That's right: it is not on the app starter configuration level and you have to right some custom code to be included as a dependency into the final uber jar for target binder-specific application.
More info how to build them is here in Docs: https://docs.spring.io/spring-cloud-stream-app-starters/docs/Einstein.RC1/reference/htmlsingle/#_patching_pre_built_applications
A variety of backend storage options exist through Spring Integration. You can read more about it in spring-cloud-starter-stream-processor-aggregator/README.
Spring Integration docs on this matter are included as a link, and the Gemfire section could be useful.
It'd be also useful to review MessageGroupStore implementation, since it is the foundation for the storage option in aggregator.
Changing the Order of the Spring Security WebFilter
I have an API Gateway implemented using Spring Cloud Gateway that uses Spring Security. Spring Security for WebFlux is implemented as a WebFilter right at the beginning of the filter chain. So after successful authentication the request would be forwarded to Spring Cloud Gateway's RoutePredicateHandlerMapping, which would try to deduce the destination based on the URL pattern, and then it would go to a FilteringWebHandler to execute the other filters of Spring Cloud Gateway.
My problem is the following: I have implemented a customized authentication algorithm which uses query string and header variables as credentials for authentication according to the requirements of the project, an this is working without any problem. The problem occurred when we needed to add a small customization for the authentication algorithm that is path independent. When the request reaches the WebFilter of Spring Security, pattern matching is not yet done so I do not know which application does it point to, for example:
app1:
-Path: /app1/**
app2:
-Path: /app2/**
Which means that instead of having authentication -> route mapping -> filtering web handler I should do route mapping -> authentication -> filtering web handler. Not that these three components are not similar, one of them is a filter another is a mapper and the last one is web handler. Now I know how to customize them but the problem is that I do not know how to intercept the Netty server building process in order to change the order of these operations. I need to wait for the building process to end and alter the content of the server before it starts. How can I do that?
EDIT: here is the final solution:
So here is how I did it:
Goal: removing the WebFilter of Spring Security from the default HttpHandler, and inserting it between RoutePredicateRouteMapping and the FilteringWebHandler of Spring Cloud Gateway
Why: Because I need to know the Application ID while carrying on my customized authentication process. This Application ID is attached to the request by the RoutePredicateRouteMapping by matching the request's URL to a predefined list.
How did I do it:
1- Removing the WebFilter of Spring Security
I created an HttpHandler bean that invokes the default WebHttpHandlerBuilder and then customize the filters. As a bonus, I removed unneeded filters in order to increase the performance of my API Gateway
#Bean
public HttpHandler httpHandler() {
WebHttpHandlerBuilder webHttpHandlerBuilder = WebHttpHandlerBuilder.applicationContext(this.applicationContext);
MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter = this.applicationContext.getBean(MY_AUTHENTICATED_HANDLER_BEAN_NAME, MyAuthenticationHandlerAdapter.class);
webHttpHandlerBuilder
.filters(filters ->
myAuthenticationHandlerAdapter.setSecurityFilter(
Collections.singletonList(filters.stream().filter(f -> f instanceof WebFilterChainProxy).map(f -> (WebFilterChainProxy) f).findFirst().orElse(null))
)
);
return webHttpHandlerBuilder.filters(filters -> filters
.removeIf(f -> f instanceof WebFilterChainProxy || f instanceof WeightCalculatorWebFilter || f instanceof OrderedHiddenHttpMethodFilter))
.build();
}
2- Wrapping Spring Cloud Gateway's FilteringWebHandler with Spring Web's FilteringWebHandler with the added WebFilter
I created my own HandlerAdapter which would match against Spring Cloud Gateway's FilteringWebHandler and wrap it with Spring Web's FilteringWebHandler plus the security filter I extracted in the first step
#Bean
public MyAuthenticationHandlerAdapter myAuthenticationHandlerAdapter() {
return new MyAuthenticationHandlerAdapter();
}
public class MyAuthenticationHandlerAdapter implements HandlerAdapter {
#Setter
private List<WebFilter> securityFilter = new ArrayList<>();
#Override
public boolean supports(Object handler) {
return handler instanceof FilteringWebHandler;
}
#Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
org.springframework.web.server.handler.FilteringWebHandler filteringWebHandler = new org.springframework.web.server.handler.FilteringWebHandler((WebHandler) handler, securityFilter);
Mono<Void> mono = filteringWebHandler.handle(exchange);
return mono.then(Mono.empty());
}
}
This way I could achieve better performance with highly customized HttpHandler pipeline that I suppose to be future-proof
END EDIT
Spring Security for WebFlux is implemented as a WebFilter which is executed almost as soon as a request is received. I have implemented custom authentication converter and authentication manager which would extract some variables from the header and URL and use them for authentication. This is working without any problem.
Now I needed to add another variable taken from RoutePredicateRouteMapping before authentication is done. What I want exactly is to remove the WebFilter (called WebFilterChainProxy) from its current position and put it between the RoutePredicateRouteMapping and the FilteringWeHandler.
Here is how the default process goes:
ChannelOperations calls ReactorHttpHandlerAdapter which calls HttpWebHandlerAdapter, ExceptionHandlingWebHandler, and then org.springframework.web.server.handler.FilterWebHandler.
This WebHandler would invoke its filters and then call the DispatchHandler. One of those filters is the WebFilterChainProxy that does the authentication for Spring Security. So first step is removing the filter from here.
Now the DispatchHandler which is called after the filters would invoke RoutePredicateHandlerMapping, which would analyze the routes and give me the route ID that I need, and then it would call the org.springframework.cloud.gateway.handler.FilteringHandler (this is not the same FilteringHandler above), and that in turn would call the other filters of the Spring Cloud Gateway. What I want here is to invoke the filter after RoutePredicatehandlerMapping and before org.springframework.cloud.gateway.handler.FilteringHandler.
What I ended doing was the following:
I created and WebHttpHandlerBuilder that would remove WebFilterChainProxy and pass it as a parameter to a customized DispatcherHandler. Now that the filter is removed the request would pass the first layers without requiring authentication. In my customized DispatcherHandler I would invoke the RoutePredicateHandlerMapping and then pass the exchange variable to the WebFilterChainProxy to do the authentication before passing it to the org.springframework.cloud.gateway.handler.FilteringHandler, which worked perfectly!
I still think that I'm over engineering it and I hope that there is a way to do it using annotations and configuration beans instead of all these customized classes (WebHttpHandlerBuilder and DispatcherHandler).
You should probably implement that security filter as a proper GatewayFilter, since only those are aware of the other GatewayFilter instances and can be ordered accordingly. In your case, you probably want to order it after the routing one.
Also, please don't cross-post, the Spring team is actively monitoring StackOverflow.
I had a similar problem. The accepted solution, while interesting, was a bit drastic for me. I was able to make it work simply by adding my custom filter before SecurityWebFiltersOrder.AUTHENTICATION in the security configuration. This is similar to what I've done with success in a regular Spring mvc application.
Here's an example using oauth authentication. tokenIntrospector is my custom introspector, and requestInitializationFilter is the filter that grabs the tenant id and stashes it in the context.
#AllArgsConstructor
#Configuration
#EnableWebFluxSecurity
public class WebApiGatewaySecurityConfiguration {
private final GatewayTokenIntrospector tokenIntrospector;
private final GatewayRequestInitializationFilter requestInitializationFilter;
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// #formatter:off
http
.formLogin().disable()
.csrf().disable()
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer.opaqueToken(c -> c.introspector(tokenIntrospector)))
.addFilterBefore(requestInitializationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
return http.build();
// #formatter:on
}
}
By default web application is completely secured for example using
security:
basic:
enabled: true
or by including Spring Cloud OAuth2
I would like to enable particular url to be unsecured for example swagger related stuff
/v2/docs
What is the simplest way of disabling security on one URL and leaving the rest of them protected.
Would like to share same approach across multiple projects - making it sort of autoconfiguration option - if Swagger classes are included then make certain urls unprotected.
Looks like this works:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SwaggerSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/**", "/swagger-ui.html", "/webjars/**", "/configuration/**", "/swagger-resources", "/docs");
}
}
I'm having a set of Sping Data Repositories which are all exposed over Rest by using Spring-data-rest project. Now I want to secure the HTTP, so that only registered users can access the http://localhost:8080/rest/ So for this purpose I add #Secured(value = { "ROLE_ADMIN" }) to all the repositories and I also enable the security by specifying the
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
So now what happens is I go to the rest and it's all good - i'm asked to authenticate. Next thing I do is I go to my website (which uses all the repositories to access the database) but my request fails with
nested exception is org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
which is correct because i'm browsing my website as anonymous user.
So my question is: is there a way to provide method authentication for the REST layer only? To me it sounds like a new annotation is needed (something like #EnableRestGlobalMethodSecurity or #EnableRestSecurity)
I don't know if this will solve your problem, however I managed to get something similar, working for me by creating an event handler for my specific repository, and then used the #PreAuthorize annotation to check for permissions, say on beforeCreate. For example:
#RepositoryEventHandler(Account.class)
public class AccountEventHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
#PreAuthorize("isAuthenticated() and (hasRole('ROLE_USER'))")
#HandleBeforeCreate
public void beforeAccountCreate(Account account) {
logger.debug(String.format("In before create for account '%s'", account.getName()));
}
#PreAuthorize("isAuthenticated() and (hasRole('ROLE_ADMIN'))")
#HandleBeforeSave
public void beforeAccountUpdate(Account account) {
logger.debug(String.format("In before update for account '%s'", account.getName()));
//Don't need to add anything to this method, the #PreAuthorize does the job.
}
}