Asked  9 Months ago    Answers:  5   Viewed   215 times

My enhanced Pet Clinic application requires security.

Currently the logout functionality does not seem to work. I have a GET version (simple link) and a POST version (hidden form submitted by a link).

After login, whichever method I use to log out, once I try to log in again, the new login is not allowed.

I believe this is linked to this section:

.sessionManagement()
    .maximumSessions(1)
    .maxSessionsPreventsLogin(true)
    .expiredUrl("/login?expired")

but I thought that this section:

.logout()
    .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
    .logoutSuccessUrl("/")
    .permitAll()

would invalidate my HttpSession so that the next login would be allowed, but that is not happening.

When I look at the logs, these are the lines that are different when I log in the 2nd time:

s.CompositeSessionAuthenticationStrategy : Delegating to org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy@2cc9f3de
w.a.UsernamePasswordAuthenticationFilter : Authentication request failed: org.springframework.security.web.authentication.session.SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded
w.a.UsernamePasswordAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
w.a.UsernamePasswordAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@16c670c3

.a.SimpleUrlAuthenticationFailureHandler : Redirecting to /login?error

Any advice would be welcome.

My application can be found at https://github.com/arnaldop/enhanced-pet-clinic.

Here's code from my WebSecurityConfigurerAdapter subclass:

private static final String[] UNSECURED_RESOURCE_LIST =
    new String[] {"/", "/resources/**", "/assets/**", "/css/**", "/webjars/**",
        "/images/**", "/dandelion-assets/**", "/unauthorized", "/error*"};

@Override
public void configure(WebSecurity web) throws Exception {
    web
        .ignoring()
            .antMatchers(UNSECURED_RESOURCE_LIST);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    //@formatter:off
    http
        .authorizeRequests()
            .antMatchers(UNSECURED_RESOURCE_LIST)
                .permitAll()
            .antMatchers("/owners/**", "/vets/**", "/vets*").hasRole("USER")
            .antMatchers("/manage/**").hasRole("ADMIN")
            .anyRequest()
                .permitAll()
        .and()
            .formLogin()
                .loginPage("/login")
                    .failureUrl("/login?error")
                    .permitAll()
        .and()
            .logout()
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .logoutSuccessUrl("/")
                .permitAll()
        .and()
            .requiresChannel()
                .antMatchers("/login", "/owners/**", "/vets/**", "/vets*", "/manage/**")
                    .requiresSecure()
        .and()
            .exceptionHandling()
                .accessDeniedPage("/router?q=unauthorized")
        .and()
            .sessionManagement()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .expiredUrl("/login?expired")
        ;
    //@formatter:on
}

 Answers

3

I had also the same problem on spring boot which I fixed it by implementing HttpSessionEventPublisher

// Register HttpSessionEventPublisher
    @Bean
    public static ServletListenerRegistrationBean httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
    }
Monday, August 30, 2021
 
1

From your question,I see you are trying to create your own logout and you also trying to use the default spring logout.I advise you should choose one method only not mixed them both.There are two way I can show you to logout from spring:

First: Default spring security logout

.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout.done").deleteCookies("JSESSIONID")
.invalidateHttpSession(true) 

From example above, you should only need to call the /logout url whenever you want to logout the user. No need to create any @Controller to handle that logout instead spring will help to log the user out. You also can add other thing you want to invalidate here.

Second: Proggrammatically logout

@RequestMapping(value = {"/logout"}, method = RequestMethod.POST)
public String logoutDo(HttpServletRequest request,HttpServletResponse response){
HttpSession session= request.getSession(false);
    SecurityContextHolder.clearContext();
         session= request.getSession(false);
        if(session != null) {
            session.invalidate();
        }
        for(Cookie cookie : request.getCookies()) {
            cookie.setMaxAge(0);
        }

    return "logout";
}

If you are using this logout,you doesn't need to include the first method in spring security config. By using this method,you can add extra action to do before and after logout done.Btw, to use this logout ,just call the /logout url and user will be logout manually.This method will invalidate session,clear spring security context and cookies.

In addition for second method, if you are using RequestMethod.POST, you need to include the csrf key as a post. The alternative way is to create a form with hidden input csrf key. This is some example of auto generated logout link with jquery :

$("#Logout").click(function(){
    $form=$("<form>").attr({"action":"${pageContext.request.contextPath}"+"/logout","method":"post"})
    .append($("<input>").attr({"type":"hidden","name":"${_csrf.parameterName}","value":"${_csrf.token}"}))
    $("#Logout").append($form);
    $form.submit();
});

You just need to create hyperlink <a id="Logout">Logout</a> to use it.

If you are using RequestMethod.GET,just include a csrf key as a parameter in you link like this:

<a href="${pageContext.request.contextPath}/logout?${_csrf.parameterName}=${_csrf.token}">Logout</a>

Thats all,hope its help.

Wednesday, July 14, 2021
 
4

To get it working, if you are using Thymeleaf 3.0.2 with Spring Boot 1.4, you need to force version 3.0.1.RELEASE of thymeleaf-extras-springsecurity4 (because it inherits version 2.1.2 which does not work in combination with Thymeleaf 3):

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.1.RELEASE</version>
</dependency>

The tags should be using the hasRole function.

<div sec:authorize="hasRole('ROLE_ADMIN')">
Monday, August 23, 2021
 
A.S.H
 
1

You should fill in the content of role by yourself when creating your UserDetails:

public class SecurityUser implements UserDetails{
    String ROLE_PREFIX = "ROLE_";

    String userName;
    String password;
    String role;

    public SecurityUser(String username, String password, String role){
        this.userName = username;
        this.password = password;
        this.role = role;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();

        list.add(new SimpleGrantedAuthority(ROLE_PREFIX + role));

        return list;
    }

Basically, what you need to do is override method: getAuthorities, and fill in the content of your role field into the GrantedAuthority list.

Sunday, September 5, 2021
 
4

CORS native support is done by default at Spring MVC HandlerMapping level, so it is expected that your CAS filter will not be CORS enabled, since it handles request earlier.

One option to consider is using the org.springframework.web.filter.CorsFilter we also provide with Spring Framework 4.2 with the AddFilterBefore approach.

Be aware that CorsConfiguration has not the same default configuration than @CrossOrigin or CorsRegistry, so you need to define most of the properties yourself, for example:

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true); // you USUALLY want this
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("GET");
    config.addAllowedMethod("PUT");
    source.registerCorsConfiguration("/**", config);
    CorsFilter filter = new CorsFilter(source);
    // ...
Monday, October 18, 2021
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share