邮箱-登录

This commit is contained in:
khalil
2025-03-14 11:28:22 +08:00
parent 6a2e90246b
commit 4d3b7c8f78
12 changed files with 192 additions and 18 deletions

View File

@@ -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);
}
/**
* 查询设备是否被封禁

View File

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

View File

@@ -17,6 +17,8 @@ public enum GrantTypeEnum {
APPLE("apple"),
VERIFY_CODE("verify_code"),
EMAIL("email"),
;
private final String value;

View File

@@ -53,6 +53,11 @@ public enum LoginTypeEnum {
*/
FACEBOOK((byte) 10),
/**
* 邮箱登录
* */
EMAIL((byte) 11),
;
private final byte value;

View File

@@ -33,6 +33,11 @@
<artifactId>accompany-sms-service</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-email-service</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-mq-service</artifactId>

View File

@@ -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;

View File

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

View File

@@ -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<String, ?> params = (Map<String, ?>) 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);
}
}

View File

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

View File

@@ -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<String, String> 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);
}
}

View File

@@ -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, "");

View File

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