package com.qxgmat.util.shiro;

import com.nuliji.tools.shiro.*;
import com.nuliji.tools.shiro.cache.RedisManager;
import com.nuliji.tools.shiro.cache.CustomCacheManager;
import com.nuliji.tools.shiro.cache.RedisCacheProvider;
import com.nuliji.tools.shiro.inter.HeaderTokenManager;
import com.nuliji.tools.shiro.session.CustomSessionDao;
import com.nuliji.tools.shiro.session.RedisSessionRepository;
import com.nuliji.tools.shiro.session.SessionRepository;
import com.qxgmat.util.shiro.impl.UserTokenManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import javax.servlet.Filter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
        chain.addPathDefinition("/admin/auth/**", "anon");
        chain.addPathDefinition("/admin/**", "role[manager]");
        chain.addPathDefinition("/api/auth/**", "anon");
        chain.addPathDefinition("/api/my/**", "token,role[user]");
        chain.addPathDefinition("/api/question/**", "token,role[user]");

        chain.addPathDefinition("/**", "anon");
        return chain;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();

        DevelopFilter developFilter = new DevelopFilter();
        RoleFilter roleFilter = new RoleFilter();
        TokenFilter tokenFilter = new TokenFilter(headerTokenManager());
        filters.put("role", roleFilter);
        filters.put("develop", developFilter);
        filters.put("token", tokenFilter);
        shiroFilterFactoryBean.setFilters(filters);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition().getFilterChainMap());
        return shiroFilterFactoryBean;
    }

    @Bean
    public RedisManager redisManager(RedisConnectionFactory factory){
        RedisManager redisManager = new RedisManager();
        redisManager.setFactory(factory);
        redisManager.setExpire(86400000);
        return redisManager;
    }

    @Bean
    public RedisCacheProvider redisCacheProvider(RedisManager redisManager){
        RedisCacheProvider redisCacheProvider = new RedisCacheProvider();
        redisCacheProvider.setRedisManager(redisManager);
        return redisCacheProvider;
    }

    @Bean
    public SessionRepository redisSessionRepository(RedisManager redisManager){
        RedisSessionRepository redisSessionRepository = new RedisSessionRepository();
        redisSessionRepository.setRedisManager(redisManager);
        return redisSessionRepository;
    }

    @Bean
    public CacheManager customCacheManager(RedisCacheProvider redisCacheProvider){
        CustomCacheManager customCacheManager = new CustomCacheManager();
        customCacheManager.setCacheProvider(redisCacheProvider);
        return customCacheManager;
    }

    /**
     * 加密方式
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);// 散列的次数，比如散列两次，相当于md5(md5(""));
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);// 表示是否存储散列后的密码为16进制，需要和生成密码时的一样，默认是base64；
        return hashedCredentialsMatcher;
    }

    @Bean
    public HeaderTokenManager headerTokenManager() {
        UserTokenManager userTokenManager = new UserTokenManager();
        return userTokenManager;
    }

    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
//        userRealm.setCredentialsMatcher(new SimpleCredentialsMatcher());
        userRealm.setCachingEnabled(true);
        return userRealm;
    }

    @Bean
    public TokenRealm tokenRealm() {
        TokenRealm tokenRealm = new TokenRealm();
//        userRealm.setCredentialsMatcher(new SimpleCredentialsMatcher());
        tokenRealm.setCachingEnabled(true);
        return tokenRealm;
    }

    @Bean
    public ManagerRealm managerRealm() {
        ManagerRealm managerRealm = new ManagerRealm();
//        managerRealm.setCredentialsMatcher(new SimpleCredentialsMatcher());
        managerRealm.setCachingEnabled(true);
        return managerRealm;
    }

    @Bean
    public OauthRealm oauthRealm(){
        OauthRealm oauthRealm = new OauthRealm();
        oauthRealm.setCachingEnabled(false);
        return oauthRealm;
    }

    @Bean
    public DevelopRealm developRealm(){
        DevelopRealm developRealm = new DevelopRealm();
        developRealm.setCachingEnabled(true);
        return developRealm;
    }

    @Bean
    public Collection<Realm> realms() {
        Collection<Realm> realms = new ArrayList<>();
        realms.add(userRealm());
        realms.add(tokenRealm());
        realms.add(oauthRealm());
        realms.add(managerRealm());
        realms.add(developRealm());
        return realms;
    }

    /**
     * 配置认证策略，只要有一个Realm认证成功即可，并且返回所有认证成功信息
     *
     * @return
     */
    @Bean
    AtLeastOneSuccessfulStrategy authenticationStrategy() {
        return new AtLeastOneSuccessfulStrategy();
    }

    /**
     * 配置使用自定义认证器，可以实现多Realm认证，并且可以指定特定Realm处理特定类型的验证
     *
     * @return
     */
    @Bean
    RealmAuthenticator authenticator() {
        RealmAuthenticator authenticator = new RealmAuthenticator();
        authenticator.setAuthenticationStrategy(authenticationStrategy());
        return authenticator;
    }

    @Bean
    public Cookie rememberMeCookie() {
        // 这个参数是cookie的名称，对应前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // <!-- 记住我cookie生效时间30天 ,单位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

    /**
     * CookieRememberMeManager
     *
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        return cookieRememberMeManager;
    }

//    @Bean
//    public MyShiroSessionListener myShiroSessionListener() {
//        return new MyShiroSessionListener();
//    }


    /**
     * 会话监听器
     *
     * @return
     */
    @Bean
    public Collection<SessionListener> sessionListeners() {
        Collection<SessionListener> listeners = new ArrayList<>();
//        listeners.add(myShiroSessionListener());
        return listeners;
    }

    /**
     * 会话ID生成器
     *
     * @return
     */
    @Bean
    public SessionIdGenerator sessionIdGenerator() {
        SessionIdGenerator idGenerator = new SessionIdGenerator() {
            @Override
            public Serializable generateId(Session session) {
                Serializable uuid = new JavaUuidSessionIdGenerator().generateId(session);
                System.out.println("sessionIdGenerator:" + uuid);
                return uuid;
            }
        };
        return idGenerator;
    }

    /**
     * 会话DAO
     *
     * @return
     */
    @Bean
    public CustomSessionDao sessionDao(SessionRepository sessionRepository) {
        CustomSessionDao sessionDao = new CustomSessionDao();

        sessionDao.setSessionRepository(sessionRepository);
        sessionDao.setSessionIdGenerator(sessionIdGenerator());
        return sessionDao;
    }

    /**
     * 处理session有效期
     *
     * @return
     */
    @Bean
    public ExecutorServiceSessionValidationScheduler sessionValidationScheduler() {
        ExecutorServiceSessionValidationScheduler sessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();
        sessionValidationScheduler.setInterval(1800000);
        return sessionValidationScheduler;
    }

    @Bean(name = "sessionManager")
    public SessionManager sessionManager(SessionDAO sessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        Cookie sessionIdCookie = new SimpleCookie("JSESSIONID");
        sessionIdCookie.setPath("/");
        sessionManager.setSessionIdCookie(sessionIdCookie);
        sessionManager.setGlobalSessionTimeout(86400000);
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionValidationScheduler(sessionValidationScheduler());
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionListeners(sessionListeners());
        sessionManager.setSessionDAO(sessionDAO);
        return sessionManager;
    }

    /**
     * 会话管理器
     *
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(CacheManager cacheManager, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setCacheManager(cacheManager);
        securityManager.setAuthenticator(authenticator());
        securityManager.setRememberMeManager(rememberMeManager());
        securityManager.setRealms(realms());
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }

    /**
     * 开启shiro注解 ---- 注解权限
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Shiro生命周期处理器 ---可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启shiro注解 ----启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor
     * 之后才可以使用
     *
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }
}
