package cn.reghao.bnt.web.admin.security;

import cn.reghao.bnt.web.admin.security.filter.LoginRedirectFilter;
import cn.reghao.bnt.web.admin.security.form.AccountAuthFilter;
import cn.reghao.bnt.web.admin.security.form.AccountAuthProvider;
import cn.reghao.bnt.web.admin.security.session.MySessionAuthenticationStrategy;
import cn.reghao.bnt.web.admin.service.AccountLoginService;
import cn.reghao.bnt.web.admin.service.MenuService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.session.*;
import org.springframework.security.web.context.SecurityContextHolderFilter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Web 应用安全配置
 *
 * @author reghao
 * @date 2019-05-20 10:41:10
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    private final String loginPage = "/login";
    private final String loginApi = "/api/auth/signin";
    private final String logoutApi = "/api/auth/signout";

    private final AccountAuthProvider userAuthProvider;
    private final AccountLoginService accountLoginService;
    private final AuthenticationSuccessHandler successHandler;
    private final AuthenticationFailureHandler failureHandler;
    private final LogoutHandler logoutHandler;
    private final LogoutSuccessHandler logoutSuccessHandler;
    private final SessionRegistry sessionRegistry;
    private final MenuService menuService;

    public WebSecurityConfig(AccountAuthProvider userAuthProvider, AccountLoginService accountLoginService,
                             AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler,
                             LogoutHandler logoutHandler, LogoutSuccessHandler logoutSuccessHandler,
                             SessionRegistry sessionRegistry, MenuService menuService) {
        this.userAuthProvider = userAuthProvider;
        this.accountLoginService = accountLoginService;
        this.successHandler = successHandler;
        this.failureHandler = failureHandler;
        this.logoutHandler = logoutHandler;
        this.logoutSuccessHandler = logoutSuccessHandler;
        this.sessionRegistry = sessionRegistry;
        this.menuService = menuService;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
        http
                /*.authorizeHttpRequests(request -> request
                        .requestMatchers("/dist/**").permitAll()
                        .requestMatchers("/bg/dist/**").permitAll()
                        // 不需要认证的接口
                        .requestMatchers("/bg/api/actuator/**").permitAll()
                        .requestMatchers(loginApi).permitAll()
                        .requestMatchers(logoutApi).permitAll()
                        .requestMatchers("/bg/code/**").permitAll()
                        .requestMatchers("/bg/api/oss/**").permitAll()
                        // 需要认证的接口
                        .requestMatchers("/bg/**").authenticated()
                        // 不需要认证的接口
                        .anyRequest().permitAll())*/
                .authorizeHttpRequests(register -> register.anyRequest().access((authentication, object) -> {
                    String method = object.getRequest().getMethod();
                    if (method == null) {
                        method = "";
                    }
                    //获取当前请求的 URL 地址
                    String requestUrl = object.getRequest().getRequestURI();
                    if (requestUrl.startsWith("/dist/") || requestUrl.startsWith("/bg/dist/")) {
                        // 放行静态资源
                        return new AuthorizationDecision(true);
                    }

                    // 获取可访问 url 的角色集合
                    List<String> roles = menuService.getRolesByUrl(method, requestUrl);
                    if (roles.isEmpty()) {
                        return new AuthorizationDecision(true);
                    }

                    //获取当前登录用户的角色
                    Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();
                    for (GrantedAuthority authority : authorities) {
                        for (String role : roles) {
                            if (authority.getAuthority().equals(role)) {
                                //说明当前登录用户具备当前请求所需要的角色
                                return new AuthorizationDecision(true);
                            }
                        }
                    }
                    return new AuthorizationDecision(false);
                }))
                //.securityContext((context) -> context.securityContextRepository(new HttpSessionSecurityContextRepository()))
                .securityContext((securityContext) -> securityContext.requireExplicitSave(true))
                .addFilterAfter(new LoginRedirectFilter(), SecurityContextHolderFilter.class)
                .addFilterBefore(accountAuthFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class)
                .formLogin().disable()
                .logout().logoutUrl(logoutApi).addLogoutHandler(logoutHandler).logoutSuccessHandler(logoutSuccessHandler)
                .and()
                .exceptionHandling().authenticationEntryPoint(new MyAuthenticationEntryPoint())
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .rememberMe().disable()
                .cors().disable()
                .csrf().disable()
                .headers().disable()
                .authenticationProvider(userAuthProvider);

        return http.build();
    }

    /**
     * 放行静态资源
     *
     * @param
     * @return
     * @date 2023-08-02 11:09:45
     */
    /*@Override
    public void configure(WebSecurity web) {
        web.ignoring()
                .antMatchers("/dist/**")
                .antMatchers("/bg/dist/**");
    }*/

    /*@Override
    public void configure(HttpSecurity http) throws Exception {
        // 在 FilterSecurityInterceptor 中用于权限判定
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(mySecurityMetadataSource);
                        o.setAccessDecisionManager(myAccessDecisionManager);
                        return o;
                    }
                })
                // 不需要认证的接口
                .antMatchers("/bg/api/actuator/**").permitAll()
                .antMatchers(loginApi).permitAll()
                .antMatchers(logoutApi).permitAll()
                .antMatchers("/bg/code/**").permitAll()
                .antMatchers("/bg/api/oss/**").permitAll()
                // 需要认证的接口
                .antMatchers("/bg/**").authenticated()
                .and()
                // 不需要认证的接口
                .authorizeRequests().anyRequest().permitAll();

        // 配置 FilterChainProxy 过滤器链
        http.addFilterAfter(new LoginRedirectFilter(), SecurityContextPersistenceFilter.class);
        http.addFilterBefore(accountAuthFilter(), UsernamePasswordAuthenticationFilter.class);

        // 禁用 UsernamePasswordAuthenticationFilter, 使用自定义的 AccountAuthFilter
        http.formLogin().disable();

        // 配置 LogoutFilter
        http.logout()
                .logoutUrl(logoutApi)
                .addLogoutHandler(logoutHandler)
                .logoutSuccessHandler(logoutSuccessHandler);

        // 配置 ExceptionTranslationFilter, 登入认证接口失败时的处理, 不会重定向到 loginPage
        http.exceptionHandling()
                .authenticationEntryPoint(new MyAuthenticationEntryPoint());

        // 配置 SessionManagementFilter
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

        // 配置 RememberMeAuthenticationFilter, 禁用 RememberMeAuthenticationFilter
        http.rememberMe().disable();
                *//*.key("DExNzAyNTQ2Nzo3NDI3MTNhYmM5MGE5")
                .rememberMeParameter("rememberMe");*//*

        // 配置 CorsFilter, 禁用 CorsFilter
        http.cors().disable();

        // 配置 CsrfFilter, 禁用 CsrfFilter
        http.csrf().disable();

        // 配置 HeaderWriterFilter, 禁用 HeaderWriterFilter
        http.headers().disable();
    }*/

    /**
     * 配置认证管理器
     *
     * @param
     * @return
     * @date 2021-07-25 下午2:28
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 配置认证使用的 provider
     *
     * @param
     * @return
     * @date 2021-07-25 下午2:31
     */
    /*@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(userAuthProvider);
    }*/

    /**
     * 配置账号密码登入 filter
     *
     * @param
     * @return
     * @date 2022-07-06 上午9:54
     */
    public AccountAuthFilter accountAuthFilter(AuthenticationManager authenticationManager) throws Exception {
        AccountAuthFilter filter = new AccountAuthFilter(loginApi, "POST", accountLoginService);
        filter.setAuthenticationManager(authenticationManager);
        filter.setAuthenticationSuccessHandler(successHandler);
        filter.setAuthenticationFailureHandler(failureHandler);
        filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy(sessionRegistry));
        return filter;
    }

    public CompositeSessionAuthenticationStrategy sessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
        int maxSession = 2;
        // 设置帐号并发登入的最大数量
        ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy =
                new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
        concurrentSessionControlAuthenticationStrategy.setMaximumSessions(maxSession);
        //concurrentSessionControlAuthenticationStrategy.setExceptionIfMaximumExceeded(true);

        List<SessionAuthenticationStrategy> authenticationStrategies = new ArrayList<>();
        authenticationStrategies.add(concurrentSessionControlAuthenticationStrategy);
        // 会话固定保护, 认证成功后 session id 会被修改
        authenticationStrategies.add(new SessionFixationProtectionStrategy());
        // 认证成功后将认证信息注册到 SessionRegistry
        authenticationStrategies.add(new RegisterSessionAuthenticationStrategy(sessionRegistry));
        authenticationStrategies.add(new MySessionAuthenticationStrategy());

        return new CompositeSessionAuthenticationStrategy(authenticationStrategies);
    }

    /**
     * 角色继承
     * ADMIN 可以访问 USER 的权限，反之不可
     *
     * @date 2019-07-05 上午11:18
     */
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
        return roleHierarchy;
    }
}
