This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 3 years ago.
I have followed the instructions below to create two different http security blocks for admin and user.
docs.spring.io/spring-security-multiple-httpsecurity
As document says , if the URL does not start with /aaa, another configuration will be used to pattern.
But when i put #Order(1) at admin block ,admin page works fine , user page will not redirect to login page /login/user
while i put #Order(1) at user block , user page works fine , admin page will not redirect to login page /login/admin either.
here is my java code
#EnableWebSecurity
public class MultiHttpSecurityConfig {
/**
* intercept user url
*/
#Configuration
#Order(1)
public static class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationSuccessHandler successHandler;
#Autowired
CustomAuthenticationFailureHandler failureHandler;
#Autowired
private CustomAuthenticationProvider customAuthProvider;
#Autowired
private CustomUserDetailsService userDetailsService;
#Value("${my.cookie.timeout}")
private int cookieTimeOut;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/css/**", "/js/**", "/images/**, /fonts/**").permitAll()
.antMatchers("/bbb/**","/aaaa/**").hasAnyRole("USER");
http.formLogin()
.successHandler(successHandler)
.failureHandler(failureHandler)
.loginPage("/login/user").permitAll();
http.logout().permitAll();
http.rememberMe().key("uniqueAndSecret").tokenValiditySeconds(cookieTimeOut);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthProvider);
auth.userDetailsService(userDetailsService);
}
}
/**
* intercept admin url
*/
#Configuration
public static class AdminWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationSuccessHandler successHandler;
#Autowired
CustomAuthenticationFailureHandler failureHandler;
#Value("${my.cookie.timeout}")
private int cookieTimeOut;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/css/**", "/js/**", "/images/**, /fonts/**").permitAll()
.antMatchers("/ccc/**","/dddd").hasAnyRole("ADMIN");
http.formLogin()
.successHandler(successHandler)
.failureHandler(failureHandler)
.loginPage("/login/admin").permitAll();
http.logout().permitAll();
http.rememberMe().key("uniqueAndSecret").tokenValiditySeconds(cookieTimeOut);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("test").password("test").roles("ADMIN");
}
}
}
update:
As dur says below,the key reason is authorizeRequests() method matches all urls in Order(1) , so i need to add antMatcher("/bbb/*")** at first before authorizeRequests().
But antMatcher() only matches just only one kind of url , if i have one more kinds of urls to match like "/bbb/" , "/aaa/*" , how to achieve this ?
Then i need to add one more WebSecurityConfigurerAdapter configuration ?
Is there any better way to do this to reduce code ?
I have found solution in spring-security SDK requestMatchers() method, it provides a example above the requestMatchers() method.
Here is my code below for match user's urls at Order(1)
http.csrf().disable();
http.requestMatchers()
.antMatchers("/bbb/**", "/aaa/**")
.and()
.authorizeRequests()
.antMatchers("/**").hasAnyRole("USER");
http.formLogin()
.successHandler(successHandler)
.failureHandler(failureHandler)
.loginPage("/login/user").permitAll();
http.logout().permitAll();
Then both bbb and aaa have been matched and don't need to create another configuration
But another problem occurs , it will show "405 method not allowed" when post username and password to login/user interface at user login page , while admin page works fine
I have searched google ,it tells to disable csrf , but i have already disable csrf...
In one of my project I did not use form login, but implemented a custom AccessDeniedHandler and AuthenticationEntryPoint that can redirect to different login pages with some custom logic I needed. However, loginPage() is also an AuthenticationEntryPoint in the end.
They can be added via:
.exceptionHandling().authenticationEntryPoint(new YourCustomAuthEntryHandler()).and()
.exceptionHandling().accessDeniedHandler(new YourCustomAccessDeniedHandler())
It's just an idea, maybe it worth checking them.
Moreover, I think you will need authenticate all requests, add this line for both configs in the authorizeRequests() block:
.anyRequest().authenticated()
Related
I would like to configure web security layer based on my request type.
If the request starts with /rest then it should use Basic authentication with stateless session management and for login authentication then it should use CSRF with stateful session management.
I have tried below code.
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/rest/**").hasRole("SUPER_ADMIN")
.anyRequest().fullyAuthenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin().and().logout().permitAll();
}
It works with basic authentication but it doesn't work for the login request because the session is not stateful. Can anyone please help me to configure Spring security. I am new to Spring security.
You need
1. Rest API's to be authenticated by basic authentication
2. Your web application be authenticated by form login.
And authorization is other part in both cases that you can set it as per your requirement.
Let me explain what was wrong with your approach. By your approach you can achieve only one authentication entry point from one configuration. i.e, you can't achieve multiple authentication entry point.
Now coming to your first requirement of achieving multiple authentication entry point.
1. For Rest API resources -- authentication by HttpBasicAuthentication for antMatcher /rest/**
2. For WebApp resources -- authentication by Form Login for antMatcher other than /rest/**
To achieve this
1. You need to have implementation of WebSecurityConfigurerAdapter of different configuration order and different antMatcher patterns.
2. Order of each configuration is important.
- wildcard pattern(/**) should be placed last order
- non wildcard pattern or restricted pattern(/rest/**) should be placed first order
3. As those configuration classes are static and inner classes for a class which is annotated #EnableWebSecurity you should be careful while defining bean using #bean and autowiring using #Autowired.
Note:
Most of people makes mistake by not defining antmather for authorizeRequest()
If first configuration #Order(1) class is configured as below
http.authorizeRequests()
2nd configuration will become dead configuration because
http.authorizeRequests() => http.antMatcher("/**").authorizeRequests()
And all URL's will be configured only for first configuration only.
Refer code given below for better understanding.
#Configuration
#EnableWebSecurity
public class SpringSecurityConfiguration
{
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Configuration
#Order(1)
public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("superadmin")
.password(passwordEncoder.encode("superadmin#123#"))
.roles("SUPER_ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.antMatcher("/rest/**")
.authorizeRequests()
.antMatchers("/rest/**").hasRole("SUPER_ADMIN")
.and().httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
#Configuration
#Order(2)
public static class LoginFormSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder.encode("user#123#"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/**") //wild card i.e, allow all (But already /rest/** is filtered by 1st config)
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/**").authenticated()
.and().formLogin()
.defaultSuccessUrl("/app/user/dashboard")
.and().exceptionHandling()
.accessDeniedPage("/403")
.and().logout()
.invalidateHttpSession(true);
http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired");
}
}
}
This question has requirement of different sets of URL's(/rest/** and other than /rest/**) for different authentication filters. Here user's (for both basic auth and form login) may be authenticated against a single table (say user_details) or multiple tables (say api_users and web_users)
If you have requirement like there is no different set of URL's but two sets of users say customer and employees(staff) both are accessing same application but they needs to be authenticated against different tables(say users and customer table) in that case refer my another answer Spring Security user authentication against customers and employee table
You have to allow users to access login page without authentication and same you can do with static pages. See below configuration.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
I am using spring boot 2.1.4 with dependencies of actuator. I wanted to configure separate authentication and authorization mechanisms for actuator and my application. I read the Multiple HttpSecurity and configured my WebSecurityAdapter as follows:
#Configuration
public class ProvisioningServiceSecurityConfiguration {
#Value("${actuator.user.name}")
private String actuatorUserName;
#Value("${actuator.password}")
private String actuatorPassword;
#Value("${actuator.role}")
private String actuatorRole;
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("ADMIN").build());
manager.createUser(
users.username(actuatorUserName).password(actuatorPassword).roles(actuatorRole).build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/locations/**")
.antMatcher("/organizations/**")
.antMatcher("/productTypes/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/manage/**")
.authorizeRequests()
.anyRequest().hasRole("ACTUATOR_ADMIN")
.and()
.httpBasic();
}
}
/*#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}*/
}
Note: I have disabled form Login temporarily
When I run a curl request
curl -XGET http://localhost:9797/provisioningService/organizations/all
I am able to see the output. Its as though the spring security never existed. When I enable form login, I get the spring login screen. The other behavior that I observed is if I interchange the username and password of /locations with the actuator username and password, I still get a valid response back.
I understand the form login is more of a fallback but I want to disable the form login (probably we may move to cas) and use authentication and authorization only based on the spring security httpBasic. I am not able to understand the mistake I am making.
My requirement is finally :
1) a request to /organizations or /locations etc should be accessible only if the username password is "user" and "password"
2) a request to /manage which is the actuator api should be accessible only if the username and password and role matches with the actuator username and password.
3) Any other API can be permitAll / form login
How do i go about achieving this?
1) Spring Security has a function to control access by filtering by Authorities(after Authentication), but there is no function to filter by the information required for login. You need business logic to verify that you are attempting to log in with the corresponding ID and password during login.
2) As mentioned above, access control with ID and password is not provided.
I recommend creating Authorities for only the two accounts you requested.
3) .antMatcher("/form").permitAll()
I configure WebSecurityConfig, create user in memory
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("username")
.password(passwordEncoder().encode("password"))
.authorities("READ_ORDERS")
.roles("USER");
}
configure WebSecurityConfig
#Configuration
#Order(1)
public static class BasicAuthenticationAdapter extends WebSecurityConfigurerAdapter {
private final AuthenticationEntryPoint authEntryPoint;
#Autowired
public BasicAuthenticationAdapter(AuthenticationEntryPoint authEntryPoint) {
this.authEntryPoint = authEntryPoint;
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/orders**")
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.csrf().disable()
.httpBasic().authenticationEntryPoint(authEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
When i try to Authorise with invalid credentials first time - 401 exception, it's ok.
But after successful authorization, when i use invalid username and password,
i also authorised.
What can be the problem ?
That is how basic authentication works. As soon as you have logged in successfully the valid credentials will always be posted.
Spring security works with SessionCreationPolicy, and default policy is IF_REQUIRED. It means spring creates session if it does not have and is required.
In order to solve your issue you have to change this policy.
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
Restart your server and try again.
EDIT:
I further drilled down the problem and turns out issue persists even with single configuration. If I use single configuration and keep
http.antMatcher("/api/test/**")
urls don't get secured.
Removing the antMatcher and antMatchers immediately secures the url.
i.e if I use:
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
then only spring security is securing url. Why isn't antMatcher functioning?
(Updated the title to include actual issue.)
Original Post:
I have referred following stackoverflow questions:
Spring REST security - Secure different URLs differently
Using multiple WebSecurityConfigurerAdapter with different AuthenticationProviders (basic auth for API and LDAP for web app)
and spring security doc:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#multiple-httpsecurity
But I am not able to configure multiple http security elements.
When I follow the official spring doc, it works in my case only becuase of the fact that the second http security element is a catch-all, but as soon as I add a specific url, all the urls can be accessed without any authentication.
Here's my code:
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
Now any url can be accessed. If I remove antMatcher from second configuration, all the urls become secured.
The pattern must not contain the context path, see AntPathRequestMatcher:
Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest.
and HttpServletRequest.html#getServletPath:
Returns the part of this request's URL that calls the servlet. This path starts with a "/" character and includes either the servlet name or a path to the servlet, but does not include any extra path information or a query string. Same as the value of the CGI variable SCRIPT_NAME.
and HttpServletRequest.html#getContextPath:
Returns the portion of the request URI that indicates the context of the request. The context path always comes first in a request URI. The path starts with a "/" character but does not end with a "/" character. For servlets in the default (root) context, this method returns "". The container does not decode this string.
Your modified and simplified code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/test/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
I am writing an application using Spring Security.
I've implemented my custom UserDetails, UserDetailsService, AccessDecisionVoter and WebSecurityConfigurerAdapter.
I want to permit unauthorized users to access the /authenthication/login page to log in, but every other access to a page needs to be handled by the custom AccessDecisionVoter.
My custom WebSecurityConfigurerAdapter class looks like this:
#Configuration
#EnableWebSecurity
#ComponentScan(value = "security")
public class Configuration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsServiceImpl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/authentication/login**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.accessDecisionManager(accessDecisionManager())
;
}
#Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters =
Arrays.asList(new AccessDecisionVoterImpl());
return new UnanimousBased(decisionVoters);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
try {
auth.userDetailsService(userDetailsServiceImpl);
} catch (Exception e) {
e.printStackTrace();
}
}
#Bean
public UserDetailsServiceImpl userDetailsService() {
return userDetailsServiceImpl;
}
}
I have defined several roles in my database. The vote method in my custom AccessDecisionVoter retrieves the permissions of the User that is logged in and grants or denies access, based on that and the URL + httpMethod.
Problem:
However when I send a POST to the /authentication/login with the username and password my code gives me a NullPointerException in the custom AccessDecisionVoter: the username (retrieved via the authentication.getPrincipal(); returns anonymousUser, which results in the NullPointer later in the code. But I don't understand why the vote method gets called anyway since the configuration file told Spring to permittAll accesses to /authentication/login
I believe that the way you have it set up, permitAll just makes it so that all requests are allowed (even unauthenticated ones) but it doesn't disable the security chain for that request, it will still try to process through them all. If you just want to bypass all the security, the approach i usually use is to override the webSecurity config method and add the exception cases there, like so:
#Override
public void configure(WebSecurity webSecurity) throws Exception
{
webSecurity
.ignoring()
.antMatchers("/authentication/login**");
}