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)); } }