zhongrj
2023-04-24 90389c7d1ff29b39cc8224fbc070b675d918e0f6
手机验证码登录实现,登录认证相关逻辑调整
10 files modified
2 files added
255 ■■■■■ changed files
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/config/MyAuthenticationProvider.java 120 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/config/SecurityConfiguration.java 16 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/BladeTokenGranter.java 2 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/PhoneTokenGranter.java 72 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/service/BladeUserDetailsServiceImpl.java 9 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/support/BladePasswordEncoder.java 2 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/utils/TokenUtil.java 2 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-common/src/main/java/cn/gistack/common/cache/CacheNames.java 5 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-common/src/main/java/cn/gistack/common/constant/LauncherConstant.java 2 ●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-service/skjcmanager-sm/src/main/java/cn/gistack/sm/intelligentCall/service/impl/CallServiceImpl.java 1 ●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-service/skjcmanager-sm/src/main/java/cn/gistack/sm/sms/controller/SmsController.java 9 ●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-service/skjcmanager-sm/src/main/java/cn/gistack/sm/sms/util/SmsUtils.java 15 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/config/MyAuthenticationProvider.java
New file
@@ -0,0 +1,120 @@
package cn.gistack.auth.config;
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.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";
    @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();
            // 手机号登录时不验证密码
            if(!CUSTOM_LOGIN_SMS.equals(password)){
                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;
                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 UsernameNotFoundException(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));
    }
}
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/config/SecurityConfiguration.java
@@ -19,9 +19,11 @@
import cn.gistack.auth.support.BladePasswordEncoderFactories;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -36,6 +38,20 @@
@AllArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyAuthenticationProvider myAuthenticationProvider;
    /**
     * 认证设置
     * @param auth 认证manager
     * @throws Exception 异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置自定义认证provider
        auth.authenticationProvider(myAuthenticationProvider);
    }
    @Bean
    @Override
    @SneakyThrows
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/BladeTokenGranter.java
@@ -43,6 +43,8 @@
        List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
        // 增加验证码模式
        granters.add(new CaptchaTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), bladeRedis));
        // 增加手机验证码模式
        granters.add(new PhoneTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), bladeRedis));
        // 增加第三方登陆模式
        granters.add(new SocialTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), userClient, socialProperties));
        // 组合tokenGranter集合
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/PhoneTokenGranter.java
New file
@@ -0,0 +1,72 @@
package cn.gistack.auth.granter;
import cn.gistack.auth.utils.TokenUtil;
import cn.gistack.common.cache.CacheNames;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * 验证码TokenGranter
 *
 * @author Chill
 */
public class PhoneTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "phone";
    private final AuthenticationManager authenticationManager;
    private BladeRedis bladeRedis;
    public PhoneTokenGranter(AuthenticationManager authenticationManager,
                             AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, BladeRedis bladeRedis) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.bladeRedis = bladeRedis;
    }
    protected PhoneTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                                ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String phone = parameters.get("username");
        String code = parameters.get("password");
        // 获取验证码
        String redisCode = bladeRedis.get(CacheNames.PHONE_KEY + phone);
        // 判断验证码
        if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {
            throw new UserDeniedAuthorizationException(TokenUtil.CAPTCHA_NOT_CORRECT);
        }
        // 通过手机号查询用户
        Authentication userAuth = new UsernamePasswordAuthenticationToken(phone, "CUSTOM_LOGIN_SMS");
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (AccountStatusException | BadCredentialsException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }
        // If the username/password are wrong the spec says we should send 400/invalid grant
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate user: " + phone);
        }
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
}
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/service/BladeUserDetailsServiceImpl.java
@@ -20,7 +20,6 @@
import cn.gistack.sm.sjztmd.entity.AttResManagePerson;
import cn.gistack.sm.sjztmd.feign.IAttResManagePersonClient;
import cn.gistack.system.entity.Dept;
import cn.gistack.system.entity.Role;
import com.alibaba.nacos.common.utils.StringUtils;
import io.jsonwebtoken.Claims;
import lombok.AllArgsConstructor;
@@ -78,7 +77,6 @@
        // 获取租户ID
        String headerTenant = request.getHeader(TokenUtil.TENANT_HEADER_KEY);
        String paramTenant = request.getParameter(TokenUtil.TENANT_PARAM_KEY);
        String password = request.getParameter(TokenUtil.PASSWORD_KEY);
        String grantType = request.getParameter(TokenUtil.GRANT_TYPE_KEY);
        // 判断租户请求头
        if (StringUtil.isAllBlank(headerTenant, paramTenant)) {
@@ -131,11 +129,6 @@
                setFailCount(tenantId, username, count);
                throw new UsernameNotFoundException(TokenUtil.USER_NOT_FOUND);
            }
            // 用户存在但密码错误,超过次数则锁定账号
            if (grantType != null && !grantType.equals(TokenUtil.REFRESH_TOKEN_KEY) && !user.getPassword().equals(DigestUtil.hex(password))) {
                setFailCount(tenantId, username, count);
                throw new UsernameNotFoundException(TokenUtil.USER_NOT_FOUND);
            }
            // 用户角色不存在
            if (Func.isEmpty(userInfo.getRoles())) {
                throw new UserDeniedAuthorizationException(TokenUtil.USER_HAS_NO_ROLE);
@@ -170,8 +163,6 @@
            else
                userInfo.getDetail().set("adCodeBase",attResManagePersonClient.getAdBaseById(deptRes.getData().getAdCode()).getData());
            // 成功则清除登录错误次数
            delFailCount(tenantId, username);
            return new BladeUserDetails(user.getId(),
                user.getTenantId(), StringPool.EMPTY, user.getName(), user.getRealName(), user.getDeptId(), user.getPostId(), user.getRoleId(), Func.join(userInfo.getRoles()), Func.toStr(user.getAvatar(), TokenUtil.DEFAULT_AVATAR),
                username, AuthConstant.ENCRYPT + user.getPassword(), userInfo.getDetail(), true, true, true, true,
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/support/BladePasswordEncoder.java
@@ -18,12 +18,14 @@
import org.springblade.core.tool.utils.DigestUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
 * 自定义密码加密
 *
 * @author Chill
 */
@Component
public class BladePasswordEncoder implements PasswordEncoder {
    @Override
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/utils/TokenUtil.java
@@ -58,6 +58,8 @@
    public final static String DEPT_HEADER_KEY = "Dept-Id";
    public final static String ROLE_HEADER_KEY = "Role-Id";
    public final static String PHONE_HEADER_KEY = "Phone-Key";
    public final static String PHONE_HEADER_CODE = "Phone-Code";
    public final static String CAPTCHA_HEADER_KEY = "Captcha-Key";
    public final static String CAPTCHA_HEADER_CODE = "Captcha-Code";
    public final static String CAPTCHA_NOT_CORRECT = "验证码不正确";
skjcmanager/skjcmanager-common/src/main/java/cn/gistack/common/cache/CacheNames.java
@@ -52,6 +52,11 @@
    String CAPTCHA_KEY = "blade:auth::blade:captcha:";
    /**
     * 手机验证码key
     */
    String PHONE_KEY = "blade:auth::blade:phone:";
    /**
     * 登录失败key
     */
    String USER_FAIL_KEY = "blade:user::blade:fail:";
skjcmanager/skjcmanager-common/src/main/java/cn/gistack/common/constant/LauncherConstant.java
@@ -40,7 +40,7 @@
    /**
     * nacos dev 地址
     */
    String NACOS_DEV_ADDR = "127.0.0.1:8848";
    String NACOS_DEV_ADDR = "127.0.0.1:8849";
    /**
     * nacos prod 地址
skjcmanager/skjcmanager-service/skjcmanager-sm/src/main/java/cn/gistack/sm/intelligentCall/service/impl/CallServiceImpl.java
@@ -62,6 +62,7 @@
        String taskName = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH:mm")) + "智能外呼";
        String[] numbers = StringUtils.split(callingNumbers,",");
        String taskScheduleTime = LocalDateTime.now().plusHours(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("taskScheduleTime = " + taskScheduleTime);
        JSONArray arr = getCalleeInfo(params);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Content-Type", CONTENT_TYPE);
skjcmanager/skjcmanager-service/skjcmanager-sm/src/main/java/cn/gistack/sm/sms/controller/SmsController.java
@@ -11,6 +11,8 @@
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.*;
import static cn.gistack.common.cache.CacheNames.PHONE_KEY;
/**
 * 短信发送控制层
 * @author zhongrj
@@ -26,6 +28,11 @@
    private final BladeRedis bladeRedis;
    @GetMapping("/test")
    public void setRedis(){
        saveCodeForRedis("super_admin","123456");
    }
    /**
     * 发送登录验证码
@@ -57,7 +64,7 @@
     * @param randNumber
     */
    private void saveCodeForRedis(String userNumber, String randNumber) {
        bladeRedis.setEx(userNumber,randNumber,5*60L);
        bladeRedis.setEx(PHONE_KEY + userNumber,randNumber,5*60L);
    }
    /**
skjcmanager/skjcmanager-service/skjcmanager-sm/src/main/java/cn/gistack/sm/sms/util/SmsUtils.java
@@ -21,6 +21,8 @@
import cn.gistack.sm.sms.constant.SmsConstant;
import com.alibaba.fastjson.JSON;
import org.aspectj.lang.annotation.Before;
import org.springblade.core.redis.cache.BladeRedis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@@ -29,10 +31,14 @@
import java.util.List;
import java.util.Random;
import static cn.gistack.common.cache.CacheNames.PHONE_KEY;
import static cn.gistack.sm.sms.constant.SmsConstant.*;
@Component
public class SmsUtils {
    @Autowired
    private BladeRedis bladeRedis;
    /**
     * 固定内容发送
@@ -49,15 +55,6 @@
        // 返回
        return JSON.toJSONString(request);
    }
//    public static void main(String[] args) {
//        SMSSendRequest sendRequest = new SMSSendRequest();
//        sendRequest.setMessageContent("你有一项编号为12349的事务需要处理;");
//        sendRequest.setTemplateId("2431012234034");
//        sendRequest.setUserNumber("15170720695");
//        String s = sendMsg(sendRequest);
//        System.out.println("s = " + s);
//    }
    /**
     * 获取随机字符串