User Impersonation with Spring Security - spring-security

I am trying to implement a user impersonation using Spring Security and its SwitchUserFilter.
Currently the Configuration looks as follows:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/impersonate*").hasRole("Administrator");
http.addFilter(switchUserFilter());
super.configure(http);
setLoginView(http, ViewLogin.class);
}
...
#Bean
public SwitchUserFilter switchUserFilter(){
SwitchUserFilter filter = new SwitchUserFilter();
filter.setUserDetailsService(userDetailsService);
filter.setSwitchUserUrl("/impersonate");
filter.setSwitchFailureUrl("/switchUser");
filter.setTargetUrl("/");
return filter;
}
And I was trying to navigate to the impersonation using:
UI.getCurrent().getPage().setLocation("/impersonate?username="+username);
Unfortunately Vaadin is trying to navigate to the page "/impersonate..." but doesn't find a corresponding Page and skips the SwitchUserFilter.
How would I force the switch?

If you use setSwitchUserUrl it will match only POST requests [1].
But you want to use a GET request. So you have to use a matcher like
this:
filter.setSwitchUserMatcher(new AntPathRequestMatcher("/impersonate", "GET"));
BTW: you don't have to addFilter the filter, if you define it as
a #Bean.
[1]
https://github.com/spring-projects/spring-security/blob/e125a76687d4ca9739cd663eedc107c7ff55e8cf/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java#L513-L515

Related

How to properly configure spring-security with vaadin14 to handle 2 entry points - keyclaok and DB

I have a vaadin14 application that I want to enable different types of authentication mechanisms on different url paths. One is a test url, where authentication should use DB, and the other is the production url that uses keycloak.
I was able to get each authentication mechanism to work separately, but once I try to put both, I get unexpected results.
In both cases, I get login page, but the authentication doesn't work correctly. Here's my security configuration, what am I doing wrong?
#Configuration
#EnableWebSecurity
public class ApplicationSecurityConfiguration {
#Configuration
#Order(2)
public static class DBAuthConfigurationAdapter extends WebSecurityConfigurerAdapter {
private static final String LOGIN_PROCESSING_URL = "/login";
private static final String LOGIN_FAILURE_URL = "/login?error";
private static final String LOGIN_URL = "/login";
private static final String LOGOUT_SUCCESS_URL = "/login";
/**
* Require login to access internal pages and configure login form.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Not using Spring CSRF here to be able to use plain HTML for the login page
http.csrf().disable()
// Register our CustomRequestCache, that saves unauthorized access attempts, so
// the user is redirected after login.
.requestCache().requestCache(new CustomRequestCache())
// Restrict access to our application.
.and().antMatcher("/test**").authorizeRequests()
// Allow all flow internal requests.
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
// Allow all requests by logged in users.
.anyRequest().hasRole("USER")
// Configure the login page.
.and().formLogin().loginPage(LOGIN_URL).permitAll().loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
// Configure logout
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
#Bean
#Override
public UserDetailsService userDetailsService() {
Properties users = null;
try {
users = PropertiesLoaderUtils.loadAllProperties("users.properties");
return new InMemoryUserDetailsManager(users);
} catch (IOException e) {
e.printStackTrace();
}
UserDetails user =
User.withUsername("user")
.password("{noop}password")
.roles("ACTOR")
.build();
return new InMemoryUserDetailsManager(user);
}
/**
* Allows access to static resources, bypassing Spring security.
*/
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(
// Vaadin Flow static resources
"/VAADIN/**",
// the standard favicon URI
"/favicon.ico",
// the robots exclusion standard
"/robots.txt",
// web application manifest
"/manifest.webmanifest",
"/sw.js",
"/offline-page.html",
// icons and images
"/icons/**",
"/images/**",
// (development mode) static resources
"/frontend/**",
// (development mode) webjars
"/webjars/**",
// (development mode) H2 debugging console
"/h2-console/**",
// (production mode) static resources
"/frontend-es5/**", "/frontend-es6/**",
"/resources/**");
}
}
#Order(1)
#Configuration
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public static class AppKeycloakSecurity extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(
AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider
= keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(
new SessionRegistryImpl());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.httpBasic().disable();
http.formLogin().disable();
http.anonymous().disable();
http.csrf().disable();
http.headers().frameOptions().disable();
http
.antMatcher("/prod**")
.authorizeRequests()
.antMatchers("/vaadinServlet/UIDL/**").permitAll()
.antMatchers("/vaadinServlet/HEARTBEAT/**").permitAll()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().hasRole("actor");
http
.logout()
.addLogoutHandler(keycloakLogoutHandler())
.logoutUrl("/logout").permitAll()
.logoutSuccessUrl("/");
http
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class);
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
http
.sessionManagement()
.sessionAuthenticationStrategy(sessionAuthenticationStrategy());
}
}
}
Navigating within a Vaadin UI will change the URL in your browser, but it will not necessarily create a browser request to that exact URL, effectively bypassing the access control defined by Spring security for that URL. As such, Vaadin is really not suited for the request URL-based security approach that Spring provides. For this issue alone you could take a look at my add-on Spring Boot Security for Vaadin which I specifically created to close the gap between Spring security and Vaadin.
But while creating two distinct Spring security contexts based on the URL is fairly easy, this - for the same reason - will not work well or at all with Vaadin. And that's something even my add-on couldn't help with.
Update: As combining both security contexts is an option for you, I can offer the following solution (using my add-on):
Starting from the Keycloak example, you would have to do the following:
Change WebSecurityConfig to also add your DB-based AuthenticationProvider. Adding your UserDetailsService should still be enough. Make sure to give every user a suitable role.
You have to remove this line from application.properties: codecamp.vaadin.security.standard-auth.enabled = false
This will re-enable the standard login without Keycloak via a Vaadin view.
Adapt the KeycloakRouteAccessDeniedHandler to ignore all test views that shouldn't be protected by Keycloak.
I already prepared all this in Gitlab repo and removed everything not important for the main point of this solution. See the individual commits and their diffs to also help focus in on the important bits.

How to make certain endpoint in spring security to be allowed without authentication?

I'm developing an application using Spring security.
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.csrf().disable().authorizeRequests().antMatchers("/api/client/findByVariable?variable=").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().requestMatchers()
.antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
.access("#oauth2.hasScope('read') or (!#oauth2.isOAuth() and hasRole('USER'))");
// #formatter:on
}
How can I modify above code so that endpoint "/api/client/findByVariable?variable=" can be allowed to be accessed without requiring authentication just as if there was no Spring Security ?
I tried adding the line :
http.csrf().disable().authorizeRequests().antMatchers("/api/client/findByVariable?variable=").permitAll();
But it is not working
You can ignore endpoints with overriding the configure method that gives you a WebSecurity instance:
#Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers("/api/client/findByVariable");
}
I'm not sure if you can also match by query params like /api/client/findByVariable?variable=*

Spring Security OAuth2 - optional login with server check

I'm working on a Web Project with different Spring Boot WebMVC Clients. Some of this Clients needs a authorization and I solved it with a Spring Security OAuth2 Server. The authentication works fine and I had no problems. Some Clients didn't need an login and they are public for all.
Technical facts: All clients use a mix between Angular, jQuery and simple JSP's. All apps use Spring Security and the public app configuration is like this:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.anyRequest().authenticated();
}
Now my question: I plan to build a Login (or Logout) Button in the Header of all apps. In the apps with a required authentication is that no problem. I can check the principal is null or not. But how can I solve this in public apps. The principal is ever null and the client didn't check the authentication status with the server. I had some ideas to fix it but nothing is working. The best way would be a automatic check in Spring Boot. But how can I configure this? Maybe I can check it with JavaScript, but my shots also didn't work.
Maybe it would help - two of my apps:
https://www.planyourtrip.travel (public application)
https://profile.planyourtrip.travel (memberonly application)
UPDATE: Maybe a better example
If I configure a public app like this
#Configuration
#EnableOAuth2Sso
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
and the MVC Controller like this
#RequestMapping("/{([a-z]{2})}")
public ModelAndView start(final Principal principal) {
return new ModelAndView("start");
}
then is the Principal ever null. I think that is my Problem. I need a check with the OAuth Server and if i logged in is the principal set and if I'm not logged in it should be null.
If I had understood your question correctly, than you need that some URL pattern can be accessed without authentication. Than in that case you can use the following method to prevent authentication for certain URL patterns -
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/urlPattern");
}
permitAll() method defines that all the authenticated users can access mentioned URL pattern. So if you want some users to access some resources (URL) without authentication, than you have to use above method.

Keycloak with vaadin and spring security

I want to secure my vaadin application with keycloak and spring security. I try to use the "keycloak-spring-security-adapter".
My problem is that I want also unauthenticated users to use my application, but with less functionality - I do this with method security and checking which roles the current user has in the UI.
Can I configure the filter so that it ignores unauthenticated requests, but If the token is present uses it?
Thanks
Daniel
A working example of what you want can be found in the public-access branch of this github project. It does use Vaadin 8 though.
In essence, you can setup your application to be partially public, i.e. accessibly to unauthenticated user for certain parts and requires login for others, as follows:
#Configuration
#EnableWebSecurity
#EnableVaadinSharedSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().disable();
http.formLogin().disable();
http.csrf().disable();
http
.authorizeRequests()
.antMatchers("/vaadinServlet/UIDL/**").permitAll()
.antMatchers("/vaadinServlet/HEARTBEAT/**").permitAll()
.anyRequest().permitAll();
http
.logout()
.addLogoutHandler(keycloakLogoutHandler())
.logoutUrl("/sso/logout").permitAll()
.logoutSuccessUrl("/");
http
.addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
.addFilterBefore(keycloakAuthenticationProcessingFilter(), BasicAuthenticationFilter.class);
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
http
.sessionManagement()
.sessionAuthenticationStrategy(sessionAuthenticationStrategy());
}
...
}
The line http.anyRequest().permitAll(); is the most important where you configure the filter to just allow all requests. You could still update this to only allow public access to certain urls.
You can then use spring security annotations on methods/views/components to configure your fine-grained access control. E.g:
#SpringComponent
#Secured("ROLE_ANONYMOUS")
public class LoginOperation implements Runnable {
#Override
public void run() {
// login logic
}
}
and
#Secured("ROLE_USER")
public class LogoutOperation implements Runnable {
#Override
public void run() {
// logout logic
}
}

spring boot basic http authentication with multiple roles throws 403 forbidden error

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

Resources