package cn.security.spring.access;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;

/**
 * 类说明
 *     访问决策管理器，通过{@link FilterInvocationSecurityMetadataSource}
 *     获取用户当前访问的 url，url 需要访问的权限。
 *     在决策器中决定是否可以让用户的请求通过认证
 *     Principal=SamUser(username=Username, password=gly, status=Status, roles=Roles, permissions=Permissions), 
    	 	Credentials=[PROTECTED], 
    		Authenticated=true, 
    		Details=null, 
    		Granted Authorities=[ROLE_ADMIN_T, ROLE_USEXBMR_T, read_T, write_T]]
    	    System.out.println("进行决策-1:" + ((SamUser)authentication.getPrincipal()).getRoles());
    	 
 */
public class UserAccessDecisionManager implements AccessDecisionManager{

    private static final String CURRENT_AUTHENTICATION_ATTR = "CURRENT_AUTHENTICATION_ATTR";
    /**
     * 判定是否拥有权限的决策方法
     * @param authentication  
     *       是CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合 
     *       用户认证通过后的认证对象，里面包含用户的权限列表
     * @param o 
     *     客户端发起的请求的requset信息，可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
     * @param urlAuthAttribute     
     *      为MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果，
     *      此方法是为了判定用户请求的url 是否在权限表中，如果在权限表中，则返回给 decide 方法，
     *      用来判定用户是否有此权限。如果不在权限表中则放行。
     * @throws AccessDeniedException 访问异常
     * @throws InsufficientAuthenticationException 权限异常
     */
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> urlAuthAttribute) throws AccessDeniedException, InsufficientAuthenticationException {
    	// 如果url需要验证的规则为空，表示该 url 不需要拦截，直接放行
        if (CollectionUtils.isEmpty(urlAuthAttribute)) {
            return;
        }
        if (authentication instanceof AnonymousAuthenticationToken){
            throw new BadCredentialsException("未登录");
        }
        String roleId;
        for (ConfigAttribute c : urlAuthAttribute) {
            /**roleId是SamAccessMetadataSource返回的具有访问某url的角色 */
            roleId = c.getAttribute();
            /** 遍历当前登录用户的权限，查看是否有该权限。也可以通过角色认证 角色认证 必须有前缀 `ROLE_`*/
            //当前用户所具有的权限*/
            for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
           
            	  if (roleId.equals(grantedAuthority.getAuthority())){
                    /** 记录当前访问权限在 session；可以用其他缓存对象保存*/
                    getRequest().getSession().setAttribute(CURRENT_AUTHENTICATION_ATTR ,roleId);
                    return;
                }
            }
        }
        //如果没有匹配上，则权限不足
        throw new AccessDeniedException("权限不足");
    }
   
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    /**
     * AbstractSecurityInterceptor 调用，来决定 AccessDecisionManager 是否可以执行传递ConfigAttribute。
     * 包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。
     * @param aClass class
     * @return 支持安全对象的类型
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

    private static HttpServletRequest getRequest(){
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) attributes;
        assert requestAttributes != null;
        return requestAttributes.getRequest();
    }
}
