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**");
}
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);
}
}
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()
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.
I just started learning Spring and Spring Security and I have created a simple project by reading Spring Security documentation. I done the following java based configuration.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin")
.password("nimda")
.roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin**").access("hasRole('ADMIN')")
.and().formLogin();
http.csrf().disable();
}
}
When I go for "/admin" it redirect me to the login page which I know spring generated with this default configuration and after login it will show the login page. Now my question is: login form is posted to "/login" and I did not defined any "AuthenticationManager" and "UserDetailService" which I read in documentation for custom configuration then how spring post the form and do the login process? Basically I want to know some detail of inner working of this default login process.
When you use the *ConfigurerAdapter classes, there is a lot of stuff that goes on during context load. Spring will check if you have defined an AuthenticationManager, if you have not it will create a default one.
If you are really interested in what happens during the magic configuration step, you will probably have to look at the source code. For instance if you look at WebSecurityConfigurerAdapter.getHttp() you can see it calls authenticationManager() in order to construct this bean.
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
In the old days you had to create all of the beans, yourself and wire them together, so we were more aware of how things fitted together. Now you either have to read the source, or copy from a guide, and hope you don't make any mistakes.
Debug tip: These days I look at the beans that exist after the context is loaded, and then I go back and set a breakpoint in the constructor of the AuthenticationManager implementation, then I can see the call-stack and how the initialisation work.
Here is the code you are looking for, instead of using the builder to build an in-memory user details source, you can implement your own custom AuthenticationProvider.
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String password = (String) authentication.getPrincipal();
String userName = (String) authentication.getCredentials();
if ("user".equals(userName) && "password".equals(password)) {
authentication = new UsernamePasswordAuthenticationToken(userName, password, Lists.newArrayList(new SimpleListProperty<GrantedAuthority>(null, "USER")));
return authentication;
}
throw new BadCredentialsException("Incorrect username or password.");
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
});
}
Be aware that you can create your own Authentication implementation in case you need to add additional information, or you can use the details property on that every Authentication can have.