Commit fb5bc2c0 authored by 李健华's avatar 李健华

极验验证落库

parent b975ed2f
...@@ -216,6 +216,11 @@ public interface Constants { ...@@ -216,6 +216,11 @@ public interface Constants {
String FN_GEETEST_SECCODE = "geetest_seccode"; String FN_GEETEST_SECCODE = "geetest_seccode";
String QG_CAPTCHA_ID = "captchaId"; String QG_CAPTCHA_ID = "captchaId";
String QG_CAPTCHA_VALUE = "captchaValue"; String QG_CAPTCHA_VALUE = "captchaValue";
String QG_CAPTCHA_INITDT = "initialize_dt";
String QG_CAPTCHA_REGISTERDT = "register_dt";
String QG_CAPTCHA_REGISTERREDT = "register_re_dt";
String QG_CAPTCHA_GEETESTLOGID = "geetestlog_id";
String QG_CAPTCHA_SETTINGTYPE= "setting_type";
/** /**
* 客户端类型参数名 * 客户端类型参数名
*/ */
......
...@@ -3,7 +3,9 @@ package cn.quantgroup.xyqb.aspect.captcha; ...@@ -3,7 +3,9 @@ package cn.quantgroup.xyqb.aspect.captcha;
import cn.quantgroup.xyqb.Constants; import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.ClientType; import cn.quantgroup.xyqb.model.ClientType;
import cn.quantgroup.xyqb.model.JsonResult; import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.SettingType;
import cn.quantgroup.xyqb.service.captcha.IGeetestCaptchaService; import cn.quantgroup.xyqb.service.captcha.IGeetestCaptchaService;
import cn.quantgroup.xyqb.service.captcha.IGeetestLogService;
import cn.quantgroup.xyqb.service.captcha.IQuantgroupCaptchaService; import cn.quantgroup.xyqb.service.captcha.IQuantgroupCaptchaService;
import cn.quantgroup.xyqb.util.IpUtil; import cn.quantgroup.xyqb.util.IpUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -18,6 +20,8 @@ import org.springframework.web.context.request.ServletRequestAttributes; ...@@ -18,6 +20,8 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
...@@ -34,6 +38,9 @@ public class CaptchaNewValidateAdvisor { ...@@ -34,6 +38,9 @@ public class CaptchaNewValidateAdvisor {
@Resource @Resource
private IQuantgroupCaptchaService quantgroupCaptchaService; private IQuantgroupCaptchaService quantgroupCaptchaService;
@Resource
private IGeetestLogService geetestLogService;
/** /**
* 图形验证码切面 * 图形验证码切面
*/ */
...@@ -84,8 +91,22 @@ public class CaptchaNewValidateAdvisor { ...@@ -84,8 +91,22 @@ public class CaptchaNewValidateAdvisor {
String challenge = request.getParameter(Constants.FN_GEETEST_CHALLENGE); String challenge = request.getParameter(Constants.FN_GEETEST_CHALLENGE);
String validate = request.getParameter(Constants.FN_GEETEST_VALIDATE); String validate = request.getParameter(Constants.FN_GEETEST_VALIDATE);
String seccode = request.getParameter(Constants.FN_GEETEST_SECCODE); String seccode = request.getParameter(Constants.FN_GEETEST_SECCODE);
String geetestLogId = request.getParameter(Constants.QG_CAPTCHA_GEETESTLOGID);
String settingType = request.getParameter(Constants.QG_CAPTCHA_SETTINGTYPE);
log.info("Geetest - 极验二次校验, phoneNo:{}, uniqueKey:{}, clientType:{}, ip:{}, challenge:{}, validate:{}, seccode:{}", phoneNo, uniqueKey, clientType, IpUtil.getRemoteIP(request), challenge, validate, seccode); log.info("Geetest - 极验二次校验, phoneNo:{}, uniqueKey:{}, clientType:{}, ip:{}, challenge:{}, validate:{}, seccode:{}", phoneNo, uniqueKey, clientType, IpUtil.getRemoteIP(request), challenge, validate, seccode);
return geetestCaptchaService.validGeetestCaptcha(uniqueKey, IpUtil.getRemoteIP(request), ClientType.valueByName(clientType), challenge, validate, seccode); Map<String, Object> result = new HashMap<>();
if (settingType.equals(SettingType.PASSWD.getAlias())) {
result = geetestCaptchaService.validGeetestCaptchaPasswd(uniqueKey, IpUtil.getRemoteIP(request), ClientType.valueByName(clientType), challenge, validate, seccode);
} else {
result = geetestCaptchaService.validGeetestCaptcha(uniqueKey, IpUtil.getRemoteIP(request), ClientType.valueByName(clientType), challenge, validate, seccode);
}
if (geetestLogId != null) {
String initializeDt = request.getParameter(Constants.QG_CAPTCHA_INITDT);
String registerDt = request.getParameter(Constants.QG_CAPTCHA_REGISTERDT);
String registerReDt = request.getParameter(Constants.QG_CAPTCHA_REGISTERREDT);
geetestLogService.updateGeetestLog(geetestLogId, initializeDt, registerDt, registerReDt, result);
}
return (boolean) result.get("valid");
} }
/** /**
......
package cn.quantgroup.xyqb.controller.external; package cn.quantgroup.xyqb.controller.external;
import cn.quantgroup.security.AESEncryption;
import cn.quantgroup.xyqb.Constants; import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.accessable.IpValidator; import cn.quantgroup.xyqb.aspect.accessable.IpValidator;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaFiniteValidator; import cn.quantgroup.xyqb.aspect.captcha.CaptchaFiniteValidator;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaNewValidator;
import cn.quantgroup.xyqb.aspect.captcha.LoginInterceptor; import cn.quantgroup.xyqb.aspect.captcha.LoginInterceptor;
import cn.quantgroup.xyqb.aspect.limit.PasswordFreeAccessValidator; import cn.quantgroup.xyqb.aspect.limit.PasswordFreeAccessValidator;
import cn.quantgroup.xyqb.aspect.lock.PasswordErrorFiniteValidator; import cn.quantgroup.xyqb.aspect.lock.PasswordErrorFiniteValidator;
...@@ -14,7 +14,7 @@ import cn.quantgroup.xyqb.exception.VerificationCodeErrorException; ...@@ -14,7 +14,7 @@ import cn.quantgroup.xyqb.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.model.*; import cn.quantgroup.xyqb.model.*;
import cn.quantgroup.xyqb.model.session.SessionStruct; import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.model.session.SessionValue; import cn.quantgroup.xyqb.model.session.SessionValue;
import cn.quantgroup.xyqb.repository.IUserRepository; import cn.quantgroup.xyqb.service.captcha.IGeetestLogService;
import cn.quantgroup.xyqb.service.http.IHttpService; import cn.quantgroup.xyqb.service.http.IHttpService;
import cn.quantgroup.xyqb.service.merchant.IMerchantService; import cn.quantgroup.xyqb.service.merchant.IMerchantService;
import cn.quantgroup.xyqb.service.register.IUserRegisterService; import cn.quantgroup.xyqb.service.register.IUserRegisterService;
...@@ -23,7 +23,10 @@ import cn.quantgroup.xyqb.service.sms.ISmsService; ...@@ -23,7 +23,10 @@ import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.service.user.*; import cn.quantgroup.xyqb.service.user.*;
import cn.quantgroup.xyqb.service.wechat.IWechatService; import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder; import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.*; import cn.quantgroup.xyqb.util.IpUtil;
import cn.quantgroup.xyqb.util.PasswordUtil;
import cn.quantgroup.xyqb.util.TenantUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
...@@ -95,6 +98,9 @@ public class UserController implements IBaseController { ...@@ -95,6 +98,9 @@ public class UserController implements IBaseController {
@Autowired @Autowired
private ILoginRecordService loginRecordService; private ILoginRecordService loginRecordService;
@Autowired
private IGeetestLogService geetestLogService;
/** /**
* 登录(账号 + 密码) * 登录(账号 + 密码)
...@@ -122,7 +128,39 @@ public class UserController implements IBaseController { ...@@ -122,7 +128,39 @@ public class UserController implements IBaseController {
@RequestParam(required = false) String dimension, @RequestParam(required = false) String dimension,
HttpServletRequest request) { HttpServletRequest request) {
log.info("loginV1 -> channelId:{},appChennel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension); log.info("loginV1 -> channelId:{},appChennel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension);
return login(channelId, appChannel, createdFrom, userId, key, dimension, request); return login(channelId, appChannel, createdFrom, userId, key, dimension, null, request);
}
/**
* 登录(账号 + 密码)
* 密码错误达到限定次数时执行图形验证码校验
* 图形验证码累计错误达到限定次数时须重新获取
* 加入极验验证码并落库
*
* @param channelId
* @param appChannel
* @param createdFrom
* @param userId
* @param key
* @param request
* @param dimension
* @return
* @yapi unknown
*/
@LoginInterceptor
@CaptchaNewValidator
@RequestMapping("/loginV2")
public JsonResult loginV2(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
@RequestParam(required = false, defaultValue = "1") Long createdFrom,
@RequestParam(required = false, defaultValue = "") String userId,
@RequestParam(required = false, defaultValue = "xyqb") String key,
@RequestParam(required = false) String dimension,
@RequestParam Long geetestlog_id,
HttpServletRequest request) {
log.info("loginV2 -> channelId:{},appChennel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension);
return login(channelId, appChannel, createdFrom, userId, key, dimension, geetestlog_id, request);
} }
...@@ -139,6 +177,7 @@ public class UserController implements IBaseController { ...@@ -139,6 +177,7 @@ public class UserController implements IBaseController {
@RequestParam(required = false, defaultValue = "") String userId, @RequestParam(required = false, defaultValue = "") String userId,
@RequestParam(required = false, defaultValue = "xyqb") String key, @RequestParam(required = false, defaultValue = "xyqb") String key,
@RequestParam(required = false) String dimension, @RequestParam(required = false) String dimension,
@RequestParam Long geetestLogId,
HttpServletRequest request) { HttpServletRequest request) {
log.info("login -> channelId:{},appChannel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension); log.info("login -> channelId:{},appChannel:{},createdFrom:{},userId:{},key:{},dimension:{}", channelId, appChannel, createdFrom, userId, key, dimension);
Merchant merchant = merchantService.findMerchantByName(key); Merchant merchant = merchantService.findMerchantByName(key);
...@@ -146,9 +185,9 @@ public class UserController implements IBaseController { ...@@ -146,9 +185,9 @@ public class UserController implements IBaseController {
return JsonResult.buildErrorStateResult("未知的连接", null); return JsonResult.buildErrorStateResult("未知的连接", null);
} }
if (StringUtils.length(userId) > Constants.UUID_MIN_LENGTH) { if (StringUtils.length(userId) > Constants.UUID_MIN_LENGTH) {
return loginWithUserId(channelId, appChannel, createdFrom, userId, merchant, dimension, request); return loginWithUserId(channelId, appChannel, createdFrom, userId, merchant, dimension, geetestLogId, request);
} else { } else {
return loginWithHttpBasic(channelId, appChannel, createdFrom, merchant, dimension, request); return loginWithHttpBasic(channelId, appChannel, createdFrom, merchant, dimension, geetestLogId, request);
} }
} }
...@@ -178,7 +217,7 @@ public class UserController implements IBaseController { ...@@ -178,7 +217,7 @@ public class UserController implements IBaseController {
@RequestParam(required = false) Integer tenantId, @RequestParam(required = false) Integer tenantId,
HttpServletRequest request) { HttpServletRequest request) {
log.info("login/fastV1 -> channelId:{},ZappChennel:{},createdFrom:{},key:{},btRegisterChannelId:{},dimension:{},clickId:{}", channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, clickId); log.info("login/fastV1 -> channelId:{},ZappChennel:{},createdFrom:{},key:{},btRegisterChannelId:{},dimension:{},clickId:{}", channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, clickId);
return loginFast(channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, clickId, tenantId, request); return loginFast(channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, clickId, tenantId, null, request);
} }
/** /**
...@@ -196,6 +235,7 @@ public class UserController implements IBaseController { ...@@ -196,6 +235,7 @@ public class UserController implements IBaseController {
@RequestParam(required = false) String dimension, @RequestParam(required = false) String dimension,
@RequestParam(name = "click_id", required = false) String clickId, @RequestParam(name = "click_id", required = false) String clickId,
@RequestParam(required = false) Integer tenantId, @RequestParam(required = false) Integer tenantId,
@RequestParam(required = false) Long geetestLogId,
HttpServletRequest request) { HttpServletRequest request) {
Map<String, JsonResult> validMap = getHeaderParam(request); Map<String, JsonResult> validMap = getHeaderParam(request);
log.info("login/fast -> channelId:{},appChannel:{},createdFrom:{},btRegisterChannelId:{},key:{},dimension:{},clickId:{}", channelId, appChannel, createdFrom, btRegisterChannelId, key, dimension, clickId); log.info("login/fast -> channelId:{},appChannel:{},createdFrom:{},btRegisterChannelId:{},key:{},dimension:{},clickId:{}", channelId, appChannel, createdFrom, btRegisterChannelId, key, dimension, clickId);
...@@ -220,7 +260,7 @@ public class UserController implements IBaseController { ...@@ -220,7 +260,7 @@ public class UserController implements IBaseController {
if (TenantUtil.validationTenantIdIsNullOrZero(tenantId)) { if (TenantUtil.validationTenantIdIsNullOrZero(tenantId)) {
tenantId = TenantUtil.TENANT_DEFAULT; tenantId = TenantUtil.TENANT_DEFAULT;
} }
return userService.loginFast(channelId, appChannel, createdFrom, btRegisterChannelId, dimension, clickId, request, merchant, phoneNo, tenantId); return userService.loginFast(channelId, appChannel, createdFrom, btRegisterChannelId, dimension, clickId, request, merchant, phoneNo, tenantId, geetestLogId);
} }
/** /**
...@@ -650,7 +690,7 @@ public class UserController implements IBaseController { ...@@ -650,7 +690,7 @@ public class UserController implements IBaseController {
return JsonResult.buildSuccessResult("登出成功"); return JsonResult.buildSuccessResult("登出成功");
} }
private JsonResult loginWithHttpBasic(Long channelId, String appChannel, Long createdFrom, Merchant merchant, String dimension, HttpServletRequest request) { private JsonResult loginWithHttpBasic(Long channelId, String appChannel, Long createdFrom, Merchant merchant, String dimension, Long geetestLogId, HttpServletRequest request) {
User user = verificateUserNameAndPassword(request); User user = verificateUserNameAndPassword(request);
if (user == null) { if (user == null) {
return JsonResult.buildErrorStateResult("用户名或密码不正确", null); return JsonResult.buildErrorStateResult("用户名或密码不正确", null);
...@@ -662,6 +702,10 @@ public class UserController implements IBaseController { ...@@ -662,6 +702,10 @@ public class UserController implements IBaseController {
if(authBean!=null){ if(authBean!=null){
authBean.setRegister(false); authBean.setRegister(false);
} }
if (geetestLogId != null) {
geetestLogService.updateByUidGeetestLog(geetestLogId, user.getId());
}
return new JsonResult(authBean); return new JsonResult(authBean);
} }
...@@ -713,7 +757,7 @@ public class UserController implements IBaseController { ...@@ -713,7 +757,7 @@ public class UserController implements IBaseController {
return user; return user;
} }
private JsonResult loginWithUserId(Long channelId, String appChannel, Long createdFrom, String userId, Merchant merchant, String dimension, HttpServletRequest request) { private JsonResult loginWithUserId(Long channelId, String appChannel, Long createdFrom, String userId, Merchant merchant, String dimension, Long geetestLogId, HttpServletRequest request) {
//查询用户 //查询用户
User user = userService.findByUuidInDb(userId); User user = userService.findByUuidInDb(userId);
if (Objects.isNull(user) || !user.getEnable()) { if (Objects.isNull(user) || !user.getEnable()) {
...@@ -725,6 +769,11 @@ public class UserController implements IBaseController { ...@@ -725,6 +769,11 @@ public class UserController implements IBaseController {
LoginProperties loginProperties = new LoginProperties("", 4, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName(), null); LoginProperties loginProperties = new LoginProperties("", 4, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName(), null);
//尝试解锁 //尝试解锁
lockIpv4Service.unLockPhone(user.getEncryptedPhoneNo()); lockIpv4Service.unLockPhone(user.getEncryptedPhoneNo());
// 更新极验用户userID
if (geetestLogId != null) {
geetestLogService.updateByUidGeetestLog(geetestLogId, user.getId());
}
//更新session //更新session
return new JsonResult(sessionService.createSession(user, loginProperties)); return new JsonResult(sessionService.createSession(user, loginProperties));
} }
......
...@@ -4,6 +4,7 @@ import cn.quantgroup.xyqb.Constants; ...@@ -4,6 +4,7 @@ import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.ClientType; import cn.quantgroup.xyqb.model.ClientType;
import cn.quantgroup.xyqb.model.JsonResult; import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.service.captcha.IGeetestCaptchaService; import cn.quantgroup.xyqb.service.captcha.IGeetestCaptchaService;
import cn.quantgroup.xyqb.service.captcha.IGeetestLogService;
import cn.quantgroup.xyqb.service.captcha.IQuantgroupCaptchaService; import cn.quantgroup.xyqb.service.captcha.IQuantgroupCaptchaService;
import cn.quantgroup.xyqb.util.IpUtil; import cn.quantgroup.xyqb.util.IpUtil;
import cn.quantgroup.xyqb.util.PasswordUtil; import cn.quantgroup.xyqb.util.PasswordUtil;
...@@ -37,10 +38,14 @@ public class NewCaptchaController { ...@@ -37,10 +38,14 @@ public class NewCaptchaController {
@Value("${geetest.close:false}") @Value("${geetest.close:false}")
private String geetestClose; private String geetestClose;
@Resource
private IGeetestLogService geetestLogService;
@RequestMapping(value = "/captcha/new") @RequestMapping(value = "/captcha/new")
@ApiOperation(value = "获取新图形验证码", notes = "获取新图形验证码", httpMethod = "POST") @ApiOperation(value = "获取新图形验证码", notes = "获取新图形验证码", httpMethod = "POST")
public JsonResult getCaptcha(String phoneNo, String clientType, HttpServletRequest request) { public JsonResult getCaptcha(String phoneNo, String clientType, String settingType, HttpServletRequest request) {
String remoteIp = IpUtil.getRemoteIP(request); String remoteIp = IpUtil.getRemoteIP(request);
log.info("获取验证码, phoneNo:{}, clientType:{}, ip:{}, verifyType-qg:{}", phoneNo, clientType, remoteIp, geetestClose); log.info("获取验证码, phoneNo:{}, clientType:{}, ip:{}, verifyType-qg:{}", phoneNo, clientType, remoteIp, geetestClose);
if (StringUtils.isNotBlank(phoneNo) && !ValidationUtil.validatePhoneNo(phoneNo)) { if (StringUtils.isNotBlank(phoneNo) && !ValidationUtil.validatePhoneNo(phoneNo)) {
...@@ -55,9 +60,15 @@ public class NewCaptchaController { ...@@ -55,9 +60,15 @@ public class NewCaptchaController {
Map<String, String> data = new HashMap<>(); Map<String, String> data = new HashMap<>();
Map<String, String> imgMap = null; Map<String, String> imgMap = null;
// 优先获取极验 // 优先获取极验
Long geetestLogId = 0L;
if (!Boolean.valueOf(geetestClose)) { if (!Boolean.valueOf(geetestClose)) {
imgMap = geetestCaptchaService.fetchGeetestCaptcha(keyMd5, remoteIp, ClientType.valueByName(clientType)); imgMap = geetestCaptchaService.fetchGeetestCaptcha(keyMd5, remoteIp, ClientType.valueByName(clientType));
data.put(Constants.VERIFY_PARAM, Constants.VERIFY_TYPE_GT); data.put(Constants.VERIFY_PARAM, Constants.VERIFY_TYPE_GT);
// 存储极验日志,phone_no, datasource, setting, challenge, captchaid
if (settingType != null) {
geetestLogId = geetestLogService.saveGeetestLog(phoneNo, clientType, settingType, imgMap);
}
} }
// 备选方案:量化派图形验证码 // 备选方案:量化派图形验证码
if (Objects.isNull(imgMap) || imgMap.isEmpty()) { if (Objects.isNull(imgMap) || imgMap.isEmpty()) {
...@@ -71,6 +82,41 @@ public class NewCaptchaController { ...@@ -71,6 +82,41 @@ public class NewCaptchaController {
// 填充数据并返回 // 填充数据并返回
data.putAll(imgMap); data.putAll(imgMap);
data.put(Constants.GT_UNIQUE_KEY, keyMd5); data.put(Constants.GT_UNIQUE_KEY, keyMd5);
data.put("geetestLogId", geetestLogId.toString());
return JsonResult.buildSuccessResult("", data);
}
@RequestMapping(value = "/captcha/new/passwd")
@ApiOperation(value = "获取新图形验证码(账密)", notes = "获取新图形验证码(账密)", httpMethod = "POST")
public JsonResult getCaptchaPasswd(String phoneNo, String clientType, String settingType, HttpServletRequest request) {
String remoteIp = IpUtil.getRemoteIP(request);
log.info("获取验证码, phoneNo:{}, clientType:{}, ip:{}, verifyType-qg:{}", phoneNo, clientType, remoteIp, geetestClose);
if (StringUtils.isNotBlank(phoneNo) && !ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号格式错误", null);
}
// 唯一key,用于初始化极验
String key = StringUtils.isNotBlank(phoneNo) ? phoneNo.trim() : UUID.randomUUID().toString();
// key指纹
String keyMd5 = PasswordUtil.MD5(key);
log.info("获取验证码, phoneNo:{}, keyMd5:{}, clientType:{}, ip:{}, verifyType-qg:{}", phoneNo, keyMd5, clientType, remoteIp, geetestClose);
// 数据容器
Map<String, String> data = new HashMap<>();
Map<String, String> imgMap = null;
// 优先获取极验
Long geetestLogId = 0L;
imgMap = geetestCaptchaService.fetchGeetestCaptchaPasswd(keyMd5, remoteIp, ClientType.valueByName(clientType));
data.put(Constants.VERIFY_PARAM, Constants.VERIFY_TYPE_GT);
// 存储极验日志,phone_no, datasource, setting, challenge, captchaid
geetestLogId = geetestLogService.saveGeetestLog(phoneNo, clientType, settingType, imgMap);
// 返回结果
if (Objects.isNull(imgMap) || imgMap.isEmpty()) {
return JsonResult.buildErrorStateResult("获取验证码失败", "");
}
// 填充数据并返回
data.putAll(imgMap);
data.put(Constants.GT_UNIQUE_KEY, keyMd5);
data.put("geetestLogId", geetestLogId.toString());
return JsonResult.buildSuccessResult("", data); return JsonResult.buildSuccessResult("", data);
} }
......
...@@ -5,6 +5,7 @@ import cn.quantgroup.xyqb.service.captcha.IGeetestCaptchaService; ...@@ -5,6 +5,7 @@ import cn.quantgroup.xyqb.service.captcha.IGeetestCaptchaService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Map;
/** /**
* 极验验证码认证 * 极验验证码认证
...@@ -23,8 +24,9 @@ public class GeeVerifyStrategy implements IImageVerifyStrategy { ...@@ -23,8 +24,9 @@ public class GeeVerifyStrategy implements IImageVerifyStrategy {
@Override @Override
public boolean verify(ImageDTO image) { public boolean verify(ImageDTO image) {
//todo param valid //todo param valid
return geetestCaptchaService.validGeetestCaptcha(image.getUniqueKey(), image.getUserIp(), Map<String, Object> result = geetestCaptchaService.validGeetestCaptcha(image.getUniqueKey(), image.getUserIp(),
ClientType.valueByName(image.getClientType()), image.getChallenge(), ClientType.valueByName(image.getClientType()), image.getChallenge(),
image.getValidate(), image.getSeccode()); image.getValidate(), image.getSeccode());
return (Boolean) result.get("valid");
} }
} }
package cn.quantgroup.xyqb.entity;
import cn.quantgroup.xyqb.entity.converter.EncryptConverter;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
/**
* Created by 11 on 2016/12/30.
*/
@Data
@Entity
@Table(name = "geetest_log")
public class GeetestLogEntity extends BaseEntity implements Serializable {
private static final long serialVersionUID = -1L;
@Column(name = "phone_no")
@Convert(converter = EncryptConverter.class)
private String phoneNo;
@Column(name = "user_id")
private Long userId;
@Column(name = "data_source")
private Integer dataSource;
@Column(name = "setting")
private Integer setting;
@Column(name = "challenge")
private String challenge;
@Column(name = "captcha_id")
private String captchaId;
@Column(name = "initialize_dt")
private Date initializeDt;
@Column(name = "register_dt")
private Date registerDt;
@Column(name = "register_re_dt")
private Date registerReDt;
@Column(name = "validata_method")
private String validataMethod;
@Column(name = "validata_dt")
private Date validataDt;
@Column(name = "validata_re_dt")
private Date validataReDt;
@Column(name = "validata_result")
private String validataResult;
}
package cn.quantgroup.xyqb.model;
import java.util.Optional;
/**
* 验证场景类型
*
*/
public enum SettingType {
PASSWD("账密"), SMSLOGIN("短验");
SettingType(String alias) {
this.alias = alias;
}
private String alias;
public String getAlias() {
return this.alias;
}
public static SettingType valueByName(String name) {
name = Optional.ofNullable(name).orElse("").toLowerCase();
switch (name) {
case "短验":
return SMSLOGIN;
default:
return PASSWD;
}
}
}
package cn.quantgroup.xyqb.repository;
import cn.quantgroup.xyqb.entity.GeetestLogEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* Created by 11 on 2016/12/30.
*/
public interface IGeetestLogRepository extends JpaRepository<GeetestLogEntity, Long>, JpaSpecificationExecutor<GeetestLogEntity> {
}
...@@ -12,6 +12,7 @@ import java.io.InputStream; ...@@ -12,6 +12,7 @@ import java.io.InputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
...@@ -37,6 +38,11 @@ public class GeetestLib { ...@@ -37,6 +38,11 @@ public class GeetestLib {
*/ */
private String captchaId; private String captchaId;
/**
* 流水号
*/
private String challenge;
/** /**
* 私钥 * 私钥
*/ */
...@@ -99,6 +105,7 @@ public class GeetestLib { ...@@ -99,6 +105,7 @@ public class GeetestLib {
data.put("success", "1"); data.put("success", "1");
data.put("gt", this.captchaId); data.put("gt", this.captchaId);
data.put("challenge", challenge); data.put("challenge", challenge);
data.put("reChallenge", this.challenge);
return data; return data;
} }
...@@ -157,6 +164,7 @@ public class GeetestLib { ...@@ -157,6 +164,7 @@ public class GeetestLib {
gtlog("result:" + result_str); gtlog("result:" + result_str);
JSONObject jsonObject = new JSONObject(result_str); JSONObject jsonObject = new JSONObject(result_str);
String return_challenge = jsonObject.getString("challenge"); String return_challenge = jsonObject.getString("challenge");
this.challenge = return_challenge;
gtlog("return_challenge:" + return_challenge); gtlog("return_challenge:" + return_challenge);
...@@ -245,9 +253,11 @@ public class GeetestLib { ...@@ -245,9 +253,11 @@ public class GeetestLib {
* @param seccode * @param seccode
* @return 验证结果, 1表示验证成功0表示验证失败 * @return 验证结果, 1表示验证成功0表示验证失败
*/ */
public int enhencedValidateRequest(String challenge, String validate, String seccode, HashMap<String, String> data) { public Map<String, Object> enhencedValidateRequest(String challenge, String validate, String seccode, HashMap<String, String> data) {
Map<String, Object> val = new HashMap<>();
val.put("bl", 0);
if (!resquestIsLegal(challenge, validate, seccode)) { if (!resquestIsLegal(challenge, validate, seccode)) {
return 0; return val;
} }
gtlog("request legitimate"); gtlog("request legitimate");
String userId = data.get("user_id"); String userId = data.get("user_id");
...@@ -269,13 +279,16 @@ public class GeetestLib { ...@@ -269,13 +279,16 @@ public class GeetestLib {
String response = ""; String response = "";
try { try {
if (validate.length() <= 0) { if (validate.length() <= 0) {
return 0; return val;
} }
if (!checkResultByPrivate(challenge, validate)) { if (!checkResultByPrivate(challenge, validate)) {
return 0; return val;
} }
gtlog("checkResultByPrivate"); gtlog("checkResultByPrivate");
val.put("validataDt", new Date());
response = readContentFromPost(postUrl, param); response = readContentFromPost(postUrl, param);
val.put("validataReDt", new Date());
val.put("validataResult", response);
gtlog("response: " + response); gtlog("response: " + response);
} catch (Exception e) { } catch (Exception e) {
log.error("向gt-server进行二次验证", e); log.error("向gt-server进行二次验证", e);
...@@ -286,13 +299,14 @@ public class GeetestLib { ...@@ -286,13 +299,14 @@ public class GeetestLib {
return_seccode = return_map.getString("seccode"); return_seccode = return_map.getString("seccode");
gtlog("md5: " + Md5Util.build(return_seccode)); gtlog("md5: " + Md5Util.build(return_seccode));
if (return_seccode.equals(Md5Util.build(seccode))) { if (return_seccode.equals(Md5Util.build(seccode))) {
return 1; val.put("validataMethod", return_map.getString("validata_method"));
return val;
} else { } else {
return 0; return val;
} }
} catch (JSONException e) { } catch (JSONException e) {
gtlog("json load error"); gtlog("json load error");
return 0; return val;
} }
} }
......
...@@ -19,6 +19,7 @@ public interface IGeetestCaptchaService { ...@@ -19,6 +19,7 @@ public interface IGeetestCaptchaService {
*/ */
Map<String, String> fetchGeetestCaptcha(String markId, String remoteIp, ClientType clientType); Map<String, String> fetchGeetestCaptcha(String markId, String remoteIp, ClientType clientType);
Map<String, String> fetchGeetestCaptchaPasswd(String markId, String remoteIp, ClientType clientType);
/** /**
* 二次验证 * 二次验证
...@@ -31,6 +32,18 @@ public interface IGeetestCaptchaService { ...@@ -31,6 +32,18 @@ public interface IGeetestCaptchaService {
* @param seccode * @param seccode
* @return * @return
*/ */
boolean validGeetestCaptcha(String markId, String remoteIp, ClientType clientType, String challenge, String validate, String seccode); Map<String, Object> validGeetestCaptcha(String markId, String remoteIp, ClientType clientType, String challenge, String validate, String seccode);
/**
* 账密二次验证
* @param markId
* @param remoteIp
* @param clientType
* @param challenge
* @param validate
* @param seccode
* @return
*/
Map<String, Object> validGeetestCaptchaPasswd(String markId, String remoteIp, ClientType clientType, String challenge, String validate, String seccode);
} }
package cn.quantgroup.xyqb.service.captcha;
import java.util.Map;
/**
* @author xufei on 2018/1/30.
*/
public interface IGeetestLogService {
Long saveGeetestLog(String phoneNo, String clientType, String settingType, Map<String, String> imgMap);
void updateGeetestLog(String geetestLogId, String initializeDt, String registerDt, String registerReDt, Map<String, Object> result);
void updateByUidGeetestLog(Long geetestLogId, Long id);
}
...@@ -32,6 +32,13 @@ public class GeetestCaptchaServiceImpl implements IGeetestCaptchaService { ...@@ -32,6 +32,13 @@ public class GeetestCaptchaServiceImpl implements IGeetestCaptchaService {
@Value("${geetest.api.url}") @Value("${geetest.api.url}")
private String apiUrl; private String apiUrl;
@Value("${geetest.passwd.captcha.id}")
private String passwdCaptchaId;
@Value("${geetest.passwd.private.key}")
private String passwdPrivateKey;
@Override @Override
public Map<String, String> fetchGeetestCaptcha(String markId, String remoteIp, ClientType clientType) { public Map<String, String> fetchGeetestCaptcha(String markId, String remoteIp, ClientType clientType) {
HashMap<String, String> param = getParam(markId, remoteIp, clientType); HashMap<String, String> param = getParam(markId, remoteIp, clientType);
...@@ -39,16 +46,35 @@ public class GeetestCaptchaServiceImpl implements IGeetestCaptchaService { ...@@ -39,16 +46,35 @@ public class GeetestCaptchaServiceImpl implements IGeetestCaptchaService {
} }
@Override @Override
public boolean validGeetestCaptcha(String markId, String remoteIp, ClientType clientType, String challenge, String validate, String seccode) { public Map<String, String> fetchGeetestCaptchaPasswd(String markId, String remoteIp, ClientType clientType) {
HashMap<String, String> param = getParam(markId, remoteIp, clientType);
return getGeetestSdkPasswd().getResponseStr(param);
}
@Override
public Map<String, Object> validGeetestCaptcha(String markId, String remoteIp, ClientType clientType, String challenge, String validate, String seccode) {
HashMap<String, String> param = getParam(markId, remoteIp, clientType); HashMap<String, String> param = getParam(markId, remoteIp, clientType);
int gtResult = getGeetestSdk().enhencedValidateRequest(challenge, validate, seccode, param); Map<String, Object> validResult = getGeetestSdk().enhencedValidateRequest(challenge, validate, seccode, param);
return Constants.GT_CAPTCHA_VALIDATE_SUCCESS == gtResult; validResult.put("valid", Constants.GT_CAPTCHA_VALIDATE_SUCCESS == (Integer)validResult.get("bl"));
return validResult;
}
@Override
public Map<String, Object> validGeetestCaptchaPasswd(String markId, String remoteIp, ClientType clientType, String challenge, String validate, String seccode) {
HashMap<String, String> param = getParam(markId, remoteIp, clientType);
Map<String, Object> validResult = getGeetestSdkPasswd().enhencedValidateRequest(challenge, validate, seccode, param);
validResult.put("valid", Constants.GT_CAPTCHA_VALIDATE_SUCCESS == (Integer)validResult.get("bl"));
return validResult;
} }
private GeetestLib getGeetestSdk() { private GeetestLib getGeetestSdk() {
return new GeetestLib(captchaId, privateKey, newFailback, apiUrl); return new GeetestLib(captchaId, privateKey, newFailback, apiUrl);
} }
private GeetestLib getGeetestSdkPasswd() {
return new GeetestLib(passwdCaptchaId, passwdPrivateKey, newFailback, apiUrl);
}
private HashMap<String, String> getParam(String markId, String remoteIp, ClientType clientType) { private HashMap<String, String> getParam(String markId, String remoteIp, ClientType clientType) {
HashMap<String, String> param = new HashMap<>(); HashMap<String, String> param = new HashMap<>();
param.put("user_id", markId); param.put("user_id", markId);
......
package cn.quantgroup.xyqb.service.captcha.impl;
import cn.quantgroup.xyqb.entity.GeetestLogEntity;
import cn.quantgroup.xyqb.model.ClientType;
import cn.quantgroup.xyqb.model.SettingType;
import cn.quantgroup.xyqb.repository.IGeetestLogRepository;
import cn.quantgroup.xyqb.service.captcha.IGeetestLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
/**
* @author xufei on 2018/1/30.
*/
@Service
@Slf4j
public class GeetestLogServiceImpl implements IGeetestLogService {
@Resource
private IGeetestLogRepository geetestLogRepository;
@Override
public Long saveGeetestLog(String phoneNo, String clientType, String settingType, Map<String, String> imgMap) {
GeetestLogEntity geetestLogEntity = new GeetestLogEntity();
geetestLogEntity.setPhoneNo(phoneNo);
geetestLogEntity.setDataSource(ClientType.valueByName(clientType).ordinal());
geetestLogEntity.setSetting(SettingType.valueByName(settingType).ordinal());
geetestLogEntity.setChallenge(imgMap.get("reChallenge"));
geetestLogEntity.setCaptchaId(imgMap.get("gt"));
geetestLogEntity = geetestLogRepository.saveAndFlush(geetestLogEntity);
return geetestLogEntity.getId();
}
@Override
public void updateGeetestLog(String geetestLogId, String initializeDt, String registerDt, String registerReDt, Map<String, Object> result) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
GeetestLogEntity geetestLogEntity = geetestLogRepository.getOne(Long.parseLong(geetestLogId));
if (geetestLogEntity != null) {
if (StringUtils.isNotBlank(initializeDt) && StringUtils.isNotBlank(registerDt) && StringUtils.isNotBlank(registerReDt)) {
geetestLogEntity.setInitializeDt(simpleDateFormat.parse(initializeDt));
geetestLogEntity.setRegisterDt(simpleDateFormat.parse(registerDt));
geetestLogEntity.setRegisterReDt(simpleDateFormat.parse(registerReDt));
}
geetestLogEntity.setValidataDt((Date) result.get("validataDt"));
geetestLogEntity.setValidataReDt((Date) result.get("validataReDt"));
geetestLogEntity.setValidataResult(result.getOrDefault("validataResult", "").toString());
geetestLogEntity.setValidataMethod(result.getOrDefault("validataMethod", "").toString());
geetestLogRepository.save(geetestLogEntity);
}
} catch (Exception e) {
log.error("修改极验验证二次验证失败--{}", e.getMessage());
}
}
@Override
public void updateByUidGeetestLog(Long geetestLogId, Long id) {
try {
GeetestLogEntity geetestLogEntity = geetestLogRepository.getOne(geetestLogId);
geetestLogEntity.setUserId(id);
geetestLogRepository.save(geetestLogEntity);
} catch (Exception e) {
log.error("修改极验验证用户userId失败--{}", e.getMessage());
}
}
}
...@@ -81,7 +81,7 @@ public interface IUserService { ...@@ -81,7 +81,7 @@ public interface IUserService {
JsonResult loginFast(Long channelId, String appChannel, Long createdFrom, Long btRegisterChannelId, JsonResult loginFast(Long channelId, String appChannel, Long createdFrom, Long btRegisterChannelId,
String dimension, String clickId, HttpServletRequest request, Merchant merchant, String dimension, String clickId, HttpServletRequest request, Merchant merchant,
String phoneNo, Integer tenantId); String phoneNo, Integer tenantId, Long geetestLogId);
/** /**
* 查询用户全量信息 * 查询用户全量信息
......
...@@ -15,6 +15,7 @@ import cn.quantgroup.xyqb.exception.UserNotExistException; ...@@ -15,6 +15,7 @@ import cn.quantgroup.xyqb.exception.UserNotExistException;
import cn.quantgroup.xyqb.exception.UserRegisterLoginException; import cn.quantgroup.xyqb.exception.UserRegisterLoginException;
import cn.quantgroup.xyqb.model.*; import cn.quantgroup.xyqb.model.*;
import cn.quantgroup.xyqb.repository.*; import cn.quantgroup.xyqb.repository.*;
import cn.quantgroup.xyqb.service.captcha.IGeetestLogService;
import cn.quantgroup.xyqb.service.register.IUserDeregisterService; import cn.quantgroup.xyqb.service.register.IUserDeregisterService;
import cn.quantgroup.xyqb.service.register.IUserRegisterService; import cn.quantgroup.xyqb.service.register.IUserRegisterService;
import cn.quantgroup.xyqb.service.session.ISessionService; import cn.quantgroup.xyqb.service.session.ISessionService;
...@@ -109,6 +110,9 @@ public class UserServiceImpl implements IUserService, IBaseController { ...@@ -109,6 +110,9 @@ public class UserServiceImpl implements IUserService, IBaseController {
@Resource @Resource
private IProductLoginRepository productLoginRepository; private IProductLoginRepository productLoginRepository;
@Resource
private IGeetestLogService geetestLogService;
@Override @Override
// @Cacheable(value = "usercache", key = "'xyqbuser' + #phone", unless = "#result == null", cacheManager = "cacheManager") // @Cacheable(value = "usercache", key = "'xyqbuser' + #phone", unless = "#result == null", cacheManager = "cacheManager")
public User findByPhoneInDb(String phone) { public User findByPhoneInDb(String phone) {
...@@ -368,7 +372,7 @@ public class UserServiceImpl implements IUserService, IBaseController { ...@@ -368,7 +372,7 @@ public class UserServiceImpl implements IUserService, IBaseController {
@Override @Override
@RedisLock(prefix = "lock:login:fast:", key = "#this[8]") @RedisLock(prefix = "lock:login:fast:", key = "#this[8]")
public JsonResult loginFast(Long channelId, String appChannel, Long createdFrom, Long btRegisterChannelId, public JsonResult loginFast(Long channelId, String appChannel, Long createdFrom, Long btRegisterChannelId,
String dimension, String clickId, HttpServletRequest request, Merchant merchant, String phoneNo, Integer tenantId) { String dimension, String clickId, HttpServletRequest request, Merchant merchant, String phoneNo, Integer tenantId, Long geetestLogId) {
Boolean register = false; Boolean register = false;
User user = findByPhoneWithCache(phoneNo); User user = findByPhoneWithCache(phoneNo);
if (user != null && !user.getEnable()) { if (user != null && !user.getEnable()) {
...@@ -425,6 +429,10 @@ public class UserServiceImpl implements IUserService, IBaseController { ...@@ -425,6 +429,10 @@ public class UserServiceImpl implements IUserService, IBaseController {
} }
oauthLoginInfoService.addLoginInfo(user, tenantId); oauthLoginInfoService.addLoginInfo(user, tenantId);
// 更新极验用户userID
if (geetestLogId != null) {
geetestLogService.updateByUidGeetestLog(geetestLogId, user.getId());
}
LoginProperties loginProperties = new LoginProperties("", 3, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName(), tenantId); LoginProperties loginProperties = new LoginProperties("", 3, channelId, createdFrom, appChannel, merchant.getId(), merchant.getName(), tenantId);
AuthBean session = sessionService.createSession(user, loginProperties); AuthBean session = sessionService.createSession(user, loginProperties);
session.setRegister(register); session.setRegister(register);
......
...@@ -3,13 +3,12 @@ package login; ...@@ -3,13 +3,12 @@ package login;
import cn.quantgroup.tech.brave.service.ITechHttpClient; import cn.quantgroup.tech.brave.service.ITechHttpClient;
import cn.quantgroup.xyqb.Bootstrap; import cn.quantgroup.xyqb.Bootstrap;
import cn.quantgroup.xyqb.Constants; import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.controller.internal.user.InnerController;
import cn.quantgroup.xyqb.controller.external.UserController; import cn.quantgroup.xyqb.controller.external.UserController;
import cn.quantgroup.xyqb.controller.internal.user.InnerController;
import cn.quantgroup.xyqb.entity.Address; import cn.quantgroup.xyqb.entity.Address;
import cn.quantgroup.xyqb.model.AuthBean; import cn.quantgroup.xyqb.model.AuthBean;
import cn.quantgroup.xyqb.model.JsonResult; import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.service.user.IAddressService; import cn.quantgroup.xyqb.service.user.IAddressService;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
...@@ -93,7 +92,7 @@ public class UserLoginTest { ...@@ -93,7 +92,7 @@ public class UserLoginTest {
String ip = "172.16.0.1"; String ip = "172.16.0.1";
request.addHeader("x-original-client-ip", ip); request.addHeader("x-original-client-ip", ip);
request.addHeader("authorization", "Basic MTg1MTMzNDE4MDg6MTIzNDU2"); request.addHeader("authorization", "Basic MTg1MTMzNDE4MDg6MTIzNDU2");
JsonResult jsonResult = userController.login(channelId, appChannel, createFrom, userId, "xyqb", "xyqb", request); JsonResult jsonResult = userController.login(channelId, appChannel, createFrom, userId, "xyqb", "xyqb", null, request);
AuthBean authBean = (AuthBean) jsonResult.getData(); AuthBean authBean = (AuthBean) jsonResult.getData();
String token = authBean.getToken(); String token = authBean.getToken();
System.out.println("user token:" + token); System.out.println("user token:" + token);
......
...@@ -40,6 +40,7 @@ public class GeetestCaptchaServiceTest { ...@@ -40,6 +40,7 @@ public class GeetestCaptchaServiceTest {
String challenge = "86664ca9f3feba52c1d070343a9d10c6"; String challenge = "86664ca9f3feba52c1d070343a9d10c6";
String validate = "9b80dd76a43e2608e54da9b865733b8c"; String validate = "9b80dd76a43e2608e54da9b865733b8c";
String seccode = "9b80dd76a43e2608e54da9b865733b8c|jordan"; String seccode = "9b80dd76a43e2608e54da9b865733b8c|jordan";
Assert.assertTrue(geetestCaptchaService.validGeetestCaptcha(PasswordUtil.MD5(phoneNo), remoteIp, clientType, challenge, validate, seccode)); Map<String, Object> result = geetestCaptchaService.validGeetestCaptcha(PasswordUtil.MD5(phoneNo), remoteIp, clientType, challenge, validate, seccode);
Assert.assertTrue((Boolean)result.get("valid"));
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment