package com.tykj.workflowcore.user.authencation;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.tykj.workflowcore.user.authencation.filter.CustomJwtAuthenticationFilter;
import com.tykj.workflowcore.user.authencation.filter.CustomUsernamePasswordAuthenticationFilter;
import com.tykj.workflowcore.user.authencation.filter.SuccessHandler;
import com.tykj.workflowcore.user.authencation.provider.JwtAuthenticationProvider;
import com.tykj.workflowcore.user.authencation.provider.UsernamePasswordAuthenticationProvider;
import com.tykj.workflowcore.user.service.CenterUserService;
import com.tykj.workflowcore.user.util.AuthenticationUtils;
import com.tykj.workflowcore.user.util.JwtTokenUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
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.AuthenticationException;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.AuthenticationEntryPoint;
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.LogoutSuccessHandler;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.web.cors.CorsUtils;

import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


/**
 * 描述：sprinSecurity安全框架配置
 *
 * @author HuangXiahao
 * @version V1.0
 * @class SecurityWebConfig
 * @packageName com.example.personnelmanager.common.config
 * @data 2020/5/20
 **/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityWebConfig extends WebSecurityConfigurerAdapter {


    private static HashMap hashMap = new HashMap();

    final
    AuthenticationUtils authenticationUtils;

    final
    CenterUserService centerUserService;

    final
    UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;

    final JwtTokenUtils jwtTokenUtils;

    /**
     *自定义Jwt用户验证类
     **/
    final
    JwtAuthenticationProvider jwtAuthenticationProvider;


    public SecurityWebConfig(UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider, CenterUserService centerUserService, AuthenticationUtils authenticationUtils, JwtTokenUtils jwtTokenUtils, JwtAuthenticationProvider jwtAuthenticationProvider) {
        this.usernamePasswordAuthenticationProvider = usernamePasswordAuthenticationProvider;
        this.centerUserService = centerUserService;
        this.authenticationUtils = authenticationUtils;
        this.jwtTokenUtils = jwtTokenUtils;
        this.jwtAuthenticationProvider = jwtAuthenticationProvider;
    }

    /**
     * SpringSecurity配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
        http.cors().and()
                .authorizeRequests()
                .antMatchers("/v2/api-docs",
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources",
                        "/swagger-resources/configuration/security",
                        "/swagger-ui.html",
                        "/webjars/**").permitAll()
                // 系统内对外开放无需权限也可以调用的接口
                .antMatchers("/images/*","/app/appToken",
                        "/app/useApi"
                        ,"/app/useData",
                        "/app/token").permitAll()
                //页面文件访问权限
                .antMatchers("/workflow/**").permitAll()
                //全部放行
//                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/user/login")
                .permitAll()
//                .and()
//                .rememberMe()
//                .tokenValiditySeconds(10)
                .and()
                .logout()
                .logoutUrl("/user/logout")
                .logoutSuccessHandler(new CustomLogoutSuccessHandler())
                .deleteCookies("JSESSIONID")
                .and()
                .headers()
                .frameOptions()
                .disable()
                .and()
                .csrf()
                .disable();
        http.exceptionHandling()
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint());
        //添加自定义用户验证拦截器至UsernamePasswordAuthenticationFilter的位置
        http
                .addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
                    HttpServletResponse resp = event.getResponse();
                    resp.setContentType("application/json;charset=utf-8");
                    resp.setStatus(401);
                    PrintWriter out = resp.getWriter();
//                    out.write(new ObjectMapper().writeValueAsString(new ResultObj(405, "您已在另一台设备登录，本次登录已下线!")));
                    out.flush();
                    out.close();
                }), ConcurrentSessionFilter.class)
                .addFilterAt(customUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(customJwtUsernamePasswordAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);

        //                .addFilterAt(new JwtAuthenticationTokenFilter(jwtTokenUtils), BasicAuthenticationFilter.class);
    }

    /***
     * 自定义用户名密码验证拦截器配置
     *
     * @Return : com.example.personnelmanager.common.authencation.filter.CustomUsernamePasswordAuthenticationFilter
     * @return
     */
    @Bean
    Filter customUsernamePasswordAuthenticationFilter() throws Exception {
        CustomUsernamePasswordAuthenticationFilter filter = new CustomUsernamePasswordAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(new SuccessHandler());
        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
        return filter;
    }

    @Bean
    CustomJwtAuthenticationFilter customJwtUsernamePasswordAuthenticationFilter() throws Exception {
        CustomJwtAuthenticationFilter filter = new CustomJwtAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
        return filter;
    }

    /***
     * 验证管理器配置
     *
     * @param auth
     * @Return : void
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(jwtAuthenticationProvider);
        auth.authenticationProvider(usernamePasswordAuthenticationProvider);
    }

    @Bean
    public SessionRegistryImpl sessionRegistry() {
        return new SessionRegistryImpl();
    }


    /***
     * 登录成功后干些啥
     */
    public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

        @Override
        public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                            Authentication authentication) throws IOException {
            //修改用户的登录时间和登录IP,这里不使用方法内部的authentication是应为考虑到单元测试时可能会存在userDetail类型不统一的问题
//            UserDetail currentUser = authenticationUtils.getAuthentication();
//            currentUser.setLoginIp(IpUtil.getIpAddress(httpServletRequest));
//            currentUser.setLoginTime(DateTime.now().toDate());
//            userService.updateUser(currentUser.toUser());
            //设置返回的信息
            httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild");
            httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
            Map<String, Object> result = new HashMap<>(2);
//            result.put("userId", currentUser.getId());
//            result.put("type", currentUser.getType());
            result.put("msg", "登录成功");
            servletOutputStream.write(new ObjectMapper().writeValueAsString(result).getBytes(StandardCharsets.UTF_8));
            servletOutputStream.flush();
            servletOutputStream.close();
        }
    }

    /**
     * 登录失败了干些啥
     */
    public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

        @Override
        public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                            AuthenticationException e) throws IOException {
            httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
            Map<String, Object> result = new HashMap<>(1);
            result.put("msg", "用户名或密码不正确");
            servletOutputStream.write(new ObjectMapper().writeValueAsString(result).getBytes(StandardCharsets.UTF_8));
            servletOutputStream.flush();
            servletOutputStream.close();
        }
    }

    /**
     * 没有登录干些啥
     */
    public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

        @Override
        public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                             AuthenticationException e) throws IOException {
            httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild");
            httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
            ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
            Map<String, Object> result = new HashMap<>(2);
            result.put("msg", "没有登录");
            servletOutputStream.write(new ObjectMapper().writeValueAsString(result).getBytes(StandardCharsets.UTF_8));
            servletOutputStream.flush();
            servletOutputStream.close();
        }
    }

    /**
     * 登出后做些什么
     */
    public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
        @Override
        public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

            httpServletResponse.setStatus(HttpStatus.OK.value());
            httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
            ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
            Map<String, Object> result = new HashMap<>(1);
            result.put("msg", "登出成功");
            servletOutputStream.write(new ObjectMapper().writeValueAsString(result).getBytes(StandardCharsets.UTF_8));
            servletOutputStream.flush();
            servletOutputStream.close();
        }
    }


}
