Commit 7f6a40eb authored by 技术部-任文超's avatar 技术部-任文超

更新One-Time Token获取策略,

优化UserController代码,
重新梳理用户安全问题解决方案的应对方法,
恢复旧接口的行为到分支初始状态
parent 072528aa
......@@ -12,7 +12,7 @@ public interface Constants {
String PASSWORD_SALT = "_lkb";
String IMAGE_CAPTCHA_KEY = "img_captcha:";
String TOKEN_ONCE_KEY_FOR_PHONE = "token_once:for_phone:";
String ONE_TIME_TOKEN = "One-Time-Token";
String REDIS_CAPTCHA_KEY = "auth:";
String REDIS_CAPTCHA_KEY_PATTERN = REDIS_CAPTCHA_KEY + IMAGE_CAPTCHA_KEY + "*";
......
package cn.quantgroup.xyqb.aspect.token;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.model.JsonResult;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* 一次性令牌校验切面
*
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@Aspect
@Component
public class OneTimeTokenValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(OneTimeTokenValidateAdvisor.class);
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 一次性令牌校验切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.token.OneTimeTokenValidator)")
private void needOneTimeToken() {}
/**
* 在受一次性令牌保护的接口方法执行前, 执行一次性令牌校验
*
* @throws Throwable
*/
@Around("needOneTimeToken()")
private Object checkOneTimeToken(ProceedingJoinPoint pjp) throws Throwable {
boolean valid = oneTimeTokenValid();
if (valid) {
return pjp.proceed();
}
return JsonResult.buildSuccessResult("Token过期,请重新请求", "", 2L);
}
/**
* 校验一次性令牌
* @return True or False
*/
private boolean oneTimeTokenValid() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 当前请求的OneTimeToken
String oneTimeToken = request.getHeader(Constants.ONE_TIME_TOKEN);
if (StringUtils.isBlank(oneTimeToken)){
return false;
}
String oneTimeToken_value = redisTemplate.opsForValue().get(oneTimeToken);
// OneTimeToken再Redis中值不应为空值(空白、空格、null)
if (StringUtils.isBlank(oneTimeToken_value)) {
return false;
}
boolean valid = Objects.equals(Boolean.TRUE.toString(), oneTimeToken_value);
// OneTimeToken校验正确时删除key
if(valid) {
redisTemplate.delete(oneTimeToken);
}else {
LOGGER.info("Token过期,请重新请求, One-Time-Token={}, clientIp={}", oneTimeToken, request.getRemoteAddr());
}
return valid;
}
}
......@@ -3,7 +3,7 @@ package cn.quantgroup.xyqb.aspect.token;
import java.lang.annotation.*;
/**
* 需要单次令牌校验标记
* 一次性令牌校验标记
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
......@@ -11,5 +11,5 @@ import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenOnceValidator {
public @interface OneTimeTokenValidator {
}
package cn.quantgroup.xyqb.aspect.token;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.util.ValidationUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 单次令牌校验切面
*
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@Aspect
@Component
public class TokenOnceValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenOnceValidateAdvisor.class);
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 单次令牌校验切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.token.TokenOnceValidator)")
private void needTokenOnceValidate() {
}
/**
* 在受单次令牌保护的接口方法执行前, 执行单次令牌校验
*
* @throws Throwable
*/
@Around("needTokenOnceValidate()")
private Object doTokenOnceValidate(ProceedingJoinPoint pjp) throws Throwable {
boolean checkTokenForPhone = checkTokenForPhone();
if (!checkTokenForPhone) {
return JsonResult.buildSuccessResult("Token过期,请重新请求", "", 2L);
}
return pjp.proceed();
}
/**
* 校验按手机号分发的单次令牌
* @return True or False
*/
private boolean checkTokenForPhone() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Map<String, String> phoneTokenMap = getHeaderParam(request);
if(phoneTokenMap == null || phoneTokenMap.isEmpty()){
return false;
}
// 当前用户手机号
String phoneNo = phoneTokenMap.get("phoneNo");
// 当前请求的TokenOnce
String requestToken = phoneTokenMap.get("requestToken");
if (StringUtils.isBlank(phoneNo) || StringUtils.isBlank(requestToken)){
return false;
}
final String key = Constants.TOKEN_ONCE_KEY_FOR_PHONE + phoneNo;
String tokenOnce = redisTemplate.opsForValue().get(key);
// TokenOnce不应为空值(空白、空格、null)
if (StringUtils.isBlank(tokenOnce)) {
redisTemplate.delete(key);
return false;
}
boolean valid = Objects.equals(tokenOnce, requestToken);
// TokenOnce校验正确时删除key
if(valid) {
redisTemplate.delete(key);
}else {
LOGGER.info("Token过期,请重新请求, token_once:for_phone:={}, requestToken={}, clientIp={}", phoneNo, requestToken, request.getRemoteAddr());
}
return valid;
}
/**
* 单次令牌参数解析
*
* @param request 当前请求,其首部行必须包含形如【TokenOnce MTM0NjEwNjc2NjI6NmFjMDY2NWItZTE5Yy00MzkyLWEyNDQtN2I2MTY5MDgzM2Y1】的UTF-8编码的Base64加密参数
* @return 令牌参数Map 或 null
*/
private Map<String, String> getHeaderParam(HttpServletRequest request) {
String headerName = "TokenOnce";
String credential = request.getHeader(headerName);
if (StringUtils.isBlank(credential)) {
LOGGER.info("令牌参数无效, credential:{}", credential);
return null;
}
boolean headerParamValid = true;
byte[] buf = Base64.getDecoder().decode(credential.getBytes());
credential = new String(buf, Charset.forName("UTF-8"));
if(!headerParamValid){
LOGGER.info("令牌参数无效, credential:{}", credential);
return null;
}
String[] credentialArr = credential.split(":");
headerParamValid = headerParamValid && credentialArr.length==2;
if (!headerParamValid) {
LOGGER.info("令牌参数无效, credential:{}", credential);
return null;
}
// 当前用户手机号
String phoneNo = credentialArr[0];
// 当前请求的TokenOnce
String requestToken = credentialArr[1];
headerParamValid = headerParamValid && ValidationUtil.validatePhoneNo(phoneNo);
if (!headerParamValid) {
LOGGER.info("令牌参数无效, credential:{}", credential);
return null;
}
LOGGER.info("获取单次令牌, phoneNo:{}, requestToken:{}", phoneNo, requestToken);
Map<String, String> phoneTokenMap = new HashMap<String, String>(2);
phoneTokenMap.put("phoneNo", phoneNo);
phoneTokenMap.put("requestToken", requestToken);
return phoneTokenMap;
}
}
package cn.quantgroup.xyqb.controller.internal.token;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.model.JsonResult;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.Charset;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 发放单次令牌
* 发放一次性令牌
*
* @author 任文超
* @version 1.0.0
......@@ -28,32 +20,25 @@ import java.util.concurrent.TimeUnit;
*/
@RestController
@RequestMapping("/token")
public class TokenOnceController implements IBaseController {
public class OneTimeTokenController implements IBaseController {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenOnceController.class);
private static final Long ONE_HOUR = 10 * 60L;
private static final Long SECONDS = 1 * 60L;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 向指定用户账号(手机号)发放一枚TokenOnce
* TokenOnce用法:其首部行必须包含形如【TokenOnce MTM0NjEwNjc2NjI6NmFjMDY2NWItZTE5Yy00MzkyLWEyNDQtN2I2MTY5MDgzM2Y1】的UTF-8编码的Base64加密参数
* 例如:Base64.getEncoder().encodeToString("13461067662:6ac0665b-e19c-4392-a244-7b61690833f5".getBytes(Charset.forName("UTF-8")));
* 发放一枚一次性令牌
* One-Time Token用法:http请求首部行添加参数{One-Time-Token 6ac0665b-e19c-4392-a244-7b61690833f5}
*
* @param phoneNo 用户账号(手机号)
* @return 单次令牌
* @return 一次性令牌
*/
@RequestMapping(value = "/once")
public JsonResult newTokenOnce(HttpServletRequest request, @ModelAttribute("phoneNo") String phoneNo) {
if (StringUtils.isBlank(phoneNo)){
return JsonResult.buildErrorStateResult("获取TokenOnce失败", "");
}
String tokenOnce = UUID.randomUUID().toString();
final String key = Constants.TOKEN_ONCE_KEY_FOR_PHONE + phoneNo;
redisTemplate.opsForValue().set(key, tokenOnce, ONE_HOUR, TimeUnit.SECONDS);
return JsonResult.buildSuccessResult("", tokenOnce);
@RequestMapping(value = "/oneTime")
public JsonResult oneTimeToken() {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, Boolean.TRUE.toString(), SECONDS, TimeUnit.SECONDS);
return JsonResult.buildSuccessResult("", token);
}
}
package cn.quantgroup.xyqb.controller.internal.user;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.token.TokenOnceValidator;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaValidator;
import cn.quantgroup.xyqb.aspect.token.OneTimeTokenValidator;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.Merchant;
import cn.quantgroup.xyqb.entity.User;
......@@ -72,28 +73,9 @@ public class UserController implements IBaseController {
private IWechatService wechatService;
private static final char[] PWD_BASE = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
//@LogHttpCaller
//@RequestMapping("/login2")
//public JsonResult login2(
// @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
// @RequestParam(required = false, defaultValue = "1") Long createdFrom,
// @RequestParam(required = false, defaultValue = "") String userId, HttpServletRequest request, @RequestParam(required = false) String openId,@RequestParam(required = false) String dimension) {
//
// if (!StringUtils.isEmpty(userId) && userId.length() > 10) {
//
// return loginWithUserId(channelId, appChannel, createdFrom, userId, null, dimension);
// } else {
// return loginWithHttpBasic(channelId, appChannel, createdFrom, null, request, null,dimension);
// }
//}
@RequestMapping("/test")
public JsonResult test() {
return JsonResult.buildSuccessResult("", getCurrentUserFromRedis());
}
/**
* 登录(账号 + 密码),H5专用入口
......@@ -107,7 +89,7 @@ public class UserController implements IBaseController {
* @param dimension
* @return
*/
@TokenOnceValidator
@CaptchaValidator
@RequestMapping("/loginForH5")
public JsonResult loginForH5(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
......@@ -130,7 +112,7 @@ public class UserController implements IBaseController {
* @param request
* @return
*/
@TokenOnceValidator
@OneTimeTokenValidator
@RequestMapping("/login/fastForH5")
public JsonResult loginFastForH5(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
......@@ -138,29 +120,29 @@ public class UserController implements IBaseController {
@RequestParam(required = false,defaultValue = "xyqb") String key,
@RequestParam(required = false)Long btRegisterChannelId,
@RequestParam(required = false) String dimension ,HttpServletRequest request) {
Map<String, JsonResult> validMap = getHeaderParam(request);
if (null != validMap.get("fail")) {
return validMap.get("fail");
}
Merchant merchant = merchantService.findMerchantByName(key);
if (merchant == null) {
return JsonResult.buildErrorStateResult("未知的连接", null);
}
JsonResult successResult = validMap.get("success");
String phoneNo = successResult.getData().toString();
String verificationCode = successResult.getMsg();
smsValidForFastLogin(phoneNo, verificationCode);
return loginFast(channelId, appChannel, createdFrom, key, btRegisterChannelId, dimension, request);
}
/**
* 账号密码登录
* @param channelId
* @param appChannel
* @param createdFrom
* @param userId
* @param key
* @param request
* @param openId
* @param dimension
* @return
*/
@RequestMapping("/login")
public JsonResult login(
@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,
HttpServletRequest request, String openId,
@RequestParam(required = false) String dimension) {
@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,
HttpServletRequest request, String openId,
@RequestParam(required = false) String dimension) {
Merchant merchant = merchantService.findMerchantByName(key);
if (merchant == null) {
......@@ -173,15 +155,34 @@ public class UserController implements IBaseController {
return loginWithHttpBasic(channelId, appChannel, createdFrom, merchant, request, openId,dimension);
}
}
//@LogHttpCaller
//@RequestMapping("/login2")
//public JsonResult login2(
// @RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
// @RequestParam(required = false, defaultValue = "1") Long createdFrom,
// @RequestParam(required = false, defaultValue = "") String userId, HttpServletRequest request, @RequestParam(required = false) String openId,@RequestParam(required = false) String dimension) {
//
// if (!StringUtils.isEmpty(userId) && userId.length() > 10) {
//
// return loginWithUserId(channelId, appChannel, createdFrom, userId, null, dimension);
// } else {
// return loginWithHttpBasic(channelId, appChannel, createdFrom, null, request, null,dimension);
// }
//}
@RequestMapping("/test")
public JsonResult test() {
return JsonResult.buildSuccessResult("", getCurrentUserFromRedis());
}
@RequestMapping("/login/fast")
public JsonResult loginFast(
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
@RequestParam(required = false, defaultValue = "1") Long createdFrom,
@RequestParam(required = false,defaultValue = "xyqb") String key,
@RequestParam(required = false)Long btRegisterChannelId,
@RequestParam(required = false) String dimension ,HttpServletRequest request) {
Map<String, JsonResult> validMap = getHeaderParam(request);
@RequestParam(required = false, defaultValue = "1") Long channelId, String appChannel,
@RequestParam(required = false, defaultValue = "1") Long createdFrom,
@RequestParam(required = false,defaultValue = "xyqb") String key,
@RequestParam(required = false)Long btRegisterChannelId,
@RequestParam(required = false) String dimension ,HttpServletRequest request) {
Map<String, JsonResult> validMap = getHeaderParam(request);
if (null != validMap.get("fail")) {
return validMap.get("fail");
}
......@@ -191,23 +192,12 @@ public class UserController implements IBaseController {
}
JsonResult successResult = validMap.get("success");
String phoneNo = successResult.getData().toString();
String verificationCode = successResult.getMsg();
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
LOGGER.info("用户快速注册失败,短信验证码错误, registerFrom:{}, phoneNo:{}, verificationCode:{}", btRegisterChannelId, phoneNo, verificationCode);
if(needRetSendCode(phoneNo)){
String hkey = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(hkey);
return JsonResult.buildErrorStateResult("错误次数过多,请重新获取短信验证码", null);
}
return JsonResult.buildErrorStateResult("短信验证码错误", null);
}
User user = userService.findByPhoneWithCache(phoneNo);
if (user != null && !user.getEnable()) {
return JsonResult.buildErrorStateResult("登录失败", null);
}
if (user == null) {
user = registerFastWhenLogin(phoneNo, verificationCode, channelId, createdFrom, appChannel,btRegisterChannelId,dimension);
user = registerFastWhenLogin(phoneNo, channelId, createdFrom, appChannel,btRegisterChannelId,dimension);
if (user == null) {
throw new UserNotExistException("用户未找到");
}
......@@ -219,9 +209,9 @@ public class UserController implements IBaseController {
// return createSession(channelId, createdFrom, appChannel, user);
}
private User registerFastWhenLogin(String phoneNo, String verificationCode, Long channelId, Long registerFrom, String appChannel, Long btRegisterChannelId,String dimension) {
private User registerFastWhenLogin(String phoneNo, Long channelId, Long registerFrom, String appChannel, Long btRegisterChannelId,String dimension) {
String password = genRandomPwd();
LOGGER.info("用户快速注册, phoneNo:{}, verificationCode:{}, channelId:{}, registerFrom:{},appChannel:{},btRegisterChannelId", phoneNo, verificationCode, channelId, registerFrom, appChannel,btRegisterChannelId);
LOGGER.info("用户快速注册, phoneNo:{}, channelId:{}, registerFrom:{},appChannel:{},btRegisterChannelId", phoneNo, channelId, registerFrom, appChannel,btRegisterChannelId);
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
LOGGER.info("用户快速注册失败,手机号错误, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
throw new UserNotExistException("手机号错误");
......@@ -232,14 +222,6 @@ public class UserController implements IBaseController {
if (channelId == 222L) {
registerFrom=222L;
}
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
if(needRetSendCode(phoneNo)){
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(key);
throw new VerificationCodeErrorException("错误次数过多,请重新获取短信验证码");
}
throw new VerificationCodeErrorException("短信验证码错误");
}
User user=userService.registerAndReturn(phoneNo, password, registerFrom,btRegisterChannelId);
LOGGER.info("用户快速注册成功, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
UserStatistics statistics=new UserStatistics(user,dimension,2,channelId);
......@@ -325,15 +307,7 @@ public class UserController implements IBaseController {
LOGGER.info("用户快速注册失败,该手机号已经被注册, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
return JsonResult.buildErrorStateResult("该手机号已经被注册", null);
}
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
LOGGER.info("用户快速注册失败,短信验证码错误, registerFrom:{}, phoneNo:{}, verificationCode:{}", registerFrom, phoneNo, verificationCode);
if(needRetSendCode(phoneNo)){
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(key);
return JsonResult.buildErrorStateResult("错误次数过多,请重新获取短信验证码", null);
}
return JsonResult.buildErrorStateResult("短信验证码错误", null);
}
smsValidForFastLogin(phoneNo, verificationCode);
if (!userService.register(phoneNo, password, registerFrom, getIp(), channelId,btRegisterChannelId,dimension)) {
LOGGER.info("用户快速注册失败,请稍后重试, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
return JsonResult.buildErrorStateResult("注册失败,请稍后重试", null);
......@@ -346,7 +320,7 @@ public class UserController implements IBaseController {
/**
* 用户注册
* 用户注册
*
* @param phoneNo
* @param password
......@@ -379,15 +353,7 @@ public class UserController implements IBaseController {
LOGGER.info("用户注册失败,该手机号已经被注册, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
return JsonResult.buildErrorStateResult("该手机号已经被注册", null);
}
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
LOGGER.info("用户快速注册失败,短信验证码错误, registerFrom:{}, phoneNo:{}, verificationCode:{}", registerFrom, phoneNo, verificationCode);
if(needRetSendCode(phoneNo)){
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(key);
return JsonResult.buildErrorStateResult("错误次数过多,请重新获取短信验证码", null);
}
return JsonResult.buildErrorStateResult("短信验证码错误", null);
}
smsValidForRegister(phoneNo, verificationCode);
if (!userService.register(phoneNo, password, registerFrom, getIp(), channelId,btRegisterChannelId,dimension)) {
LOGGER.info("用户快速注册失败,请稍后重试, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
return JsonResult.buildErrorStateResult("注册失败,请稍后重试", null);
......@@ -403,9 +369,8 @@ public class UserController implements IBaseController {
*
* @param phoneNo 手机号
* @return
*
*/
@RequestMapping(value = {"/exist", "/exist_check"})
@RequestMapping("/exist")
public JsonResult exist(@RequestParam String phoneNo) {
LOGGER.info("检查用户是否存在, phoneNo:{}", phoneNo);
if (userService.exist(phoneNo)) {
......@@ -415,6 +380,18 @@ public class UserController implements IBaseController {
return JsonResult.buildSuccessResult(null, null);
}
/**
* 检查用户是否存在
*
* @param phoneNo 手机号
* @return
*/
@RequestMapping("/exist_check")
public JsonResult existForResetPwd(@RequestParam String phoneNo) {
LOGGER.info("检查用户是否存在, phoneNo:{}", phoneNo);
return JsonResult.buildSuccessResult(null, userService.exist(phoneNo));
}
/**
* 重置密码
*
......@@ -437,14 +414,7 @@ public class UserController implements IBaseController {
if (password.length() < 6 || password.length() > 12) {
return JsonResult.buildErrorStateResult("密码应为6-12位", null);
}
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
if(needRetSendCode(phoneNo)){
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(key);
return JsonResult.buildErrorStateResult("错误次数过多,请重新获取短信验证码", null);
}
return JsonResult.buildErrorStateResult("短信验证码错误", null);
}
smsValidForRegister(phoneNo, verificationCode);
if (!userService.resetPassword(phoneNo, password)) {
return JsonResult.buildErrorStateResult("修改密码失败", null);
}
......@@ -511,9 +481,9 @@ public class UserController implements IBaseController {
return JsonResult.buildSuccessResult("token校验成功", userModel);
}
private User registerFastWhenLogin(String phoneNo, String verificationCode, Long channelId, Long registerFrom, String appChannel) {
private User registerFastWhenLogin(String phoneNo, Long channelId, Long registerFrom, String appChannel) {
String password = genRandomPwd();
LOGGER.info("用户快速注册, phoneNo:{}, verificationCode:{}, channelId:{}, registerFrom:{},appChannel:{}", phoneNo, verificationCode, channelId, registerFrom, appChannel);
LOGGER.info("用户快速注册, phoneNo:{}, channelId:{}, registerFrom:{},appChannel:{}", phoneNo, channelId, registerFrom, appChannel);
if (!ValidationUtil.validatePhoneNo(phoneNo)) {
LOGGER.info("用户快速注册失败,手机号错误, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
throw new UserNotExistException("手机号错误");
......@@ -521,20 +491,14 @@ public class UserController implements IBaseController {
if (null == registerFrom) {
registerFrom = 1L;
}
LOGGER.info("用户快速注册成功, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
if(needRetSendCode(phoneNo)){
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(key);
throw new VerificationCodeErrorException("错误次数过多,请重新获取短信验证码");
}
throw new VerificationCodeErrorException("短信验证码错误");
User newUser = userService.registerAndReturn(phoneNo, password, registerFrom);
if(newUser != null && newUser.getId() != null && newUser.getId() > 0){
LOGGER.info("用户快速注册成功, registerFrom:{}, phoneNo:{}", registerFrom, phoneNo);
}
return userService.registerAndReturn(phoneNo, password, registerFrom);
return newUser;
}
private JsonResult loginWithHttpBasic(Long channelId, String appChannel, Long createdFrom, Merchant merchant, HttpServletRequest request, String openId,String dimension) {
User user = verificateUserNameAndPassword(request, openId);
if (user == null) {
......@@ -624,4 +588,40 @@ public class UserController implements IBaseController {
return JsonResult.buildSuccessResult(null, null);
}
/**
* 注册时校验短信验证码
* @param phoneNo
* @param verificationCode
*/
private void smsValidForRegister(String phoneNo, String verificationCode) {
if (!smsService.validRegisterOrResetPasswdVerificationCode(phoneNo, verificationCode)) {
smsReSendOrNot(phoneNo);
throw new VerificationCodeErrorException("短信验证码错误");
}
}
/**
* 登录时校验短信验证码
* @param phoneNo
* @param verificationCode
*/
private void smsValidForFastLogin(String phoneNo, String verificationCode) {
if (!smsService.validateFastLoginVerificationCode(phoneNo, verificationCode)) {
smsReSendOrNot(phoneNo);
throw new VerificationCodeErrorException("短信验证码错误");
}
}
/**
* 是否需要重新获取短信验证码
* @param phoneNo
*/
private void smsReSendOrNot(String phoneNo) {
if(needRetSendCode(phoneNo)){
String key = Constants.REDIS_PREFIX_VERIFICATION_CODE + phoneNo;
stringRedisTemplate.delete(key);
throw new VerificationCodeErrorException("错误次数过多,请重新获取短信验证码");
}
}
}
......@@ -27,7 +27,7 @@ public class RequestFilter implements Filter {
private static final String[] ALLOWED_PATTERNS = {
"/user_detail/**","/hello/**","/innerapi/**", "/user/exist", "/motan/**", "/user/register", "/user/login", "/user/register/fast",
"/token/once", "/user/loginForH5", "/user/register/fastForH5",
"/token/oneTime", "/user/loginForH5", "/user/register/fastForH5",
"/auth/info/login","/user/login/fast","/user/reset_password", "/user/exist_check","/user/center/**",
"/jr58/**", "/app/login", "/app/login_super","/app/login2","/user/login2", "/wechat/**", "/config/**", "/api/**", "/user/exists_token","/query/**",
"/platform/api/page/return_url", "/MP_" +
......
package login;
import cn.quantgroup.xyqb.Bootstrap;
import cn.quantgroup.xyqb.Constants;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.octo.captcha.service.CaptchaServiceException;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
......@@ -21,13 +24,14 @@ import org.springframework.web.context.WebApplicationContext;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.Optional;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Bootstrap.class)
@WebAppConfiguration
public class LoginWithTokenOnceTests {
public class LoginForH5Tests {
final String phoneNo = "13461067662";
final String userName = "root";
final String password = "!QAZ2wsx";
......@@ -54,71 +58,19 @@ public class LoginWithTokenOnceTests {
.andExpect(status().isOk());
mvc.perform(MockMvcRequestBuilders.get("/user/login/fastForH5").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
mvc.perform(MockMvcRequestBuilders.get("/api/captcha").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
mvc.perform(MockMvcRequestBuilders.get("/api/sms/send_login_code").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
/**
* 测试TokenOnce发放服务
* 测试图形验证码账密登录
* @throws Exception
*/
@Test
public void testLoginFoH5() throws Exception{
// 获取TokenOnce
String tokenOnceUri = "/token/once";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON)
.param("phoneNo", phoneNo))
.andExpect(status().isOk())
.andReturn();
String content = mvcResult.getResponse().getContentAsString();
JSONObject jsonResult = JSON.parseObject(new String(content));
Object code = jsonResult.get("code");
Assert.assertEquals("0000", code);
Object data = jsonResult.get("data");
Assert.assertNotNull(data);
StringBuilder tokenBuilder = new StringBuilder(phoneNo);
String tokenOnce = new String(Base64.getEncoder().encodeToString(tokenBuilder.append(":").append(data).toString().getBytes(Charset.forName("UTF-8"))));
StringBuilder basicBuilder = new StringBuilder();
String authorization = "Basic " + new String(Base64.getEncoder().encodeToString(basicBuilder.append(userName).append(":").append(password).toString().getBytes(Charset.forName("UTF-8"))));
// 第一次使用TokenOnce
String aspectUri = "/user/loginForH5";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.header("authorization", authorization)
.param("openId", "none"))
.andExpect(status().isOk())
.andReturn();
content = mvcResult.getResponse().getContentAsString();
jsonResult = JSON.parseObject(new String(content));
code = jsonResult.get("code");
Object businessCode = jsonResult.get("businessCode");
Assert.assertEquals("0000", code);
Assert.assertNotEquals("0002", businessCode);
// 使用过期的TokenOnce
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.header("authorization", authorization)
.param("openId", "none"))
.andExpect(status().isOk())
.andReturn();
content = mvcResult.getResponse().getContentAsString();
jsonResult = JSON.parseObject(new String(content));
code = jsonResult.get("code");
Assert.assertEquals("0000", code);
businessCode = jsonResult.get("businessCode");
Assert.assertEquals("0002", businessCode);
// 不使用TokenOnce
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("authorization", authorization)
.param("openId", "none"))
.andExpect(status().isOk())
.andReturn();
content = mvcResult.getResponse().getContentAsString();
jsonResult = JSON.parseObject(new String(content));
code = jsonResult.get("code");
Assert.assertEquals("0000", code);
businessCode = jsonResult.get("businessCode");
Assert.assertEquals("0002", businessCode);
Assert.assertTrue(false);
}
/**
......@@ -128,8 +80,8 @@ public class LoginWithTokenOnceTests {
@Test
public void testLoginFastFoH5() throws Exception{
// 获取TokenOnce
String tokenOnceUri = "/token/once";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON)
String oneTimeTokenUri = "/token/oneTime";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(oneTimeTokenUri).accept(MediaType.APPLICATION_JSON)
.param("phoneNo", phoneNo))
.andExpect(status().isOk())
.andReturn();
......@@ -139,11 +91,11 @@ public class LoginWithTokenOnceTests {
Assert.assertEquals("0000", code);
Object data = jsonResult.get("data");
Assert.assertNotNull(data);
StringBuilder tokenBuilder = new StringBuilder(phoneNo);
String tokenOnce = new String(Base64.getEncoder().encodeToString(tokenBuilder.append(":").append(data).toString().getBytes(Charset.forName("UTF-8"))));
String oneTimeToken = String.valueOf(data);
Assert.assertNotEquals("", oneTimeToken);
String smsUri = "/api/sms/send_login_code";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON)
mvcResult = mvc.perform(MockMvcRequestBuilders.get(oneTimeTokenUri).accept(MediaType.APPLICATION_JSON)
.param("phoneNo", phoneNo))
.andExpect(status().isOk())
.andReturn();
......@@ -159,7 +111,7 @@ public class LoginWithTokenOnceTests {
// 第一次使用TokenOnce
String aspectUri = "/user/login/fastForH5";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.header(Constants.ONE_TIME_TOKEN, oneTimeToken)
.header("authorization", authorization))
.andExpect(status().isOk())
.andReturn();
......@@ -168,10 +120,10 @@ public class LoginWithTokenOnceTests {
code = jsonResult.get("code");
Object businessCode = jsonResult.get("businessCode");
Assert.assertEquals("0000", code);
Assert.assertNotEquals("0001", businessCode);
Assert.assertNotEquals("0002", businessCode);
// 使用过期的TokenOnce与verificationCode
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.header(Constants.ONE_TIME_TOKEN, oneTimeToken)
.header("authorization", authorization))
.andExpect(status().isOk())
.andReturn();
......
package token;
import cn.quantgroup.xyqb.Bootstrap;
import cn.quantgroup.xyqb.Constants;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
......@@ -27,7 +28,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Bootstrap.class)
@WebAppConfiguration
public class TokenOnceTests {
public class OneTimeTokenTests {
final String phoneNo = "13461067662";
private MockMvc mvc;
......@@ -48,70 +49,54 @@ public class TokenOnceTests {
public void testServer() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
mvc.perform(MockMvcRequestBuilders.get("/token/once").accept(MediaType.APPLICATION_JSON))
mvc.perform(MockMvcRequestBuilders.get("/token/oneTime").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
/**
* 测试TokenOnce发放服务
* 测试OneTime-Token发放服务
* @throws Exception
*/
@Test
public void testTokenOnce() throws Exception{
String tokenOnceUri = "/token/once";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON)
.header("Session-Id", "82107d0326772b8b5c72ec11801b8ab3"))
String tokenOnceUri = "/token/oneTime";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String content = mvcResult.getResponse().getContentAsString();
JSONObject jsonResult = JSON.parseObject(new String(content));
Object code = jsonResult.get("code");
Assert.assertEquals("0000", code);
Object data = jsonResult.get("data");
Assert.assertEquals(data, "");
Object msg = jsonResult.get("msg");
Assert.assertEquals("获取TokenOnce失败", jsonResult.get("msg"));
jsonResult = JSON.parseObject(new String(content));
mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON)
.param("phoneNo", phoneNo))
.andExpect(status().isOk())
.andReturn();
content = mvcResult.getResponse().getContentAsString();
jsonResult = JSON.parseObject(new String(content));
code = jsonResult.get("code");
Assert.assertEquals("0000", code);
data = jsonResult.get("data");
Object data = jsonResult.getString("data");
Assert.assertNotNull(data);
msg = jsonResult.get("msg");
Assert.assertEquals(msg, "");
String oneTimeToken = String.valueOf(data);
Assert.assertNotEquals("", oneTimeToken);
}
/**
* 测试TokenOnce切面
* 测试OneTime-Token切面
* @throws Exception
*/
// TODO 用户注册先不加TokenOnce校验,进一步确认后再添加或删除本用例
// TODO 用户注册先不加OneTime-Token校验,进一步确认后再添加或删除本用例
//@Test
public void testAspect() throws Exception{
// 获取TokenOnce
String tokenOnceUri = "/token/once";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(tokenOnceUri).accept(MediaType.APPLICATION_JSON)
.param("phoneNo", phoneNo))
// 获取OneTime-Token
String oneTimeTokenUri = "/token/oneTime";
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get(oneTimeTokenUri).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
String content = mvcResult.getResponse().getContentAsString();
JSONObject jsonResult = JSON.parseObject(new String(content));
Object code = jsonResult.get("code");
Assert.assertEquals("0000", code);
Object data = jsonResult.get("data");
Object data = jsonResult.getString("data");
Assert.assertNotNull(data);
StringBuilder tokenBuilder = new StringBuilder(phoneNo);
String tokenOnce = new String(Base64.getEncoder().encodeToString(tokenBuilder.append(":").append(data).toString().getBytes(Charset.forName("UTF-8"))));
// 第一次使用TokenOnce
String aspectUri = "/user/register";
String oneTimeToken = String.valueOf(data);
Assert.assertNotEquals("", oneTimeToken);
// 第一次使用OneTime-Token
String aspectUri = "/user/loginForH5";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.header(Constants.ONE_TIME_TOKEN, oneTimeToken)
.param("phoneNo", phoneNo)
.param("password", "Qg123456")
.param("verificationCode", "1234"))
......@@ -125,7 +110,7 @@ public class TokenOnceTests {
Assert.assertNotEquals("0002", businessCode);
// 使用过期的TokenOnce
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.header("TokenOnce", oneTimeToken)
.param("phoneNo", phoneNo)
.param("password", "Qg123456")
.param("verificationCode", "1234"))
......
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