10 files modified
2 files added
| New file |
| | |
| | | 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)); |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | @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 |
| | |
| | | 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集合 |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | // 获取租户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)) { |
| | |
| | | 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); |
| | |
| | | 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, |
| | |
| | | |
| | | 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 |
| | |
| | | |
| | | 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 = "验证码不正确"; |
| | |
| | | String CAPTCHA_KEY = "blade:auth::blade:captcha:"; |
| | | |
| | | /** |
| | | * 手机验证码key |
| | | */ |
| | | String PHONE_KEY = "blade:auth::blade:phone:"; |
| | | |
| | | /** |
| | | * 登录失败key |
| | | */ |
| | | String USER_FAIL_KEY = "blade:user::blade:fail:"; |
| | |
| | | /** |
| | | * nacos dev 地址 |
| | | */ |
| | | String NACOS_DEV_ADDR = "127.0.0.1:8848"; |
| | | String NACOS_DEV_ADDR = "127.0.0.1:8849"; |
| | | |
| | | /** |
| | | * nacos prod 地址 |
| | |
| | | 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); |
| | |
| | | import org.springblade.core.tool.api.R; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.gistack.common.cache.CacheNames.PHONE_KEY; |
| | | |
| | | /** |
| | | * 短信发送控制层 |
| | | * @author zhongrj |
| | |
| | | |
| | | |
| | | private final BladeRedis bladeRedis; |
| | | |
| | | @GetMapping("/test") |
| | | public void setRedis(){ |
| | | saveCodeForRedis("super_admin","123456"); |
| | | } |
| | | |
| | | /** |
| | | * 发送登录验证码 |
| | |
| | | * @param randNumber |
| | | */ |
| | | private void saveCodeForRedis(String userNumber, String randNumber) { |
| | | bladeRedis.setEx(userNumber,randNumber,5*60L); |
| | | bladeRedis.setEx(PHONE_KEY + userNumber,randNumber,5*60L); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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; |
| | |
| | | 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; |
| | | |
| | | /** |
| | | * 固定内容发送 |
| | |
| | | // 返回 |
| | | 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); |
| | | // } |
| | | |
| | | /** |
| | | * 获取随机字符串 |