SecurityConfig.java

package org.flasby.security;

import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.flasby.entity.ChristmasUser;
import org.flasby.entity.Users;
import org.flasby.entity.Authority;
import org.flasby.entity.repository.UserRepository;
import org.flasby.christmas.Config;
import org.flasby.security.IpAddressAuthProvider.BannedIpAddressException;
import org.flasby.security.servlet.ServletAuthenticationDetailsSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import lombok.extern.log4j.Log4j2;


@Log4j2
@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true, 
    jsr250Enabled = true,
    securedEnabled = true)
@EnableWebSecurity
@ComponentScan("org.flasby.entity")
public class SecurityConfig<TheUser extends Users<Authority>> extends WebSecurityConfigurerAdapter {

    @Autowired
    Environment env;

    @Autowired
    UserRepository<ChristmasUser> users;

    @Autowired
    Config config;

    protected UserDetailsService userDetailsService = new UserDetailsService() {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            final ChristmasUser user = users.findByName(username);
            log.warn("=========================>>    Looking for UserDetails of " + username);
            UserDetails ud = new UserDetails() {
                private static final long serialVersionUID = 1L;

                @Override
                public Collection<SimpleGrantedAuthority> getAuthorities() {
                    List<SimpleGrantedAuthority> auths = new ArrayList<>();
                    user.getRoles().forEach((a) -> auths.add(new SimpleGrantedAuthority(a.getAuthority())));
                    return auths;
                }

                @Override
                public String getPassword() {
                    return user.getPassword();
                }

                @Override
                public String getUsername() {
                    return user.getName();
                }

                @Override
                public boolean isAccountNonExpired() {
                    return true;
                }

                @Override
                public boolean isAccountNonLocked() {
                    return ! user.isDisabled();
                }

                @Override
                public boolean isCredentialsNonExpired() {
                    return true;
                }

                @Override
                public boolean isEnabled() {
                    return ! user.isDisabled();
                }

                @Override
                public String toString() {
                    return getUsername();
                }
            };
            return ud;
        }
    };

    @Bean
    LogoutSuccessHandler logoutSuccessHandler() {
        return new LogoutSuccessHandler() {

            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                                               Authentication authentication) throws IOException, ServletException {
                response.setStatus(HttpStatus.OK.value());
                response.sendRedirect(request.getContextPath() + "/loggedout.html");
            }
        };
    }

    @Bean
    AuthenticationSuccessHandler loginSuccessHandler() {
        return new AuthenticationSuccessHandler() {

            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                                                       Authentication authentication) throws IOException, ServletException {
                response.setStatus(HttpStatus.OK.value());
                response.sendRedirect(request.getContextPath() + "/");
            }
        };
    }

    // Called during init only
    @Bean
    AuthenticationFailureHandler loginFailureHandler() {
        ExceptionMappingAuthenticationFailureHandler handler = new ExceptionMappingAuthenticationFailureHandler();
        Map<String, String> mapping = new HashMap<>();
        mapping.put(DisabledException.class.getName(), "/userdisabled.html");
        mapping.put(UsernameNotFoundException.class.getName(), "/login.html?error"); // error flag for use by the Login page
        mapping.put(BannedIpAddressException.class.getName(), "/login.html?banned");
        handler.setExceptionMappings(mapping);
        return handler;
    }

    @Autowired
    IpAddressAuthProvider ipAddressAuthProvider;

    @Autowired
    MyAuthProvider myAuthProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(ipAddressAuthProvider);
        auth.authenticationProvider(myAuthProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // List things which don't need to be authenticated
                .antMatchers("/public/**", "/index", "/authenticate", "/webjars/**", "/webjars/**/**", "/xxx",
                        "/xxx/**", 
                        "/logout", "/loggedout.html", "/login.html", "/login.css", "/css/login.css", "/userdisabled.html", "login.html",
                        "/forgotPassword.html").permitAll()
                // Things which need an admin role 
                .antMatchers("/admin.html").hasAuthority(Authority.ADMIN.getAuthority())
                
                // and these things do require authentication before use
                .antMatchers("/topic/**", "/queue/**", "/socket", "/success").authenticated().anyRequest()
                .authenticated()
                .and()
                // Use a Form style login
                .formLogin().loginPage("/login.html") // The URL to be displayed with the login form
                .loginProcessingUrl("/login") // The URL to process the login request
                .authenticationDetailsSource( new ServletAuthenticationDetailsSource() )
                .permitAll()
                // These are the default names
                // .usernameParameter("username")
                // .passwordParameter("password")

                // On success to go root
                // .loginProcessingUrl("/")
                // .loginProcessingUrl("/authenticate")
                .successHandler(loginSuccessHandler())
                .failureHandler(loginFailureHandler())
                // .failureForwardUrl("/public/login.html?error=true")
                // .failureUrl("/login")
                .permitAll().and().logout().logoutSuccessHandler(logoutSuccessHandler()).and().rememberMe()
                .userDetailsService(userDetailsService).key("0b2f9fef-29e8-48ed-acf5-c074dba06b3b") // Generated by
                                                                                                    // https://www.uuidgenerator.net/
                .rememberMeParameter("remember-me") // it is name of checkbox at login page
                .rememberMeCookieName("remember-login") // it is name of the cookie
                .tokenValiditySeconds((int) Duration.ofDays(config.getLogin().getLifetimeindays()).getSeconds())
                .and()
                /**
                 * Applies to User Roles - not to login failures or unauthenticated access
                 * attempts.
                 */
                // .exceptionHandling().accessDeniedHandler(accessDeniedHandler()).and()
                // .authenticationProvider(authenticationProvider())
                ;

        /** Disabled for local testing */
        http.csrf().disable();

        /**
         * This is solely required to support H2 console viewing in Spring MVC with
         * Spring Security
         */
        // http.headers().frameOptions().disable();
    }
}