I am building a spring security app. Login and logout are working fine, but I would like to make it impossible for non-logged users to logout which is possible by default (strange...).
I tried to add a custom Controller for /logout (checking if user is authenticated) but nothing seems to be working. Is there a way to do it in spring configuration instead? Below is my code. The controller is not working. The default spring-security /logout view is visible even if user is not authenticated.
package com.example.demo.config;
import com.example.demo.security.CustomAccessDeniedHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll() // This will be your home screen URL
.antMatchers("/css/**").permitAll()
.antMatchers("/assets/css/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler())
;
}
}
-----------------
#RequestMapping("/logout")
public String logout() {
if (!(SecurityContextHolder
.getContext()
.getAuthentication()
.getPrincipal() instanceof MyUserPrincipal)) {
// System.out.println("Non logged user is trying to logout");
return "redirect:/";
} else {
// System.out.println("Logged user is trying to logout");
return "redirect:/logout";
}
}
Finally found the solution. It appears that the "Are You sure..." page that is showed after a user goes to /logout is generated by CSRF module. After turning of CSRF the page did not show again. That was not the solution though because csrf needs to be enabled for security. One line was missing:
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
. Now user is logged out after going to /logout with no questions asked. It is not my desired solution but it does the trick for now ;). Thank You all for your responses.
package com.example.demo.config;
import com.example.demo.security.CustomAccessDeniedHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll() // This will be your home screen URL
.antMatchers("/css/**").permitAll()
.antMatchers("/assets/css/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler())
;
}
}
You need to handle the permissions for the logout URL via the WebSecurityConfig class in the configure method which you are already doing for some URLs so change to something like:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").permitAll() // This will be your home screen URL
.antMatchers("/css/**").permitAll()
.antMatchers("/assets/css/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logoutSuccessful")
.permitAll()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler())
;
}
notice the addition of
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logoutSuccessful")
.permitAll()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
This is our logout handling code, so if users hit /logout they will be logged out and forwarded to the /logoutSuccessful (I would usually redirect to login page here).
You can also add a selection of other methods here for configuring your logout as well. The logout is just as configurable as the login, so you can add logout handlers as you see fit.
Related
Could you help me with setup Hilla + Spring Security (LDAP)?
I have created demo project from https://hilla.dev/docs/getting-started
npx #vaadin/cli init --hilla --auth hilla-with-auth
This project has simple auth, but i would like LDAP auth.
Like in my another application without Hilla:
#Configuration
#EnableAutoConfiguration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig extends VaadinWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/logout/**", "/logout-success", "/login/**", "/static/**", "/**.png").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/grocery", true)
.failureUrl("/login?error=true")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout=true")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.httpBasic();
http.addFilterAfter(new CsrfLoggerFilter(), CsrfFilter.class);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authBuilder) throws Exception {
authBuilder
.ldapAuthentication()
.userSearchFilter(new ParseConfigFile().getConf("AuthenticationManagerBuilder.userSearchFilter"))
.userSearchBase(new ParseConfigFile().getConf("AuthenticationManagerBuilder.userSearchBase"))
.groupSearchBase(new ParseConfigFile().getConf("AuthenticationManagerBuilder.groupSearchBase"))
.groupSearchFilter(new ParseConfigFile().getConf("AuthenticationManagerBuilder.groupSearchFilter"))
.contextSource()
.url(new ParseConfigFile().getConf("AuthenticationManagerBuilder.url"))
.managerDn(new ParseConfigFile().getConf("AuthenticationManagerBuilder.managerDn"))
.managerPassword(new ParseConfigFile().getConf("AuthenticationManagerBuilder.managerPassword"));
}
}
What I must change in config file for get LDAP auth?
LDAP uses a different protocol for communication. So you must have an LDAP server running first and foremost, and then use Spring Security to authenticate using what Spring Security offers.
The spring boot doc have an config file similar to what you may be looking for :Ldap Auth example
I am following the documentation from Spring.io to setup user authentication using Spring Boot.
Here are the files in question:
MvcConfig.java
package com.*********.*******;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
WebSecurityConfig.java
package com.*********.*******;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
#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);
}
}
How can I eliminate the home.html file and wire it so that localhost:808 takes the user directly to the login which, if successful, brings them to the hello.html file?
If I change the default controller to the following code, then it just enters a loop of the login page because the login takes you to the previous page you were trying to access. How can I instead have it send the user to hello.html?
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("login");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
I apologize if this post is worded poorly. If there is anything I can add for clarity, please don't hesitate to ask and I will make any corrections.
Remove the line .antMatchers("/", "/home").permitAll() so all urls are protected and redirected to login.
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
I have struggling to configure security for some different paths I have.
I would like this structure:
/actuator/health <-- open
/api/** <-- hasAnyAuthority
/auth/** <-- basic authentication
all others no access
So far this is what I have
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**") // If you want to override the security provided by Spring Boot.
.addFilter(preAuthFilter())
.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/actuator/health").permitAll()
.antMatchers("/api/**").hasAnyAuthority("something")
.antMatchers("/auth/**").authenticated()
.and()
.httpBasic();
}
I would like to add .anyRequest().denyAll() but that doesn't seem to be possible after httpBasic().
Can anyone confirm that the above code will be the same as what I would like?
Example on how to split configuration by path:
#Configuration
public class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/api/public/**", "/api/register").anonymous() //if you need to allow some path in api
.antMatchers("/api/**", "/api/register/**").hasRole("API_USER")
.and()
.formLogin()
.loginPage("/api/")
.failureHandler(failureHandler())
.loginProcessingUrl("/api/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(successHandler())
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessUrl("/api/")
.invalidateHttpSession(true)
.and()
.rememberMe()
.key("something")
.and()
.csrf().disable()
.exceptionHandling()
.accessDeniedPage("/api/loginfailed");
}
}
Second path:
#Configuration
public class AuthSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/auth/**")
.authorizeRequests()
.antMatchers("/auth/register").anonymous()
.antMatchers("/auth/**", "/auth/register/**").hasRole("USER")
.and()
.formLogin()
.loginPage("/auth/")
.failureHandler(failureHandler())
.loginProcessingUrl("/auth/login")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(successHandler())
.and()
.logout()
.logoutUrl("/auth/logout")
.logoutSuccessUrl("/auth/")
.invalidateHttpSession(true)
.and()
.rememberMe()
.key("something")
.and()
.csrf().disable()
.exceptionHandling()
.accessDeniedPage("/auth/loginfailed");
}
}
Now since you have not added security for /actuator/health you can either leave it without one or you can make another adapter for it and permit access to everyone.
Also you should use csrf protection, it is easy to implement.
I have a fundamental problem with spring boot application. The start webpage is http://127.0.0.1:8080/login and after log in user is redirect to http://127.0.0.1:8080/MainMenu. Unfortunatelly it is also possible to write in url bar http://127.0.0.1:8080/MainMenu and going there without authentication.
What is the main idea to block that action?
#edit
This is my configuration:
package local.vlex.security;
import org.springframework.context.annotation.Configuration;
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;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "src/main/resources/static/assets/css/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
and Controller:
#GetMapping("/MainMenu")
public ModelAndView correctCredentials(){
ModelAndView mav = new ModelAndView();
mav.setViewName("MainMenu");
return mav;
}
Sum up:
I would like to block going to other sites without authentication - if someone is not logged in then it should go 404 on everything other than /login
if someone is not logged in then it should go 404 on everything other
than /login
This is a bit pecular requirement. What is usually done is that people get redirected to login page on all other urls than login, or get 401 Unauthorized or 403 Forbidden. However, it's not hard to do this one either.
You mentioned you need your static resources (css, js, images) in the assets directory as well, so given that, this should work:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Configuration
#EnableWebSecurity
public class YourSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.NOT_FOUND))
;
}
}
Using
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
and
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
in Maven pom.xml
Note that there is also accessDeniedHandler() in exceptionHandling(), but that seems to only fire when user is already authenticated but denied access, so you'd need authenticationentrypoint for your use case.
Addition:
Sometimes having a custom authentication entrypoint messes the existing entry point handler. That is the case at least if you use default spring-generated form login. One way to go around that is to specify your entrypoint to only match urls that are not matching your authentication handler url, like this:
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
// snip...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.NOT_FOUND),
new NegatedRequestMatcher(new AntPathRequestMatcher("/login")) )
.and()
.formLogin()
;
}
Add the following code, It will block all the request except "/" & "/login"
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(
"/css/**",
"/js/**",
"/lib/**",
"/video/**",
"/images/**"
);
}
}
And add WebConfig
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(31556926)
.resourceChain(true);
}
}
Don't forget to add the security dependency in your pom.xml file.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Provided that you have spring-security dependencies on classpath, this is a working example from my code.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/login**")
.permitAll()
.anyRequest()
.authenticated();
This blocks me from any other requests than '/' or 'login' or 'loginPage' etc. with Authorization.
Follow that with ignoring the static ones, css, js etc.,
If this still doesn't work, for now try working on blocking the page alone:
http.authorizeRequests()
.antMatchers("/MainMenu")
.authenticated();
The above would give idea what's happening. Dependency issue or something like that.
I have the following Spring security configuration class for two separate security realms, the admin area and the frontend area:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsServiceImpl userDetailsService;
#Configuration
#Order(1)
public static class AdminAreaConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthSuccessAdmin authSuccessAdmin;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new AntPathRequestMatcher("/admin/**"))
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/login/login.html").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/admin/login.html")
.permitAll()
.successHandler(authSuccessAdmin)
.and()
.logout()
.permitAll();
}
}
#Configuration
#Order(2)
public static class UserAreaConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private AuthSuccessFrontend authSuccessFrontend;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new AntPathRequestMatcher("/**"))
.csrf().disable()
.authorizeRequests()
.antMatchers("/about", "/register").permitAll()
.antMatchers("/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(authSuccessFrontend)
.and()
.logout()
.permitAll();
}
}
}
When the app is started, the authentication success handler of the admin area is overwritten by the authentication handler of the frontend area, which is loaded after the first. This results in a wrong redirect when logging into the admin area (redirects to url defined in the frontend auth success handler). How can I assign disctinct handlers to the separate configurations?
The issue seems to be in RequestMatcher pattern.
Your USER app has the RequestMatcher pattern '/**'(means anything after / which will include path /admin as well) which will override your ADMIN RequestMatcher pattern /admin/**
Change the user RequestMatcher to /user/**