Can't logout by custom logout handler - spring-boot

In my spring boot app I want to add link to logout.
In template welcome.html:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title th:text="${appName}">Template title</title>
<body>
<h1>Welcome to <span th:text="${appName}">Our App</span></h1>
<h2>Dictionaries:</h2>
<p>Categories</p>
<p>Users</p>
<p>Logout</p>
</body>
</html>
In my SecurityConfiguration
import org.springframework.beans.factory.annotation.Autowired;
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.password.NoOpPasswordEncoder;
import org.springframework.security.web.DefaultRedirectStrategy;
import ru.otus.software_architect.eshop.handler.CustomLogoutSuccessHandler;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource; // get by Spring
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// Here, you are making the public directory on the classpath root available without authentication (e..g. for css files)
.antMatchers("/public/**", "/registration.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.successHandler((request, response, authentication) -> new DefaultRedirectStrategy().sendRedirect(request, response, "/welcome"))
.failureUrl("/login-error.html")
.permitAll()
.and()
.logout()
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.permitAll();
}
// login by user from db
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.usersByUsernameQuery("SELECT username, password, active FROM usr WHERE username=?")
.authoritiesByUsernameQuery("SELECT u.username, ur.role FROM usr u INNER JOIN user_roles ur ON u.id = ur.user_id WHERE u.username=?");
}
My handler:
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler implements LogoutSuccessHandler {
private static Logger logger = LogManager.getLogger(CustomLogoutSuccessHandler.class);
#Override
public void onLogoutSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
logger.info("onLogoutSuccess:");
// some custom logic here
response.sendRedirect(request.getContextPath() + "/login.html");
}
}
as you can see it just forward to login page.
But when I click on welcome.html on logout link the method onLogoutSuccess is not call and I get error page:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sun Jan 26 18:21:53 EET 2020
There was an unexpected error (type=Not Found, status=404).
No message available

Logout will redirect you to "/logout" performing a GET request.
Unless you have a page rendered at "/logout", you will receive a 404.
By default logout is only triggered with a POST request to "/logout".
Since you are using Thymeleaf, you could instead use something like this for logging out.
<form action="#" th:action="#{/logout}" method="post">
<input type="submit" value="Logout" />
</form>

Related

display spring security authentication object when SessionCreationPolicy.STATELESS

#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
MyUserDetailsService myUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index").permitAll();
http.authorizeRequests().antMatchers("/main").permitAll();
http.formLogin().loginPage("/login").permitAll().successHandler(successHandler());
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // Session is STATELESS
}
I set spring security sessionpolicy to STATLESS
because I'm using JWT so that STATLESS would be better
but STATELESS cause one problem
it's impossible to dispaly authentication object in thymeleaf
<h1>[[${#authentication }]]</h1>
if I changed session policy I could display authentication object
but but that's not what i want
in short
can i use authentication object with thymeleaf when spring's session policy is STATELESS
Form based log in requires a session, so marking as stateless would mean that the user is not available. Likely you can see the page because it is marked as permitAll which means no user is necessary to see it.
To fix this, you can switch to a form of authentication that is stateless too (i.e. it is included in every request). For example:
// #formatter:off
http
.authorizeRequests()
.mvcMatchers("/index", "/main").permitAll()
.and()
.httpBasic()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// #formatter:on
I'm also not sure about the syntax the themleaf template is using. For me, I use something like this:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Test</title>
</head>
<body>
<h1 th:text="${authentication?.name}"></h1>
</body>
</html>
Then I use the following to expose the Authentication as a model attribute:
#Controller
public class IndexController {
#GetMapping("/")
String index() {
return "index";
}
#ModelAttribute
Authentication authentication(Authentication authentication) {
return authentication;
}
}
I have a test that validates it works
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class DemoApplicationTests {
#Autowired
TestRestTemplate rest;
#Test
void indexWhenAnonymous() throws Exception{
ResponseEntity<String> result = rest.exchange(RequestEntity.get(URI.create("/")).build(), String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getBody()).doesNotContain("user");
}
#Test
void indexWhenAuthenticated() throws Exception{
ResponseEntity<String> result = rest.exchange(RequestEntity.get(URI.create("/")).headers(h -> h.setBasicAuth("user", "password")).build(), String.class);
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getBody()).contains("user");
}
}
You can find the complete sample at https://github.com/rwinch/spring-security-sample/tree/display-auth-stateless-thymeleaf which allows log in with the username user and password password.

Spring security always returning 403

Can someone tell me why this code is always returning 403?
I mapped /login to trigger the security login but it is not working properly.
package esercizio.security;
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;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("q#q").password("pluto").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").anonymous()
.antMatchers("/auth/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp")
.defaultSuccessUrl("/auth/list-student")
.failureUrl("/errorPage")
.and()
.logout().logoutSuccessUrl("/login.jsp");
}
}
It should let anyone in if the URL doesn't start with /auth, I don't know why it doesn't happen.
I think, you must prepend 'ROLE_' to you authority like ROLE_USER.
For more visit :
Spring Security always return the 403 accessDeniedPage after login

Add CSS file to Spring Boot + Spring Security Thymeleaf file

I wanted to add CSS file to my HTML file.
The problem appeared when I tried to add CSS to Spring Security application (I work on basic Spring Getting Started Content). I blame Spring Security because without it the CSS file loads properly.
Application.java file:
package mainpack;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Throwable {
SpringApplication.run(Application.class, args);
}
}
MvcConfig.java file:
package mainpack;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/register").setViewName("register");
registry.addViewController("/whatever").setViewName("whatever");
}
}
WebSecurityConfig.java file:
package mainpack;
import org.springframework.beans.factory.annotation.Autowired;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/index", "/register", "../static/css", "../static/images").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
I load CSS with line:
<link href="../static/css/index.css" th:href="#{/css/index.css}" rel="stylesheet" />
in index.html file.
Your pattern ../static/css is not matching your relative URL ../static/css/index.css, see AntPathMatcher:
PathMatcher implementation for Ant-style path patterns.
Part of this mapping code has been kindly borrowed from Apache Ant.
The mapping matches URLs using the following rules:
? matches one character
* matches zero or more characters
** matches zero or more directories in a path
{spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"
and Spring Boot Reference:
By default, resources are mapped on /** but you can tune that via spring.mvc.static-path-pattern.
Your request will be redirected to login form, because your are not logged in and all other requests need authentication.
To fix it, change your pattern to /css/** and /images/**.
A better solution for static resources is WebSecurity#ignoring:
Allows adding RequestMatcher instances that Spring Security should ignore. Web Security provided by Spring Security (including the SecurityContext) will not be available on HttpServletRequest that match. Typically the requests that are registered should be that of only static resources. For requests that are dynamic, consider mapping the request to allow all users instead.
Example Usage:
webSecurityBuilder.ignoring()
// ignore all URLs that start with /resources/ or /static/
.antMatchers("/resources/**", "/static/**");
The web.ignore()worked the best for me. just add the following method to your WebSecurityConfig class.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/img/**", "/icon/**");
}
.antMatchers("/**/*.js", "/**/*.css").permitAll();
This allows all the js and css files present in resources/static folder to be permitted for request access.

Spring Security blocking Rest Controller

My app has Spring boot 1.3.2 and I'm trying use Spring MVC with Spring Security.
I have administration panel under http://localhost:8080/admin and my page content for common users under http://localhost:8080/
If You are trying to open an admin panel (http://localhost:8080/admin) You have to log in, if You are common just enter http://localhost:8080/ and have fun no log in required.
My Security config class:
import org.springframework.beans.factory.annotation.Autowired;
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;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login");
}
}
Config above let me to require login from /admin
But I have some problem with Admin panel features.
This is Controller I'm trying to request with POST from admin panel:
#RestController
#RequestMapping("/admin/v1")
public class AdminController {
#RequestMapping(value = "/logout", method = RequestMethod.POST)
public String logout(HttpServletRequest request, HttpServletResponse response) {
String hello = "hi!";
return hello;
}
}
So I can log in, browser render Admin panel for me but when I'm clicking logout button which request POST logout method from Controller above. App tells me 403 Forbidden
Can anybody tell me what I'm doing wrong?
Most probably the 403 Forbidden error is because the spring by default enable csrf protection.
You can disable csrf in configuration or Include the CSRF Token in the POST method.
Disable csrf in config:
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/admin/v1/logout");
Include the CSRF Token in Form Submissions:
<c:url var="logoutUrl" value="/admin/v1/logout"/>
<form action="${logoutUrl}" method="post">
<input type="submit" value="Log out" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>

Return to the previous page after authorization, Spring Security AuthenticationSuccessHundler

I have a login page (/page/login) and dropdown login forms in every page. I want user to be redirected to the page from which he has logged in (by dropdown login form), or the home page if it was from login page.
I tried to use AuthenticationSuccessHandler but it does not seems to work, every time it just redirects user to home page. What is the right way to solve it?
#Component
public class MySimpleUrlAuthenticationSuccessHendler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException {
if(httpServletRequest.getContextPath().equals("/login")){
sendRedirect(httpServletRequest, httpServletResponse, "/user/profile");
}
else{
sendRedirect(httpServletRequest, httpServletResponse,httpServletRequest.getContextPath());
}
}
private void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
if(!response.isCommitted()){
new DefaultRedirectStrategy().sendRedirect(request,response,url);
}
}
}
Spring security config
package com.example.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
#Configuration
public class DemoSpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.regexMatchers(HttpMethod.GET, "rating/place/[0-9]{0,}", "/place/[0-9]{0,}/liked/", "/rating/place/[0-9]{0,}")
.hasRole("USER")
.antMatchers(HttpMethod.GET, "/user/orders",
"/user/places")
.hasRole("USER")
.regexMatchers(HttpMethod.POST, "/menu/[0-9]{0,}/comment",
"/place/[0-9]{0,}/menu/[0-9]{0,}")
.hasRole("USER")
.regexMatchers(HttpMethod.POST, "/place/menu/[0-9]{0,}")
.hasRole("OWNER")
.antMatchers(HttpMethod.GET, "/newplace")
.authenticated()
.antMatchers(HttpMethod.POST, "/newplace")
.authenticated()
.antMatchers(HttpMethod.POST, "/registration")
.permitAll()
.antMatchers(HttpMethod.GET, "/resend", "/page/login", "/registration", "/place/")
.permitAll();
http
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/");
http
.rememberMe()
.key("rememberme");
http
.formLogin()
.loginPage("/page/login")
.failureUrl("/page/login")
.loginProcessingUrl("/login")
.usernameParameter("j_username")
.passwordParameter("j_password")
.successHandler(authenticationSuccessHandler);
http.
userDetailsService(userDetailsService);
http.
csrf().disable();
}
}
You need something like this in your AuthenticationSuccessHandler.
I also had similar requirement in my project and I solved this using below step:-
When the login form in dropdown is submitted I also send the current url (window.location.href) as a hidden request parameter.
Inside UserNamePasswordFilter and I get this parameter from request and store it in session (say variable name is redirectPrevUrl).
Now, in authentication success handler if this variable is present (i.e. redirectPrevUrl!=null) I redirect to this url instead of default home page.
This worked for me and I hope it will work for you as well,

Resources