From 4d3b7c8f78850f6f92b7c460010262db5cd2f64e Mon Sep 17 00:00:00 2001 From: khalil Date: Fri, 14 Mar 2025 11:28:22 +0800 Subject: [PATCH] =?UTF-8?q?=E9=82=AE=E7=AE=B1-=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../account/AccountBlockCheckService.java | 12 +++- .../business/controller/EmailController.java | 2 +- .../oauth2/constant/GrantTypeEnum.java | 2 + .../oauth2/constant/LoginTypeEnum.java | 5 ++ .../accompany-oauth2-service/pom.xml | 5 ++ .../oauth2/service/MyUserDetailsService.java | 2 + .../service/MyUserDetailsServiceImpl.java | 51 ++++++++++++----- .../email/EmailAuthenticationProvider.java | 56 +++++++++++++++++++ .../email/EmailAuthenticationToken.java | 31 ++++++++++ .../support/email/EmailTokenGranter.java | 39 +++++++++++++ .../oauth2/ticket/TicketServices.java | 3 +- .../config/AuthorizationServerConfig.java | 2 + 12 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationProvider.java create mode 100644 accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationToken.java create mode 100644 accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailTokenGranter.java diff --git a/accompany-base/accompany-basic/accompany-basic-service/src/main/java/com/accompany/core/service/account/AccountBlockCheckService.java b/accompany-base/accompany-basic/accompany-basic-service/src/main/java/com/accompany/core/service/account/AccountBlockCheckService.java index a35107e28..dd5b97fad 100644 --- a/accompany-base/accompany-basic/accompany-basic-service/src/main/java/com/accompany/core/service/account/AccountBlockCheckService.java +++ b/accompany-base/accompany-basic/accompany-basic-service/src/main/java/com/accompany/core/service/account/AccountBlockCheckService.java @@ -87,7 +87,7 @@ public class AccountBlockCheckService { return checkBlocked(ip, BlockTypeEnum.BLOCK_IP); } - public Long checkReturnEndTime(Long erbanNo, String phone, String deviceId, String ip){ + public Long checkReturnEndTime(Long erbanNo, String phone, String email, String deviceId, String ip){ Long endTime = checkBlockedErbanNoReturnBlockEndTime(erbanNo); if (null != endTime){ return endTime; @@ -96,6 +96,10 @@ public class AccountBlockCheckService { if (null != endTime){ return endTime; } + endTime = checkBlockedEmailReturnBlockEndTime(email); + if (null != endTime){ + return endTime; + } endTime = checkBlockedDeviceReturnBlockEndTime(deviceId); if (null != endTime){ return endTime; @@ -143,6 +147,12 @@ public class AccountBlockCheckService { return checkBlockedReturnBlockEndTime(phone, BlockTypeEnum.BLOCK_PHONE); } + public Long checkBlockedEmailReturnBlockEndTime(String email){ + if (!StringUtils.hasText(email)){ + return null; + } + return checkBlockedReturnBlockEndTime(email, BlockTypeEnum.BLOCK_EMAIL); + } /** * 查询设备是否被封禁 diff --git a/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/EmailController.java b/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/EmailController.java index e6db25f66..416ac9027 100644 --- a/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/EmailController.java +++ b/accompany-business/accompany-business-web/src/main/java/com/accompany/business/controller/EmailController.java @@ -71,7 +71,7 @@ public class EmailController extends BaseController { return new BusiResult<>(BusiStatus.ACCOUNT_BLOCK_ERROR, I18NMessageSourceUtil.getMessage(ACCOUNT_LOGIN_BLOCK_MSG, new Object[]{emailAddress}, PartitionEnum.ENGLISH.getId())); } - emailService.sendEmailCode(emailAddress, type, deviceInfo, ip, null, true); + emailService.sendEmailCode(emailAddress, type, deviceInfo, ip, null, false); return new BusiResult<>(BusiStatus.SMS_SEND_SUCCESS); } diff --git a/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/GrantTypeEnum.java b/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/GrantTypeEnum.java index 2b7181ed2..8f35c994f 100644 --- a/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/GrantTypeEnum.java +++ b/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/GrantTypeEnum.java @@ -17,6 +17,8 @@ public enum GrantTypeEnum { APPLE("apple"), VERIFY_CODE("verify_code"), + + EMAIL("email"), ; private final String value; diff --git a/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/LoginTypeEnum.java b/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/LoginTypeEnum.java index 3a7ea2de9..7cccbfa7e 100644 --- a/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/LoginTypeEnum.java +++ b/accompany-oauth2/accompany-oauth2-sdk/src/main/java/com/accompany/oauth2/constant/LoginTypeEnum.java @@ -53,6 +53,11 @@ public enum LoginTypeEnum { */ FACEBOOK((byte) 10), + /** + * 邮箱登录 + * */ + EMAIL((byte) 11), + ; private final byte value; diff --git a/accompany-oauth2/accompany-oauth2-service/pom.xml b/accompany-oauth2/accompany-oauth2-service/pom.xml index 525b8958b..885b08de6 100644 --- a/accompany-oauth2/accompany-oauth2-service/pom.xml +++ b/accompany-oauth2/accompany-oauth2-service/pom.xml @@ -33,6 +33,11 @@ accompany-sms-service ${revision} + + com.accompany + accompany-email-service + ${revision} + com.accompany accompany-mq-service diff --git a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsService.java b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsService.java index 05cc542f0..5436903a1 100644 --- a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsService.java +++ b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsService.java @@ -9,6 +9,8 @@ import org.springframework.security.core.userdetails.UserDetailsService; public interface MyUserDetailsService extends UserDetailsService { + UserDetails loadUserByEmail(String email, String code, DeviceInfo deviceInfo, String ipAddress) throws Exception; + UserDetails loadUserByPhone(String phone, String phoneAreaCode, String smsCode, DeviceInfo deviceInfo, String ipAddress) throws Exception; UserDetails loadUserByOpenId(String openid, Byte type, DeviceInfo deviceInfo, String ipAddress, String unionId) throws Exception; diff --git a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsServiceImpl.java b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsServiceImpl.java index 198734ad0..5df42fa8c 100644 --- a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsServiceImpl.java +++ b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/service/MyUserDetailsServiceImpl.java @@ -7,6 +7,7 @@ import com.accompany.common.device.DeviceInfo; import com.accompany.common.redis.RedisKey; import com.accompany.common.status.BusiStatus; import com.accompany.common.utils.CommonUtil; +import com.accompany.core.enumeration.PartitionEnum; import com.accompany.core.exception.ServiceException; import com.accompany.core.model.Account; import com.accompany.core.model.AccountLoginRecord; @@ -23,6 +24,7 @@ import com.accompany.core.service.region.RegionNetworkService; import com.accompany.core.service.user.PhoneBlackService; import com.accompany.core.service.user.UsersBaseService; import com.accompany.core.util.I18NMessageSourceUtil; +import com.accompany.email.service.EmailService; import com.accompany.oauth2.constant.LoginTypeEnum; import com.accompany.oauth2.exception.CustomOAuth2Exception; import com.accompany.oauth2.model.AccountDetails; @@ -71,9 +73,9 @@ public class MyUserDetailsServiceImpl implements MyUserDetailsService { @Autowired private SmsService smsService; @Autowired - private SysConfService sysConfService; + private EmailService emailService; @Autowired - private PhoneBlackService phoneBlackService; + private SysConfService sysConfService; @Autowired private RegionNetworkService regionService; @Autowired @@ -125,6 +127,16 @@ public class MyUserDetailsServiceImpl implements MyUserDetailsService { return new AccountDetails(account); } + @Override + public UserDetails loadUserByEmail(String email, String code, DeviceInfo deviceInfo, String ipAddress) throws Exception { + Account account = accountService.getAccountByEmail(email); + if (account == null) { + throw new CustomOAuth2Exception(CustomOAuth2Exception.USER_NOT_EXISTED, + BusiStatus.USER_NOT_EXISTED.getReasonPhrase()); + } + return new AccountDetails(account); + } + @Override public UserDetails loadUserByPhone(String phone, String phoneAreaCode, String smsCode, DeviceInfo deviceInfo, String ipAddress) throws Exception { @@ -159,33 +171,36 @@ public class MyUserDetailsServiceImpl implements MyUserDetailsService { @Override public void login(String reqUserName, UserDetails userDetails, LoginTypeEnum loginType, DeviceInfo deviceInfo, - String ip, String openId, String unionId, String smsCode) throws Exception { + String ip, String openId, String unionId, String code) throws Exception { AccountDetails details = (AccountDetails) userDetails; Account account = details.getAccount(); + Long uid = account.getUid(); + String deviceId = deviceInfo.getDeviceId(); String client = deviceInfo.getClient(); String app = deviceInfo.getApp(); String appVersion = deviceInfo.getAppVersion(); - Long uid = account.getUid(); Date date = new Date(); + // 拦截指定账号登录 Users users = usersBaseService.getUsersByUid(account.getUid()); if (users != null && NEED_INTERCEPT_USER_TYPE.contains(users.getDefUser())) { throw new ServiceException(BusiStatus.ILLEGAL_OPERATE); } - Long blockEndTime = accountBlockCheckService.checkReturnEndTime(account.getErbanNo(), account.getPhone(), deviceId, ip); + Long blockEndTime = accountBlockCheckService.checkReturnEndTime(account.getErbanNo(), account.getPhone(), account.getEmail(), deviceId, ip); //检查账号、设备号、号段是否封禁 if (null != blockEndTime){ CustomOAuth2Exception exception = new CustomOAuth2Exception(CustomOAuth2Exception.ACCOUNT_ERROR, ""); - Integer partitionId = users.getPartitionId(); - exception.addAdditionalInformation("reason", I18NMessageSourceUtil.getMessage(ACCOUNT_LOGIN_BLOCK_MSG, new Object[]{users.getErbanNo()}, partitionId)); + Integer partitionId = null != users? users.getPartitionId(): PartitionEnum.ENGLISH.getId(); + exception.addAdditionalInformation("reason", I18NMessageSourceUtil.getMessage(ACCOUNT_LOGIN_BLOCK_MSG, new Object[]{account.getErbanNo()}, partitionId)); exception.addAdditionalInformation("date", String.valueOf(blockEndTime)); throw exception; } //校验验证码 - checkSmsCodeByUserType(account, smsCode, loginType, deviceInfo.getApp()); + checkCodeByUserType(account, code, loginType); + accountManageService.checkAccountCancel(uid); //更新account信息 String newToken = null; @@ -194,6 +209,7 @@ public class MyUserDetailsServiceImpl implements MyUserDetailsService { } else { newToken = accountService.refreshAndGetNetEaseToken(account); } + account.setNeteaseToken(newToken); account.setApp(deviceInfo.getApp()); account.setAppVersion(deviceInfo.getAppVersion()); @@ -210,8 +226,10 @@ public class MyUserDetailsServiceImpl implements MyUserDetailsService { account.setLastLoginTime(date); account.setLastLoginIp(ip); accountService.updateById(account); + //更新用户正在使用的app字段 userAppService.updateCurrentApp(uid, app, date, ip, appVersion); + //将用户信息登记 AccountLoginRecord accountLoginRecord = buildAccountLoginRecord(ip, account, loginType.getValue(), deviceInfo, openId); loginRecordService.addAccountLoginRecordAsync(accountLoginRecord); @@ -269,21 +287,24 @@ public class MyUserDetailsServiceImpl implements MyUserDetailsService { * 普通用户需要用手机验证码登录,官方账号和公会账号不校验验证码 * * @param account - * @param smsCode - * @param appName + * @param code */ - private void checkSmsCodeByUserType(Account account, String smsCode, LoginTypeEnum loginType, String appName) { + private void checkCodeByUserType(Account account, String code, LoginTypeEnum loginType) { //是否手机号登录 - Boolean isPhone = LoginTypeEnum.ID.getValue() == loginType.getValue(); - if (!isPhone) { + boolean needVerifyCode = LoginTypeEnum.ID.getValue() == loginType.getValue() + || LoginTypeEnum.EMAIL.getValue() == loginType.getValue(); + if (!needVerifyCode) { return; } - if (StringUtils.isEmpty(smsCode)) { + if (!StringUtils.hasText(code)) { throw new CustomOAuth2Exception(CustomOAuth2Exception.VERIFY_CODE_ERROR, BusiStatus.VERIFY_CODE_ERROR.getReasonPhrase()); } - if (!smsService.verifySmsCode(account.getPhone(), smsCode)) { + boolean verifyResult = LoginTypeEnum.ID.getValue() == loginType.getValue()? + smsService.verifySmsCode(account.getPhone(), code): + emailService.verifyEmailCode(account.getEmail(), code); + if (!verifyResult) { throw new CustomOAuth2Exception(CustomOAuth2Exception.VERIFY_CODE_ERROR, BusiStatus.VERIFY_CODE_ERROR.getReasonPhrase()); } diff --git a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationProvider.java b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationProvider.java new file mode 100644 index 000000000..366b9d886 --- /dev/null +++ b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationProvider.java @@ -0,0 +1,56 @@ +package com.accompany.oauth2.support.email; + +import com.accompany.common.device.DeviceInfo; +import com.accompany.oauth2.constant.LoginTypeEnum; +import com.accompany.oauth2.exception.CustomOAuth2Exception; +import com.accompany.oauth2.service.MyUserDetailsService; +import com.accompany.oauth2.util.RequestContextHolderUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.BeanUtils; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collections; +import java.util.Map; + +@Slf4j +public class EmailAuthenticationProvider implements AuthenticationProvider { + + private final MyUserDetailsService userDetailsService; + + public EmailAuthenticationProvider(MyUserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Map params = (Map) authentication.getDetails(); + String email = authentication.getName(); + String code = (String) authentication.getCredentials(); + DeviceInfo deviceInfo = new DeviceInfo(); + try { + BeanUtils.populate(deviceInfo, params); + } catch (Exception e) { + log.error("populate deviceInfo fail", e); + } + + UserDetails userDetails = null; + try { + userDetails = userDetailsService.loadUserByEmail(email, code, deviceInfo, RequestContextHolderUtils.getRemoteAddr()); + userDetailsService.login(email, userDetails, LoginTypeEnum.EMAIL, deviceInfo, code); + } catch (CustomOAuth2Exception e) { + throw e; + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new RuntimeException(e.getMessage()); + } + return new EmailAuthenticationToken(userDetails, Collections.emptyList()); + } + + @Override + public boolean supports(Class aClass) { + return EmailAuthenticationToken.class.isAssignableFrom(aClass); + } +} diff --git a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationToken.java b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationToken.java new file mode 100644 index 000000000..1301eeeee --- /dev/null +++ b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailAuthenticationToken.java @@ -0,0 +1,31 @@ +package com.accompany.oauth2.support.email; + +import org.springframework.security.authentication.AbstractAuthenticationToken; + +public class EmailAuthenticationToken extends AbstractAuthenticationToken { + + protected static final String EMAIL = "email"; + + protected static final String CODE = "code"; + + private final Object principal; + private final Object credentials; + + public EmailAuthenticationToken(Object principal, Object credentials) { + super(null); + this.principal = principal; + this.credentials = credentials; + this.setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getPrincipal() { + return principal; + } +} + diff --git a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailTokenGranter.java b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailTokenGranter.java new file mode 100644 index 000000000..9fe986340 --- /dev/null +++ b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/support/email/EmailTokenGranter.java @@ -0,0 +1,39 @@ +package com.accompany.oauth2.support.email; + +import com.accompany.oauth2.constant.GrantTypeEnum; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +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.Map; + +public class EmailTokenGranter extends AbstractTokenGranter { + + private final AuthenticationManager authenticationManager; + + public EmailTokenGranter(AuthenticationManager authenticationManager, + AuthorizationServerTokenServices tokenServices, + ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory) { + super(tokenServices, clientDetailsService, requestFactory, GrantTypeEnum.EMAIL.getValue()); + this.authenticationManager = authenticationManager; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + Map parameters = tokenRequest.getRequestParameters(); + String email = parameters.get(EmailAuthenticationToken.EMAIL); + String code = parameters.get(EmailAuthenticationToken.CODE); + EmailAuthenticationToken token = new EmailAuthenticationToken(email, code); + token.setDetails(parameters); + Authentication authentication = authenticationManager.authenticate(token); + if (authentication == null || !authentication.isAuthenticated()) { + throw new InvalidGrantException("Could not authenticate user: " + email); + } + OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOAuth2Request, authentication); + } +} diff --git a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/ticket/TicketServices.java b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/ticket/TicketServices.java index c170ecd97..870a3035c 100644 --- a/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/ticket/TicketServices.java +++ b/accompany-oauth2/accompany-oauth2-service/src/main/java/com/accompany/oauth2/ticket/TicketServices.java @@ -1,5 +1,6 @@ package com.accompany.oauth2.ticket; +import cn.hutool.core.util.StrUtil; import com.accompany.common.device.DeviceInfo; import com.accompany.core.model.Account; import com.accompany.core.model.AccountLoginRecord; @@ -106,7 +107,7 @@ public class TicketServices implements InitializingBean { Account account = accountDetails.getAccount(); Long uid = account.getUid(); Users users = usersBaseService.getUsersByUid(uid); - Long blockEndTime = accountBlockCheckService.checkReturnEndTime(account.getErbanNo(), account.getPhone(), "", ""); + Long blockEndTime = accountBlockCheckService.checkReturnEndTime(account.getErbanNo(), account.getPhone(), account.getEmail(), StrUtil.EMPTY, StrUtil.EMPTY); //检查账号、设备号、号段是否封禁 if (null != blockEndTime){ CustomOAuth2Exception exception = new CustomOAuth2Exception(CustomOAuth2Exception.ACCOUNT_ERROR, ""); diff --git a/accompany-oauth2/accompany-oauth2-web/src/main/java/com/accompany/oauth2/config/AuthorizationServerConfig.java b/accompany-oauth2/accompany-oauth2-web/src/main/java/com/accompany/oauth2/config/AuthorizationServerConfig.java index 0fa6e6608..b73d809b5 100644 --- a/accompany-oauth2/accompany-oauth2-web/src/main/java/com/accompany/oauth2/config/AuthorizationServerConfig.java +++ b/accompany-oauth2/accompany-oauth2-web/src/main/java/com/accompany/oauth2/config/AuthorizationServerConfig.java @@ -5,6 +5,7 @@ import com.accompany.oauth2.constant.GrantTypeEnum; import com.accompany.oauth2.exception.CustomOAuth2WebResponseExceptionTranslator; import com.accompany.oauth2.jwt.JwtTicketConverter; import com.accompany.oauth2.jwt.JwtTokenConverter; +import com.accompany.oauth2.support.email.EmailTokenGranter; import com.accompany.oauth2.support.password.PasswordTokenGranter; import com.accompany.oauth2.support.verify.VerifyCodeTokenGranter; import com.accompany.oauth2.ticket.RedisTicketStore; @@ -120,6 +121,7 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap if (authenticationManager != null) { tokenGranters.add(new PasswordTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); tokenGranters.add(new VerifyCodeTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); + tokenGranters.add(new EmailTokenGranter(authenticationManager, tokenServices, clientDetails, requestFactory)); } return new CompositeTokenGranter(tokenGranters); }