I have working REST API under Spring 4 using Basic authentication. These REST services are under /api/v1/** URL. However, I want to add another set of REST endpoints under different url /api/v2/**, but protected with token-based authentication.
Is it possible to do this with one servlet ? How to configure Spring Security to use different forms of authentication for different URLs ?
Thank you.
Here's a code sample in Java config that uses UserDetailsService and has different security configurations for different URL endpoints:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.httpBasic()
.realmName("API")
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated();
}
}
#Configuration
#Order(2)
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v2/**")
/* other config options go here... */
}
}
}
Related
I'm trying to build a microservice architecture. But i'm struggling since two days with OAuth2 and Zuul. I managed to run a Auth/Resource-Server on the same service, but not with Zuul. At the moment i'm swichting to another service, the authorization doesn't work anymore. I tried many guides (as an example Baeldung) but no one works for me. Probably because i'm using Spring Boot 2.0.3? Most guides using Spring Boot 1.5.x.
I think it's a problem of configuration. I'm using Eureka for service discovering, Zuul as Gateway and entry point. When the user request a protected service, he should be redirectet to my auth-service (OAuth2/JWT). The token he gets after login should be stored by Zuul (right ?). Actually Zuul doesn't get the token or doesn't store it. Do I have to do this by my own or should Zuul and OAuth manage this and I just have bad configurations? Could someone show me, how you configure this architecture or a new/working guide for Spring Boot 2.0.3? I'm actually really frustrated, need help. I'm new to Spring, but have to learn it for work. But at the moment i'm just overstrained.
Additional infos:
I didnt create any views now. I just defined some default controller which return Strings and are secured by #PreAuthorize.
Gateway-Service:
GatewayServiceApplication.java
#SpringBootApplication
#EnableZuulProxy
#EnableDiscoveryClient
#Configuration
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
SecurityConfig.java
#Configuration
#EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/", "/core/", "/core/login**", "/oauth/authorize",
"/core/oauth/authorize", "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().permitAll();
}
}
Here I had a lot of different antMatchers.
application.properties
server.port=8000
spring.application.name=gateway-service
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/
security.oauth2.sso.login-path=http://localhost:8000/core/login
security.oauth2.client.client-id=zuul
security.oauth2.client.client-secret=zuul
security.oauth2.client.access-token-uri=http://localhost:8000/core/oauth/token
security.oauth2.client.user-authorization-uri=http://localhost:8000/core/oauth/authorize
#security.oauth2.resource.user-info-uri=http://localhost:8000/core/user/me
security.oauth2.resource.user-info-uri=http://localhost:8000/core/secured
spring.thymeleaf.cache=false
I think here's a failure.
Core-Service
CoreApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableResourceServer
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
AuthServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
public void configure(
AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("zuul")
.secret(passwordEncoder.encode("zuul"))
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true)
.redirectUris("http://localhost:8000/core/secured");
}
}
SecurityConfig
#Configuration
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("john")
.password(passwordEncoder().encode("123"))
.roles("USER");
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
application.properties
server.port=8003
spring.application.name=core
eureka.client.service-url.defaultZone=http://localhost:8001/eureka
Ok, especially here I changed many things. So probably I destroyed much from older guides. (Sorry my english is bad!)
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.
I have a spring boot setup with an OAuth2 authorization and resource server. The user is able to acquire tokens by making a POST request to /oauth/token. So far, so good.
However, I don't want to protect /oauth/token via BASIC auth but by means of a custom security filter.
I tried the following but DemoAuthenticationFilter is never called:
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// ...
#Override
public void configure(HttpSecurity http) throws Exception {
// ...
http.addFilterBefore(new DemoAuthenticationFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/oauth/token").authenticated();
}
}
Also, if I try to add it to WebSecurityConfigurerAdapter the filter is only called after the request is authenticated via OAuth2:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// ...
#Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.addFilterBefore(new DemoAuthenticationFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/oauth/token").authenticated();
}
}
Some simple example how to achieve this would be really helpful. Thank you!
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients().addTokenEndpointAuthenticationFilter(new AligenieFilter());
}
//....
}
It works for me.
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();
}
}
}
I am having some issues getting my application set up using method level annotation controlled by #EnableGlobalMethodSecurity I am using Servlet 3.0 style initialization using
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(MultiSecurityConfig.class);
}
}
I have attempted 2 different ways of initialising an AuthenticationManager both with their own issues. Please note that not using #EnableGlobalMethodSecurity results in a successful server start up and all of the form security executes as expected. My issues arise when I add #EnableGlobalMethodSecurity and #PreAuthorize("hasRole('ROLE_USER')") annotations on my controller.
I am attempting to set up form-based and api-based security independently. The method based annotations need only work for the api security.
One configuration was the following.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
}
This is not ideal as I really want only a single registration of the authentication mechanism but the main issue is that it results in the following exception:
java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found []
As far as I am aware #EnableGlobalMethodSecurity sets up its own AuthenticationManager so I'm not sure what the problem is here.
The second configuration is as follows.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR)
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.and()
.build();
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
}
}
This config actually starts successfully but with an exception
java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required
at org.springframework.security.authentication.ProviderManager.checkState(ProviderManager.java:117)
at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:106)
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.performBuild(AuthenticationManagerBuilder.java:221)
and when I test I found that the security doesn't work.
I've been looking at this for a couple of days now and even after diving into spring security implementation code I can't seem to find what is wrong with my configuration.
I am using spring-security-3.2.0.RC1 and spring-framework-3.2.3.RELEASE.
When you use the protected registerAuthentication methods on WebSecurityConfigurerAdapter it is scoping the Authentication to that WebSecurityConfigurerAdapter so EnableGlobalMethodSecurity cannot find it. If you think about this...it makes sense since the method is protected.
The error you are seeing is actually a debug statement (note the level is DEBUG). The reason is that Spring Security will try a few different ways to automatically wire the Global Method Security. Specifically EnableGlobalMethodSecurity will try the following ways to try and get the AuthenticationManager:
If you extend GlobalMethodSecurityConfiguration and override the registerAuthentication it will use the AuthenticationManagerBuilder that was passed in. This allows for isolating the AuthenticationManager in the same way you can do so with WebSecurityConfigurerAdapter
Try to build from the global shared instance of AuthenticationManagerBuilder, if it fails it logs the error message you are seeing (Note the logs also state "This is ok for now, we will try using an AuthenticationManager directly")
Try to use an AuthenticationManager that is exposed as a bean.
For your code, you are going to be best off using something like the following:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
// Since MultiSecurityConfig does not extend GlobalMethodSecurityConfiguration and
// define an AuthenticationManager, it will try using the globally defined
// AuthenticationManagerBuilder to create one
// The #Enable*Security annotations create a global AuthenticationManagerBuilder
// that can optionally be used for creating an AuthenticationManager that is shared
// The key to using it is to use the #Autowired annotation
#Autowired
public void registerSharedAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
}
NOTE: More documentation around this will be getting added to the reference in the coming days.