Commit 3ecec227 authored by 技术部-任文超's avatar 技术部-任文超

Merge branch '20190522-VerifyCode' into 'master'

20190522 verify code

VCC

See merge request !30
parents 84b61e36 a2473769
......@@ -12,6 +12,7 @@ import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
......@@ -123,7 +124,7 @@ public class RedisConfig {
final RedisTemplate<String, String> template = new RedisTemplate<>();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
template.setEnableTransactionSupport(true);
template.setEnableTransactionSupport(false);
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jdkSerializationRedisSerializer);
......
......@@ -56,10 +56,10 @@ public class AppController implements IBaseController {
* @param idNo - 用户身份证号
* @param name - 用户姓名
* @param key - Merchant表name属性
* @param btRegisterChannelId - 白条注册渠道id
* @param registerFrom - 注册渠道
* @param channelId -
* @param appChannel -
* @param btRegisterChannelId - 白条注册渠道ID
* @param registerFrom - 注册渠道(第一次用户来源channelId)
* @param channelId - 渠道ID
* @param appChannel - 应用平台(应用商店、AppStore...)
* @param request
* @return Token和phoneNo
*/
......@@ -105,11 +105,11 @@ public class AppController implements IBaseController {
* @param phoneNo - 手机号
* @param idNo - 用户身份证号
* @param name - 用户姓名
* @param key - Merchant表name属性
* @param key - merchant表的name属性(跳转相关)
* @param btRegisterChannelId - 白条注册渠道id
* @param registerFrom - 注册渠道
* @param channelId -
* @param appChannel -
* @param registerFrom - 注册渠道(第一次用户来源channelId)
* @param channelId - 渠道
* @param appChannel - 应用平台(应用商店、AppStore...)
* @param request
* @return 用户信息
*/
......@@ -151,6 +151,7 @@ public class AppController implements IBaseController {
AuthBean bean = sessionService.createSession(user, loginProperties);
LoginInfo loginInfo = new LoginInfo();
loginInfo.setUser(new UserRet(user));
loginInfo.setHasPassword(user.getHasPassword());
loginInfo.setToken(bean.getToken());
LoginInfo.LoginContext context = new LoginInfo.LoginContext();
context.setChannelId(channelId);
......@@ -166,10 +167,10 @@ public class AppController implements IBaseController {
* 第三方用户免密登录
* 发现新手机号不会执行注册
*
* @param phoneNo - 手机号
* @param registerFrom - 注册渠道
* @param channelId -
* @param appChannel -
* @param phoneNo - 手机号
* @param registerFrom - 注册渠道(第一次用户来源channelId)
* @param channelId - 渠道
* @param appChannel - 应用平台(应用商店、AppStore...)
* @param request
* @return Token和phoneNo
*/
......
......@@ -1363,6 +1363,9 @@ public class InnerController implements IBaseController {
@RequestMapping(path = "/user/password/reset", method = RequestMethod.POST)
public JsonResult resetPassword(@RequestParam("phone") String phone, @RequestParam(required = false) String password) {
if (ValidationUtil.validatePhoneNo(phone)) {
if (PasswordUtil.validPwd(password)) {
return JsonResult.buildErrorStateResult("密码应为6-12位", null);
}
try {
// 默认重置的密码是123456
password = StringUtils.isBlank(password) ? "123456" : password;
......
package cn.quantgroup.xyqb.controller.external.user;
import java.util.Objects;
import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.accessable.IpValidator;
import cn.quantgroup.xyqb.entity.User;
import cn.quantgroup.xyqb.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.model.session.SessionStruct;
import cn.quantgroup.xyqb.service.session.ISessionService;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import cn.quantgroup.xyqb.service.user.IUserService;
import cn.quantgroup.xyqb.session.XyqbSessionContextHolder;
import cn.quantgroup.xyqb.util.ValidationUtil;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Objects;
/**
* Created by FrankChow on 15/12/16.
......@@ -39,6 +37,8 @@ public class UserApiController {
@Resource
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> stringRedisTemplate;
@Resource
private ISmsService smsService;
@RequestMapping("/user/is_passwd_set")
public JsonResult isPasswordSet(String key, String phoneNo) {
......@@ -64,7 +64,6 @@ public class UserApiController {
* @return
*/
@ApiOperation(notes = "检查token是否有效,如果有效,可选择是否延续生命期(延续后有效期24Hour)", value = "Check token and then prolong session", nickname = "checkToken")
@IpValidator
@RequestMapping(value = "/valid/{token}", method = RequestMethod.POST)
public JsonResult checkToken(@ApiParam(value = "sid,session的id", required = true) @PathVariable("token") String token,
......@@ -102,4 +101,24 @@ public class UserApiController {
}
}
/**
* 校验短信验证码
*
* @param phoneNo
* @param verificationCode
*/
@IpValidator
@RequestMapping(value = "/verifyPhoneAndCode", method = RequestMethod.POST)
public JsonResult verifyPhoneAndCode(@RequestParam String phoneNo, @RequestParam String verificationCode) {
if (!smsService.verifyPhoneAndCode(phoneNo, verificationCode)) {
// 是否需要重新发送短信验证码
if (smsService.needResendCode(phoneNo)) {
throw new VerificationCodeErrorException("验证码失效,请重新获取");
}
log.info("验证码校验失败,phoneNo:{} , verificationCode:{}", phoneNo, verificationCode);
throw new VerificationCodeErrorException("短信验证码错误");
}
return JsonResult.buildSuccessResult(null);
}
}
......@@ -4,6 +4,7 @@ import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.accessable.IpValidator;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaFiniteValidator;
import cn.quantgroup.xyqb.aspect.limit.PasswordFreeAccessValidator;
import cn.quantgroup.xyqb.aspect.lock.PasswordErrorFiniteValidator;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.Merchant;
......@@ -27,6 +28,7 @@ import cn.quantgroup.xyqb.util.MqUtils;
import cn.quantgroup.xyqb.util.PasswordUtil;
import cn.quantgroup.xyqb.util.ValidationUtil;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
......@@ -34,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
......@@ -260,7 +263,7 @@ public class UserController implements IBaseController {
log.info("用户注册失败,密码不能为空:register -> registerFrom:{}, phoneNo:{}, password:{}", registerFrom, phoneNo, password);
return JsonResult.buildErrorStateResult("密码不能为空", null);
}
if (password.length() < 6 || password.length() > 12) {
if (PasswordUtil.validPwd(password)) {
log.info("用户注册失败,密码长度须在6位至12位之间:register -> registerFrom:{}, phoneNo:{}, password:{}", registerFrom, phoneNo, password);
return JsonResult.buildErrorStateResult("密码应为6-12位", null);
}
......@@ -330,10 +333,10 @@ public class UserController implements IBaseController {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号错误", null);
}
if (StringUtils.isEmpty(password)) {
if (StringUtils.isBlank(password)) {
return JsonResult.buildErrorStateResult("密码不能为空", null);
}
if (password.length() < 6 || password.length() > 12) {
if (PasswordUtil.validPwd(password)) {
return JsonResult.buildErrorStateResult("密码应为6-12位", null);
}
verifyPhoneAndCode(phoneNo, verificationCode);
......@@ -350,6 +353,43 @@ public class UserController implements IBaseController {
return JsonResult.buildSuccessResult(null, null);
}
/**
* 重置密码
*/
@PasswordFreeAccessValidator
@RequestMapping(path = "/resetPassword", method = RequestMethod.POST)
public JsonResult resetPassword(@RequestParam String phoneNo, @RequestParam String password, @RequestParam(required = false) String passwordNew) {
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
return JsonResult.buildErrorStateResult("手机号错误", null);
}
if (StringUtils.isBlank(passwordNew)) {
return JsonResult.buildErrorStateResult("密码不能为空", null);
}
if (PasswordUtil.validPwd(password)) {
return JsonResult.buildErrorStateResult("密码应为6-12位", null);
}
User user = userService.findByPhoneWithCache(phoneNo);
if (Objects.isNull(user)) {
log.info("修改密码失败,该手机号尚未注册, phoneNo:{}", phoneNo);
return JsonResult.buildErrorStateResult("修改密码失败", null);
}
if (!user.getEnable()) {
log.info("修改密码失败,该用户已禁用, phoneNo:{}", phoneNo);
return JsonResult.buildErrorStateResult("修改密码失败", null);
}
// 验证密码:原密码不存在时,必须为空
if (StringUtils.isBlank(user.getPassword()) ^ StringUtils.isBlank(password)) {
return JsonResult.buildErrorStateResult("修改密码失败", null);
}
if (StringUtils.isNotBlank(user.getPassword()) && !validatePassword(password, user.getPassword())) {
return JsonResult.buildErrorStateResult("修改密码失败", null);
}
if (!userService.resetPassword(phoneNo, passwordNew)) {
return JsonResult.buildErrorStateResult("修改密码失败", null);
}
return JsonResult.buildSuccessResult("修改密码成功");
}
/**
* 检查token是否已经过期不存在了
......@@ -440,8 +480,19 @@ public class UserController implements IBaseController {
return user;
}
/**
* 账密登陆
*
* @param paramPass 不允许空密码
* @param targetPassword
* @return
*/
private boolean validatePassword(String paramPass, String targetPassword) {
return StringUtils.defaultString(targetPassword, "").equals(PasswordUtil.MD5(paramPass.toLowerCase() + Constants.PASSWORD_SALT));
if(StringUtils.isBlank(paramPass)){
return false;
}
String thePassword = PasswordUtil.MD5(paramPass.toLowerCase() + Constants.PASSWORD_SALT);
return Objects.equals(thePassword, targetPassword);
}
private JsonResult loginWithUserId(Long channelId, String appChannel, Long createdFrom, String userId, Merchant merchant, String dimension, HttpServletRequest request) {
......@@ -494,10 +545,6 @@ public class UserController implements IBaseController {
* @param verificationCode
*/
private void verifyPhoneAndCode(String phoneNo, String verificationCode) {
// 非生产环境直接跳过验证码检验
if (!TechEnvironment.isPro()) {
return;
}
if (!smsService.verifyPhoneAndCode(phoneNo, verificationCode)) {
// 是否需要重新发送短信验证码
if (smsService.needResendCode(phoneNo)) {
......
......@@ -9,6 +9,7 @@ import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Objects;
/**
* Created by Miraculous on 15/7/4.
......@@ -55,5 +56,12 @@ public class User implements Serializable {
@JSONField(serializeUsing = Timestamp2LongConverter.class)
private Timestamp updatedAt;
/**
* 是否有密码
* @return
*/
public boolean getHasPassword() {
return Objects.nonNull(password) && !Objects.equals("", password);
}
}
......@@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.Setter;
import java.security.Principal;
import java.util.Objects;
/**
* Created by Miraculous on 15/7/9.
......@@ -15,6 +16,7 @@ public class AuthBean {
private String token;
private String phoneNo;
private String uuid;
private boolean hasPassword;
public AuthBean(String token, Principal user) {
this.token = token;
......
......@@ -36,6 +36,12 @@ public class UserRet implements Serializable {
//上一次修改时间
private Long updatedAt;
/**
* 是否有密码
* @return
*/
private boolean hasPassword;
public UserRet(User user) {
if(Objects.isNull(user)){
return;
......@@ -46,6 +52,7 @@ public class UserRet implements Serializable {
this.setPhoneNo(user.getPhoneNo());
this.setEnable(user.getEnable());
this.setPassword("");
this.hasPassword = Objects.nonNull(password) && !Objects.equals("", password);
this.setRegisteredFrom(user.getRegisteredFrom());
this.setUuid(user.getUuid());
this.setCreatedAt(createTimeStamp);
......
......@@ -12,6 +12,11 @@ public class LoginInfo {
private String token;
private UserRet user;
private LoginContext loginContext;
/**
* 是否有密码
* @return
*/
private boolean hasPassword;
@Data
public static class LoginContext {
......
......@@ -43,9 +43,14 @@ public class UserRegisterServiceImpl implements IUserRegisterService {
@Override
public User register(Long registerFrom, String phoneNo, String idNo, String name, Long channelId, Long btRegisterChannelId) {
UserRegisterParam userRegisterParam = UserRegisterParam.builder()
.registerFrom(registerFrom).phoneNo(phoneNo).idNo(idNo).name(name)
.channelId(channelId).btRegisterChannelId(btRegisterChannelId)
.generateRandomPwd(true).sendSuccessSms(true).sendAppSms(true)
.registerFrom(registerFrom)
.phoneNo(phoneNo)
.idNo(idNo)
.name(name)
.channelId(channelId)
.btRegisterChannelId(btRegisterChannelId)
.sendSuccessSms(true)
.sendAppSms(true)
.sendSuccessMq(true)
.build();
User user = saveUser(userRegisterParam);
......@@ -74,7 +79,9 @@ public class UserRegisterServiceImpl implements IUserRegisterService {
if (userRegisterParam.isGenerateRandomPwd()) {
password = PasswordUtil.generateRandomPwd(Constants.RANDOM_PWD_LEN);
}
user.setPassword(PasswordUtil.MD5(password.toLowerCase() + Constants.PASSWORD_SALT));
if (StringUtils.isNotBlank(password)) {
user.setPassword(PasswordUtil.MD5WithSalt(password));
}
Timestamp currentTime = new Timestamp(System.currentTimeMillis());
user.setUpdatedAt(currentTime);
user.setCreatedAt(currentTime);
......@@ -88,10 +95,13 @@ public class UserRegisterServiceImpl implements IUserRegisterService {
@Override
public boolean register(String phoneNo, String password, Long registerFrom, String ip, Long channelId, Long btRegisterChannelId, String dimension) {
UserRegisterParam userRegisterParam = UserRegisterParam.builder()
.registerFrom(registerFrom).phoneNo(phoneNo).password(password)
.channelId(channelId).btRegisterChannelId(btRegisterChannelId)
.registerFrom(registerFrom)
.phoneNo(phoneNo).password(password)
.channelId(channelId)
.btRegisterChannelId(btRegisterChannelId)
.dimension(dimension)
.generateRandomPwd(false).sendSuccessSms(true).sendAppSms(true)
.sendSuccessSms(true)
.sendAppSms(true)
.sendSuccessMq(true)
.build();
User user = saveUser(userRegisterParam);
......@@ -103,10 +113,13 @@ public class UserRegisterServiceImpl implements IUserRegisterService {
@Override
public User register(String phoneNo, Long channelId, Long registerFrom, String appChannel, Long btRegisterChannelId, String dimension) {
UserRegisterParam userRegisterParam = UserRegisterParam.builder()
.registerFrom(registerFrom).phoneNo(phoneNo)
.channelId(channelId).btRegisterChannelId(btRegisterChannelId)
.registerFrom(registerFrom)
.phoneNo(phoneNo)
.channelId(channelId)
.btRegisterChannelId(btRegisterChannelId)
.dimension(dimension)
.generateRandomPwd(true).sendSuccessSms(true).sendAppSms(true)
.sendSuccessSms(true)
.sendAppSms(true)
.sendSuccessMq(true)
.build();
User user = saveUser(userRegisterParam);
......@@ -118,12 +131,17 @@ public class UserRegisterServiceImpl implements IUserRegisterService {
@Override
public User register(Long registeredFrom, Long channelId, String phoneNo, String name, String idNo, Address addressObj, String contacts, List<Contact> contactList, Long btRegisterChannelId) {
UserRegisterParam userRegisterParam = UserRegisterParam.builder()
.registerFrom(registeredFrom).phoneNo(phoneNo).idNo(idNo).name(name)
.registerFrom(registeredFrom)
.phoneNo(phoneNo)
.idNo(idNo)
.name(name)
.channelId(channelId)
.btRegisterChannelId(btRegisterChannelId)
.address(addressObj).contacts(contacts)
.address(addressObj)
.contacts(contacts)
.contactList(contactList)
.generateRandomPwd(true).sendSuccessSms(true).sendAppSms(true)
.sendSuccessSms(true)
.sendAppSms(true)
.sendSuccessMq(true)
.build();
User user = saveUser(userRegisterParam);
......
......@@ -70,6 +70,7 @@ public class SessionServiceImpl implements ISessionService {
authBean.setPhoneNo(user.getPhoneNo());
authBean.setToken(sessionStruct.getSid());
authBean.setUuid(uuid);
authBean.setHasPassword(user.getHasPassword());
log.info("用户登录成功, loginFrom:{}, phoneNo:{},appChannel:{},channelId:{}", properties.getCreatedFrom(), user.getPhoneNo(), properties.getAppChannel(), properties.getChannelId());
return authBean;
}
......
......@@ -2,6 +2,7 @@ package cn.quantgroup.xyqb.service.sms.impl;
import cn.quantgroup.sms.MsgParams;
import cn.quantgroup.sms.SmsSender;
import cn.quantgroup.tech.util.TechEnvironment;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.service.sms.ISmsService;
import lombok.Synchronized;
......@@ -87,6 +88,10 @@ public class SmsServiceImpl implements ISmsService {
*/
@Override
public boolean verifyPhoneAndCode(String phoneNo, String verificationCode) {
// 非生产环境直接跳过验证码检验
if (!TechEnvironment.isPro()) {
return true;
}
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
String randomCode = stringRedisTemplate.opsForValue().get(key);
if (StringUtils.isBlank(randomCode)) {
......
......@@ -2,8 +2,10 @@ package cn.quantgroup.xyqb.util;
import cn.quantgroup.xyqb.Constants;
import com.google.common.base.Preconditions;
import org.apache.commons.lang3.StringUtils;
import java.security.MessageDigest;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
/**
......@@ -22,10 +24,16 @@ public class PasswordUtil {
};
public final static String MD5WithSalt(String s){
if(Objects.isNull(s)){
return null;
}
return MD5(s.toLowerCase() + Constants.PASSWORD_SALT);
}
public final static String MD5(String s) {
if(Objects.isNull(s)){
return null;
}
try {
byte[] strTemp = s.getBytes("utf-8");
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
......@@ -67,4 +75,14 @@ public class PasswordUtil {
return pwd.toString();
}
/**
* 校验密码是否合法
*
* @param password
* @return
*/
public static boolean validPwd(String password) {
return Objects.nonNull(password) && (password.length() < 6 || password.length() > 12);
}
}
......@@ -2,12 +2,14 @@ package common;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -18,6 +20,7 @@ import cn.quantgroup.xyqb.util.ValidationUtil;
@Slf4j
@RunWith(JUnit4.class)
public class TestJdk8 {
final static String RANDOM_CHARS = "0123456789";
@Test
public void testString() {
......@@ -46,4 +49,11 @@ public class TestJdk8 {
log.info("uuid:{}", uuid);
}
@Test
public void random() {
for(int i=0; i<10; i++){
log.info("{}-random:{}", i, RandomStringUtils.random(6, RANDOM_CHARS));
}
}
}
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