xieb
2023-07-26 006baff0642ddb99514f268c8876d0cb3766e298
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package cn.gistack.auth.config;
 
import cn.gistack.auth.granter.PhoneTokenGranter;
import cn.gistack.auth.utils.TokenUtil;
import cn.gistack.common.cache.CacheNames;
import com.alibaba.nacos.common.utils.StringUtils;
import lombok.SneakyThrows;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.DigestUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.WebUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
 
/**
 * 自定义安全认证
 * @author zhongrj
 * @time 2022-9-2
 */
@Component
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
 
    /**
     * 免密常量
     */
    public static final String CUSTOM_LOGIN_SMS = "CUSTOM_LOGIN_SMS";
    public static final String CUSTOM_LOGIN_WX = "CUSTOM_LOGIN_WX";
 
    public static final String CUSTOM_LOGIN_OAUTH = "CUSTOM_LOGIN_OAUTH";
 
    public static final String OAUTH = "oauth";
    public static final String PHONE = "phone";
    public static final String WX = "wx";
 
    @Autowired
    private BladeRedis bladeRedis;
 
    /**
     * 设置 userDetailsService
     * @param userDetailsService
     */
    public MyAuthenticationProvider(UserDetailsService userDetailsService) {
        super();
        setUserDetailsService(userDetailsService);
    }
 
    /**
     * 自定义处理密码校验逻辑
     * @param userDetails 用户信息
     * @param authentication 认证对象信息
     * @throws AuthenticationException 认证异常对象信息
     */
    @SneakyThrows
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String password = authentication.getCredentials().toString();
 
            HttpServletRequest request = WebUtil.getRequest();
            String grantType = request.getParameter(TokenUtil.GRANT_TYPE_KEY);
            // 获取租户ID
            String headerTenant = request.getHeader(TokenUtil.TENANT_HEADER_KEY);
            String paramTenant = request.getParameter(TokenUtil.TENANT_PARAM_KEY);
            // 指定租户ID
            String tenantId = StringUtils.isBlank(headerTenant) ? paramTenant : headerTenant;
 
            // 手机号短信登录时不验证密码、微信登录时不验证密码、单点登录时不验证密码
            if (grantType.equals(PHONE) || grantType.equals(WX) || grantType.equals(OAUTH)) {
                // 成功则清除登录错误次数
                delFailCount(tenantId, userDetails.getUsername());
                return;
            }
 
            String detailsPassword = userDetails.getPassword().substring(7);
            if (!detailsPassword.equals(DigestUtil.hex(password))) {
                int count = getFailCount(tenantId, userDetails.getUsername());
                // 用户存在但密码错误,超过次数则锁定账号
                if (grantType != null && !grantType.equals(TokenUtil.REFRESH_TOKEN_KEY)) {
                    setFailCount(tenantId, userDetails.getUsername(), count);
                    throw new UserDeniedAuthorizationException(TokenUtil.USER_NOT_FOUND);
                }
                this.logger.debug("Failed to authenticate since password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
            // 成功则清除登录错误次数
            delFailCount(tenantId, userDetails.getUsername());
        }
    }
 
    /**
     * 获取账号错误次数
     *
     * @param tenantId 租户id
     * @param username 账号
     * @return int
     */
    private int getFailCount(String tenantId, String username) {
        return Func.toInt(bladeRedis.get(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username)), 0);
    }
 
    /**
     * 设置账号错误次数
     *
     * @param tenantId 租户id
     * @param username 账号
     * @param count    次数
     */
    private void setFailCount(String tenantId, String username, int count) {
        bladeRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username), count + 1, Duration.ofMinutes(30));
    }
 
    /**
     * 清空账号错误次数
     *
     * @param tenantId 租户id
     * @param username 账号
     */
    private void delFailCount(String tenantId, String username) {
        bladeRedis.del(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username));
    }
}