package com.tykj.workflowcore.user.authencation.provider;

import com.tykj.workflowcore.user.authencation.checks.DefaultPreAuthenticationChecks;
import com.tykj.workflowcore.user.service.CenterUserService;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;


/**
 * 自定义SpringSecurity的用户处理类类
 * 当用户通过用户名和密码访问本系统时由该类进行用户验证
 *
 * @author HuangXiahao
 * @version V1.0
 * @class UsernamePasswordAuthenticationProvider
 * @packageName com.example.personnelmanager.common.authencationProvider
 * @data 2020/6/14
 **/
@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {

    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * 用户密码加密验证工具
     */
    private final PasswordEncoder passwordEncoder;

    private final CenterUserService centerUserService;

    /**
     * 用户可用性检查类
     */
    private final UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();

    public UsernamePasswordAuthenticationProvider(PasswordEncoder passwordEncoder, CenterUserService centerUserService) {
        this.passwordEncoder = passwordEncoder;
        this.centerUserService = centerUserService;
    }

    /***
     * 验证用户
     *
     * @param authentication
     * @Return : org.springframework.security.core.Authentication
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                "错误的凭证");
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();
        UserDetails user = retrieveUser(username);
        preAuthenticationChecks.check(user);
        additionalAuthenticationChecks(user,
                (UsernamePasswordAuthenticationToken) authentication);
        return createSuccessAuthentication(user, authentication, user);

    }

    /**
     * 描述：返回True则由该对象进行用户验证
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }

    protected final UserDetails retrieveUser(String username) {
        UserDetails loadedUser = centerUserService.getUserDetail(username);
        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        return loadedUser;
    }

    /***
     * 描述：验证用户的密码是否正确
     * 该方法由SpringSecurity源码魔改得到
     * @param userDetails
     * @param authentication
     * @Return : void
     * @Author : HuangXiahao
     * @Date : 2020/6/14 15:50
    */
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException("Authentication failed: no credentials provided");
        }
        String presentedPassword = authentication.getCredentials().toString();
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");
            throw new BadCredentialsException("Authentication failed: password does not match stored value");
        }
    }
    /***
     * 描述：创建一个已经通过验证的用户实例
     * 该方法由SpringSecurity源码魔改得到
     * @param principal
     * @param authentication
     * @param user
     * @Return : org.springframework.security.core.Authentication
     * @Author : HuangXiahao
     * @Date : 2020/6/14 15:52
    */
    protected Authentication createSuccessAuthentication(Object principal,
                                                         Authentication authentication, UserDetails user) {
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                user.getAuthorities());
        result.setDetails(authentication.getDetails());
        return result;
    }
}
