Spring MVC 4 Global Basic HTTP Authentication - spring-mvc

I need to set up global basic HTTP authentication for a staging server. Nothing fancy. I just want to require username/password to access anything. I also would like to use only Java config. I've experimented with a lot of different solutions, but none of them working. I'm always able to access all resources on the server. This is what I'm doing now:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Configuring HttpSecurity");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("Configuring global AuthenticationManagerBuilder");
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
I can see in the logs that these snippets are being executed. As you can see, in the first method, I am requiring that all requests are authenticated. In the second method, I am specifying in memory authentication.

Your SOP statements are getting printed (while container instantiation) because of #Configuration (which is again not required as it is also declared by #EnableWebSecurity). You still need to register the spring security filter chain in your web.xml or MVC initializer class that extends WebMvcConfigurerAdapter or implements WebApplicationInitializer if you wish to use it with the application filter chain. For example (java config as you are looking for the same):
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(
DispatcherType.REQUEST, DispatcherType.ERROR);
container.addFilter("springSecurityFilterChain",
DelegatingFilterProxy.class).addMappingForUrlPatterns(
dispatcherTypes, false, "/*");
where container is an instance of ServletContext.

Related

Spring boot application not able to access rest path

I am trying to set up the rules for spring boot to allow/deny access for specific paths. I looked up various examples and stack overflow question, but none was helpful. I created the configuration file as follows:
package xyz.blackmonster.window.configs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#EnableWebSecurity
#Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${admin.console.username:admin}")
private String username;
#Value("${admin.console.password:admin}")
private String password;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(username)
.password(passwordEncoder().encode(password)).roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/assets/**", "/api/order/calculate", "/api/order/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/login.html")
.defaultSuccessUrl("/admin/orders.html")
.failureUrl("/login.html?error=true")
.and()
.logout().logoutSuccessUrl("/");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I can access the "/" without a problem. The page is loaded and show with the help of the MVC controller. But the paths that I defined as REST end points, I am not able to access them. I keep getting a 403 response:
{"timestamp":"2018-10-08T19:22:04.963+0000","status":403,"error":"Forbidden","message":"Forbidden","path":"/api/order/calculate"}
What is wrong with my configuration? As you can see from the class, I even went one further and specifically set the calculate end point as oppose to having the ** to include all subpaths.
If "/" is working and "/api/order/calculate" is not, it means that they have different HTTP verbs.
"/" - is a GET request
"/api/order/calculate" - is a POST request
By default, spring security will enable csrf protection (only for POST because GET is considered safe). If you are getting 403, it means that you are not sending csrf header => your access is forbidden.
You said that this is a REST endpoint, so you should disable csrf for this endpoints. To do that, please update your configuration with:
1.disable csrf in general (not recommended if you have web forms)
http.csrf()
.disable();
2.if you need to ignore csrf only for specific endpoints, you can add:
http.csrf()
.ignoringAntMatchers("/api/order/calculate")

Spring security Login does NOT propagate my form(POST) to Controller

On Home page (permitAll() )
I have a form (Action "/saveX" authenicated() )
I have a Controller with #PostMapping(value="/saveX")
User fills in form data & hits submit (POST)
Spring redirects to default login page
User enters username & password & hits login
User is now presented with Home page (logged-in features are now visible)
Was expecting Spring (successful)login to forward to my (#PostMapping)Controller
Now that User is logged-In
User fills in form data & hits submit (POST)
Controller with #PostMapping(value="/saveX") is invoked
Question
Shouldn't successful login result in continuing with the original action (ie my form POST)
NOTE - there will be many paths which could be the 1st reason for Authenticating the User --- so I won't have a default successfullogin link that can be used
I want to get these basics sorted out before I move on to implementing more sophisticated Authentication mechanisms - so will be replacing Spring default login later
Any help/hints greatly appreciated
You can do this by implementing AuthenticationSuccessHandler and need to extend SimpleUrlAuthenticationSuccessHandler which will give you options on redirection decisions.
public class RefererRedirectionAuthenticationSuccessHandler
extends SimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
public RefererRedirectionAuthenticationSuccessHandler() {
super();
setUseReferer(true);
}
}
In Spring Security configuration file, you need to register your AuthenticationSuccessHandler using successHandler() method.
#Configuration
#EnableWebSecurity
public class BasicConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
//put your logic here
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.successHandler(new RefererAuthenticationSuccessHandler());
}
}

why spring security makes spring mvc 's postmapping controllder do not work

when I config spring security like this
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Bean
public UserDetailsService userDetailsService(){
return new MyUserDetailsService();
}
#Bean
public MyAuthenticationProvider myAuthenticationProvider(){
MyAuthenticationProvider provider = new MyAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
and then I config my controller like this
#GetMapping("/login")
public String showLoginPage(){
System.out.println("GetMapping");
return "login";
}
#PostMapping("/login")
public void authUser(#RequestParam String username,#RequestParam String password){
// just for testing
System.out.println("PostMapping");
}
and then I visit my login page and enter my username and password, but the console doesn't print "PostMapping", which means the program doesn't go into my method "authUser" with #PostMapping.
Though my program runs successfully, but it makes me quite confuse.I suppose spring security doing some work automatically, but now I have no idea where to add my Authentications to the SecurityContextHolder.
I hope somebody can help and thanks very much
It has done by UsernamePasswordAuthenticationFilter, and the default processing path is Post /login, and the Authentication already exist in SecurityContextHolder, you can get it in controller.
If you want to disable form login, change to this.
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated().and()
.formLogin().disable();
Normally, POST mappings are filtered by CSRFfilters. Although it is not recommended in the production environment, you can disable CSRF filter simply using for learning cases:
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and().logout()
.and().csrf().disable();

When to use Spring Security`s antMatcher()?

When do we use antMatcher() vs antMatchers()?
For example:
http
.antMatcher("/high_level_url_A/**")
.authorizeRequests()
.antMatchers("/high_level_url_A/sub_level_1").hasRole('USER')
.antMatchers("/high_level_url_A/sub_level_2").hasRole('USER2')
.somethingElse()
.anyRequest().authenticated()
.and()
.antMatcher("/high_level_url_B/**")
.authorizeRequests()
.antMatchers("/high_level_url_B/sub_level_1").permitAll()
.antMatchers("/high_level_url_B/sub_level_2").hasRole('USER3')
.somethingElse()
.anyRequest().authenticated()
.and()
...
What I expect here is,
Any request matches to /high_level_url_A/** should be authenticated + /high_level_url_A/sub_level_1 only for USER and /high_level_url_A/sub_level_2 only for USER2
Any request matches to /high_level_url_B/** should be authenticated + /high_level_url_B/sub_level_1 for public access and /high_level_url_A/sub_level_2 only for USER3.
Any other pattern I don't care - But should be public ?
I have seen latest examples do not include antMatcher() these days. Why is that? Is antMatcher() no longer required?
You need antMatcher for multiple HttpSecurity, see Spring Security Reference:
5.7 Multiple HttpSecurity
We can configure multiple HttpSecurity instances just as we can have multiple <http> blocks. The key is to extend the WebSecurityConfigurationAdapter multiple times. For example, the following is an example of having a different configuration for URL’s that start with /api/.
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) { 1
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
#Configuration
#Order(1) 2
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**") 3
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration 4
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
1 Configure Authentication as normal
2 Create an instance of WebSecurityConfigurerAdapter that contains #Order to specify which WebSecurityConfigurerAdapter should be considered first.
3 The http.antMatcher states that this HttpSecurity will only be applicable to URLs that start with /api/
4 Create another instance of WebSecurityConfigurerAdapter. If the URL does not start with /api/ this configuration will be used. This configuration is considered after ApiWebSecurityConfigurationAdapter since it has an #Order value after 1 (no #Order defaults to last).
In your case you need no antMatcher, because you have only one configuration. Your modified code:
http
.authorizeRequests()
.antMatchers("/high_level_url_A/sub_level_1").hasRole('USER')
.antMatchers("/high_level_url_A/sub_level_2").hasRole('USER2')
.somethingElse() // for /high_level_url_A/**
.antMatchers("/high_level_url_A/**").authenticated()
.antMatchers("/high_level_url_B/sub_level_1").permitAll()
.antMatchers("/high_level_url_B/sub_level_2").hasRole('USER3')
.somethingElse() // for /high_level_url_B/**
.antMatchers("/high_level_url_B/**").authenticated()
.anyRequest().permitAll()
I'm updating my answer...
antMatcher() is a method of HttpSecurity, it doesn't have anything to do with authorizeRequests(). Basically, http.antMatcher() tells Spring to only configure HttpSecurity if the path matches this pattern.
The authorizeRequests().antMatchers() is then used to apply authorization to one or more paths you specify in antMatchers(). Such as permitAll() or hasRole('USER3'). These only get applied if the first http.antMatcher() is matched.

Spring Cloud Netflix : Passing host request parameter via RequestInterceptor to FeignClient

I am building a Spring Cloud project (Brixton.M4 with Spring Boot 1.3.1) with Eureka, Zuul and FeignClient where I am trying to add multi tenancy support (Tenants are identified by subdomain : tenant1.myservice.com). To do so, I would like to somehow pass the original subdomain along requests that are forwarded from a service to the other via Feign but I can't seem to be able to find the right way to do it.
What I have is a client that exposes a #RestController which calls a #FeignClient to communicate with my backend which exposes server operations to the client through its own #RestController.
The #FeignClient using same interface as my #RestController on the server :
#FeignClient(name = "product")
public interface ProductService extends IProductService {
}
What I am currently trying to do is set a header in a RequestInterceptor :
#Component
public class MultiTenancyRequestInterceptor implements RequestInterceptor {
private CurrentTenantProvider currentTenantProvider;
#Autowired
public MultiTenancyRequestInterceptor(CurrentTenantProvider currentTenantProvider) {
this.currentTenantProvider = currentTenantProvider;
}
#Override
public void apply(RequestTemplate template) {
try {
template.header("TENANT", currentTenantProvider.getTenant());
} catch (Exception e) {
// "oops"
}
}
}
My provider class is a simple component where I'm trying to inject a request / session scope bean :
#Component
public class CurrentTenantProvider {
#Autowired
private CurrentTenant currentTenant;
//...
}
The bean (I tried both session and request scope) :
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CurrentTenant currentTenant() {
return new CurrentTenant();
}
On the server, I use Hibernate multitenant provider that is supposed to catch the header value and use it to define which DB to connect to :
#Autowired
private HttpServletRequest httpRequest;
#Override
public String resolveCurrentTenantIdentifier() {
return httpRequest.getHeader("TENANT");
}
It seems the Feign call to the server is done in another thread and out of the incoming request scope, so i'm not sure how to pass that value along.
It all works fine when I hardcode the tenant value in the RequestInterceptor so I know the rest is working properly.
I have also looked at many other posts about Zuul "X-Forwaded-For" header and cannot find it in the request received on the server. I have also tried adding a ZuulFilter to pass host name to next request but what I see is that original request to the Client is picked up by the ZuulFilter and I can add but not when the Feign request is sent to the backend service even if I map it in zuul (i guess that is intended ?).
I am not really sure what's the next step and would appreciate some suggestions.
Hope that it's of any use for you but we're doing sth similar in Spring-Cloud-Sleuth but we're using a ThreadLocal to pass span between different libraries and approaches (including Feign + Hystrix).
Here is an example with the highlighted line where we retrieve the Span from the thread local: https://github.com/spring-cloud/spring-cloud-sleuth/blob/master/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceFeignClientAutoConfiguration.java#L123

Resources