Spring security with multiple providers - java

I am setting up an app with 2 differents users:
one from my ldap that can connect with cas authentication
one external, hard coded with a simple formlogin
I created 2 Security configurations:
External User Configuration:
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(1)
public class SecurityVanuatuConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withUsername("user")
.password("{noop}user")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable().antMatcher("/user/**")
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(LOGIN_URL).permitAll()
.loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
.successHandler(successHandler)
.failureHandler(successHandler)
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
Cas Configuration:
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(2)
public class SecurityCasConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().antMatcher("/admin/**")
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(casLogoutFilter(), CasAuthenticationFilter.class);
}
#Bean
public SingleSignOutFilter casLogoutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
return singleSignOutFilter;
}
// if I remove this bean, external configuration works
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(new Cas30ServiceTicketValidator("https://sso.unc.nc/cas"));
provider.setKey("cas");
provider.setAuthenticationUserDetailsService(successHandler);
return provider;
}
Each configuration work when it's alone, but when there is both, the external doesn't work.
It seems that the CasAuthenticationProvider bean prevent the formLogin to work and I endup in the FailureHandler.
Here is the error:
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
How can I make these 2 configuration work together?

when you mark something as #Bean you are putting it in the pool of beans that spring can use to inject into classes if they need it.
you have registered
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
...
}
as a #Bean which is an AuthenticationProvider. When you register this using #Bean it will get picked up by everyone that needs it, meaning that both your WebSecurityConfigurerAdapter will use it.
Spring has no way of knowing that you only want this is in one of your security configurations and not the other.
you have to explicitly define that you want it in one and not the other by removing #Bean and setting it manually.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
}
public CasAuthenticationProvider casAuthenticationProvider() {
...
}
You have to always decied if you want to set something manually, or use #Bean and let spring inject it for you.
So for instance, you are registering a filter (casLogoutFilter) setting it manually but also defining it as #Bean telling spring to inject it into the filter chain, which sort of doesn't make sense.

Related

Disable securitycontext without using depricated WebSecurityConfigurerAdapter

I'm trying to rewrite following class in order to get rid of the depricated WebSecurityConfigurerAdapter:
#EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity security) throws Exception {
security.mvcMatcher("/my/path/*").securityContext().disable();
}
}
And I've tried to rewrite this with the help of the official Spring documentation. The following two attempts resulted in 403 Errors when trying to access resources on that path:
#EnableWebSecurity
public class MyWebSecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
security.mvcMatcher("/my/path/*").securityContext().disable();
return security.build();
}
}
#EnableWebSecurity
public class ConsentWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public WebSecurityCustomizer webSecurityCustomizer() throws Exception {
return (web) -> web.ignoring().mvcMatchers("/v1/containers/*");
}
}
While in the original code everything is running
I also faced the same scenario of discarding the deprecated method and replacing it with SecurityFilterChain
if you want to disable the security on given path then try this:
security.mvcMatcher("/my/path/*").permitAll();
Edit: Here is my migrated code which worked fine with permitting every request without authentication.
#Configuration
#EnableWebMvc
public class SecurityConfig {
#Autowired
private UserDetailsService userDetailsService;
#Bean
protected SecurityFilterChain authorizationConfig(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/post/**", "/newcomment/**", "/page/**","/api/","/api/posts/filter",
"/api/comments").permitAll();
return httpSecurity.build();
}
You can use below code for reference
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration {
private final String[] WHITE_LABEL_URLS = {"/blogapp", "/usercreation", "/css/**", "/saveuser", "/page/**"};
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic()
.and()
.authorizeHttpRequests()
.antMatchers(WHITE_LABEL_URLS).permitAll()
.anyRequest().authenticated()
.securityContext().disable();
return httpSecurity.build();
}
}

include security filters when running controller tests

In my Spring Boot app, I'm trying to remove the usage of the deprecated class WebSecurityConfigurerAdapter for configuring security, as recommended in this blog post.
Previously, my security configuration class looked like this:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// the filter classes constructor arguments are
// #Autowired fields of SecurityConfiguration
var jwtAuthenticationFilter = new JwtAuthenticationFilter(
authenticationManager(),
authenticationAuditService,
jwtTokenService,
roleResolver,
objectMapper,
messageSourceAccessor,
roleActivityService);
var jwtAuthorisationFilter = new JwtAuthorisationFilter(jwtTokenService, userRepository, userRoleRepository);
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class)
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
All this really does is create 2 Spring Security filters and adds them to the filter chain. Following the removal of WebSecurityConfigurerAdapter, the class now looks like this:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
// the filter classes constructor arguments are
// #Autowired fields of SecurityConfiguration
var jwtAuthenticationFilter = new JwtAuthenticationFilter(
authenticationConfiguration.getAuthenticationManager(),
authenticationAuditService,
jwtTokenService,
roleResolver,
objectMapper,
messageSourceAccessor,
roleActivityService);
var jwtAuthorisationFilter = new JwtAuthorisationFilter(jwtTokenService, userRepository, userRoleRepository);
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class)
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
However, following these changes a bunch of tests are failing. It seems that the reason for this is because the security filters are no longer loaded. For example, the following test passed before making the changes above
#WebMvcTest(MyController.class)
#WithMockUser
public class MyControllerTests {
#Autowired
private MyController controller;
#Test
void deleteRole_ShouldThrowsAccessDeniedException_WhenUserHasInvalidRole() {
assertThrows(AccessDeniedException.class, () -> this.controller.deleteRole(UUID.randomUUID()));
}
}
The controller method/action that is being tested is:
#PreAuthorize("hasAuthority('SYSTEM_ADMIN')")
public void deleteRole(#PathVariable UUID id) {
// implementation omitted
}
It appears the test is now failing because the security filters required to implement the role-checking defined by #PreAuthorize are not present.
Is there a way to force the security configuration to be loaded when the tests are run?
You need to annotate your test with
#Import(SecurityConfiguration.class)
The filter for the context slice of #WebMvcTest automatically includes implementations of WebSecurityConfigurerAdapter that are annotated with #Component but it doesn't scan #Bean factory methods of configuration classes.
This is also noted in the release notes for Spring Boot 2.7. There are more details on the discussion here: https://github.com/spring-projects/spring-boot/issues/31162

Using multiple WebSecurityConfigurerAdapter with different AuthenticationProviders (basic auth for API and LDAP for web app)

According the Spring Security Reference section 5.7 it should be possible to define more than one security adapter.
I try to do the same but without success. After a server reboot, the first x times the API works fine with basic auth, but after a couple of times I'm redirected to the login (form) page, this should only happen for our web app, not for the API calls.
My code:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
withUser("admin").password("pw_test").roles(API_ROLE);
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/services/**")
.authorizeRequests()
.anyRequest().hasRole(API_ROLE)
.and()
.httpBasic()
.and()
.csrf()
.disable();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
auth.eraseCredentials(false);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// LDAP FORM AUTHENTICATION
http.authorizeRequests()
.antMatchers("/login.html").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/images/**").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.failureUrl("/login.html?error=1")
.loginPage("/login.html")
.loginProcessingUrl("/j_spring_security_check")
.defaultSuccessUrl("/success.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll();
http.csrf().disable();
// iFRAMES SETTINGS
http
.headers()
.frameOptions().sameOrigin()
.httpStrictTransportSecurity().disable();
// HTTPS
http
.requiresChannel()
.anyRequest()
.requiresSecure();
//MAP 8080 to HTTPS PORT
http.portMapper().http(8080).mapsTo(443);
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
CustomLdapAuthenticationProvider provider = new CustomLdapAuthenticationProvider(env.getProperty("ldap.domain"), env.getProperty("ldap.url"), env.getProperty("ldap.base"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
}
Any idea?
I'm using Spring Boot version 1.4.1-RELEASE and Spring Security version 4.1.3-RELEASE.
You use the same AuthenticationManager for both configurations, because you autowire the same AuthenticationManagerBuilder.
See Spring Security Architecture:
#Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
... // web stuff here
#Autowired
public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
This example relates to a web application, but the usage of AuthenticationManagerBuilder is more widely applicable (see below for more detail on how web application security is implemented). Note that the AuthenticationManagerBuilder is #Autowired into a method in a #Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way:
#Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
... // web stuff here
#Override
public void configure(AuthenticationManagerBuilder builder) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
(using an #Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one.

Spring Security ignore path for filter

How can I configure Spring Security to use a custom filter for all requests except the ones I whitelist in the same level, e.g. "/login" skips my filter but every thing else "/**" goes through the filter.
As a workaround I could use different prefixes, "/secured/**" vs "/whitelist/**" or ignore the whitelisted ones in the filter, but that does not seem to be a clean solution.
I already tried setting up two configurations with #Order(1 and 2) but it didn't work.
#EnableWebSecurity
public class SpringSecurityConfig {
#Configuration
#Order(1)
#EnableGlobalMethodSecurity(securedEnabled = true)
public static class JwsSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private StatelessAuthenticationFilter statelessAuthenticationFilter;
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/login");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").authenticated()
.anyRequest().authenticated()
.and().addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
}

Spring OAuth2 Resource allowed only for clients and token auto creation

I have an App that is using OAuth2 password grant type to manage the user authorizations to his resources. All App resources are only allowed access for a client with once provided token to act on behalf of some user, except the URI to create users, this one I want that only authenticaed clients have access to it. I'm using spring-security-oauth2 as my OAuth implementation and but can't figure out how to accomplish this in a less hacky way than the one described bellow:
POST /users to be acessed only by authenticated clients.
Currently I figured out how to this by removing #EnableAuthorizationServer and creating a new class and extending AuthorizationServerSecurityConfiguration class and overriding method: configure( HttpSecurity http ) and creating a new #Configuration class and #Import AuthorizationServerEndpointsConfiguration and CustomAuthorizationServerSecurityConfiguration.
The problem is that, in my new custom class I need to override and copy/paste the entire method original code in the overrided method, ending with something like:
#Override
protected void configure( HttpSecurity http ) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
configure(configurer);
http.apply(configurer);
String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
http
.authorizeRequests()
.antMatchers(tokenEndpointPath).fullyAuthenticated()
.antMatchers( HttpMethod.POST, "/users/**").fullyAuthenticated()
.antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
.antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
.and()
.requestMatchers()
.requestMatchers( new AntPathRequestMatcher(tokenKeyPath),
new AntPathRequestMatcher(tokenEndpointPath),
new AntPathRequestMatcher(checkTokenPath),
new AntPathRequestMatcher("/users/**", HttpMethod.POST.name()));
http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}
My first question is, the a better way to do this?
The second thing that I want to do is to auto create the AccessToken by password grant type when a new user is created (in the URI POST /users), and I can't figure any way to do this.
Can someone provide any insight on this two needs?
Thanks
Not sure if this is what you are asking but what I understad is that you want
to configure specific security constrains for request on /users endpoint with POST method. so
this is how I would do this.I do not think that extending
AuthorizationServerSecurityConfiguration is neccesary since recomended way
is usually to extend just WebSecurityConfigurerAdapter in your main
security config class, remember that you can configure your HttpSecurity multiple times for multiple endpoints, but if you configure the same endpoint in multiple places the last configuration read will be the one active
#EnableWebSecurity public class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
//other methods ...
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super.authenticationManagerBean();
}
#Order(1)
#Override
protected void configure(HttpSecurity http) throws Exception {
//configure your path here
//I purposly configured GET user to
// permit all to see diference
//for example
// #formatter:off
http
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/user")
.permitAll()
.antMatchers(HttpMethod.POST,"/user")
.fullyAuthenticated()
.and().csrf().disable()
.formLogin();
// #formatter:on
}
}
and then in your Ouath configuration
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources
.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requestMatchers()
.antMatchers("/resources/**","/greeting")
.and()
.authorizeRequests()
.antMatchers("/resources").access("#oauth2.hasScope('read') or hasRole('ROLE_USER')")
.antMatchers("/greeting").access("#oauth2.hasScope('read')");
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// #formatter:off
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(authenticationManager);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password","refresh_token")
.authorities("USER")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret("123456");
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
}
As you can see above HttpSecurity is conconfigured twice once in class that extends WebSecurityConfigurerAdapter and also in your class extendingResourceServerConfigurerAdapter for your Ouath configuration
part of this example is taken from this gitHub example by royclarkson
https://github.com/royclarkson/spring-rest-service-oauth
I am not sure what you are asking about in your second question, could you clarify ?

Categories