package com.project.shiro.config;

import com.project.shiro.util.AuthenticationFilter;
import com.project.shiro.util.AuthenticationRealm;
import com.project.shiro.util.AuthorizationFilter;
import com.project.shiro.util.redis.ShiroRedisCacheManager;
import com.project.shiro.util.redis.ShiroRedisSessionDAO;
import org.apache.shiro.session.mgt.SessionManager;
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.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Description: shiro配置类
 * User: mxy
 * Date: 2019-04-16
 */
@Configuration
public class ShiroConfig {

    private static final transient Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     * 配置拦截器
     * <p>
     * 定义拦截URL权限,优先级从上到下
     * 1). anon  : 匿名访问,无需登录
     * 2). authc : 登录后才能访问
     * 3). logout: 登出
     * 4). frameperms : 自定义的过滤器
     * <p>
     * URL 匹配风格
     * 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/;
     * 2). *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1;
     * 3). **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b
     * <p>
     * 配置身份验证成功,失败的跳转路径
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        logger.info("进入Shiro拦截工厂");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 自定义的过滤器
        Map<String, Filter> filterMap = new HashMap<>();
        // map里面key值要为过滤器的名称,value为过滤器对象
        filterMap.put("authc", authenticationFilter());
        filterMap.put("frameperms", authorizationFilter());
        // 将自定义的过滤器加入到过滤器集合中
        shiroFilterFactoryBean.setFilters(filterMap);

        // 设置拦截器集合
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/admin/", "anon"); // 后台资源-匿名访问
        filterChainDefinitionMap.put("/admin/res/**", "anon"); // 静态资源-匿名访问
        filterChainDefinitionMap.put("/admin/anon/**", "anon"); // 后台可匿名访问资源-匿名访问
        filterChainDefinitionMap.put("/admin/login", "authc"); // 登录页面-身份认证
        filterChainDefinitionMap.put("/admin/logout", "logout");    // 用户退出,只需配置logout即可实现该功能
        filterChainDefinitionMap.put("/admin/common/**", "anon");  // 其他路径均需要身份认证,一般位于最下面,优先级最低
        filterChainDefinitionMap.put("/admin/**", "authc,frameperms");  // 其他路径均需要身份认证,一般位于最下面,优先级最低

        // 设置拦截器
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        shiroFilterFactoryBean.setLoginUrl("/admin/login");       // 登录的路径
//        shiroFilterFactoryBean.setUnauthorizedUrl("/admin/common/unauthorized.jhtml");  // 验证失败后跳转的路径
        logger.info("Shiro拦截工厂配置完成");
        return shiroFilterFactoryBean;
    }

    /**
     * 配置Shiro生命周期处理器
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 自动创建代理类,若不添加,Shiro的注解可能不会生效。
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启Shiro的注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 配置加密匹配,使用MD5的方式,进行1024次加密
     */
//    @Bean
//    public HashedCredentialsMatcher hashedCredentialsMatcher() {
//        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//        hashedCredentialsMatcher.setHashIterations(1024);
//        return hashedCredentialsMatcher;
//    }

    /**
     * SecurityManager 安全管理器;Shiro的核心
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义的Realm
        securityManager.setRealm(authenticationShiroRealm());
        // 缓存管理
        securityManager.setCacheManager(shiroRedisCacheManager());
        // 会话管理
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /**
     * 自定义Realm,可以多个
     */
    @Bean
    public AuthenticationRealm authenticationShiroRealm() {
        AuthenticationRealm authenticationRealm = new AuthenticationRealm();
        //authenticationRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return authenticationRealm;
    }

    /**
     * redis缓存管理
     */
    @Bean
    public ShiroRedisCacheManager shiroRedisCacheManager() {
        return new ShiroRedisCacheManager();
    }

    /**
     * 设置session会话管理者
     */
    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionIdCookie(simpleCookie());
        defaultWebSessionManager.setSessionDAO(shiroRedisSessionDAO());
        return defaultWebSessionManager;
    }

    /**
     * session管理
     */
    @Bean
    public ShiroRedisSessionDAO shiroRedisSessionDAO() {
        return new ShiroRedisSessionDAO();
    }

    /**
     * 限制同一账号登录同时登录人数控制
     * @return
     */
    public KickoutSessionControlFilter kickoutSessionControlFilter(){
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;
        //这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理
        //也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性
        kickoutSessionControlFilter.setCacheManager(cacheManager());
        //用于根据会话ID,获取会话进行踢出操作的;
        kickoutSessionControlFilter.setSessionManager(sessionManager());
        //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。
        kickoutSessionControlFilter.setKickoutAfter(false);
        //同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
        kickoutSessionControlFilter.setMaxSession(1);
        //被踢出后重定向到的地址;
        kickoutSessionControlFilter.setKickoutUrl("/kickout");
        return kickoutSessionControlFilter;
    }

    /**
     * 这里需要设置一个cookie的名称  原因就是会跟原来的session的id值重复的
     */
    @Bean
    public SimpleCookie simpleCookie() {
        return new SimpleCookie("SHAREJSESSIONID");
    }


    @Bean
    public AuthenticationFilter authenticationFilter() {
        return new AuthenticationFilter();
    }

    @Bean
    public AuthorizationFilter authorizationFilter() {
        return new AuthorizationFilter();
    }

}