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

提交单次令牌发放的http服务和单词令牌校验切面+注解

parent 1f7db0de
......@@ -12,6 +12,7 @@ public interface Constants {
String PASSWORD_SALT = "_lkb";
String IMAGE_CAPTCHA_KEY = "img_captcha:";
String TOKEN_SINGLE_KEY_FOR_PHONE = "token_single:for_phone:";
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.exception.VerificationCodeErrorException;
import cn.quantgroup.xyqb.model.JsonResult;
import cn.quantgroup.xyqb.util.ValidationUtil;
import org.apache.commons.codec.binary.Base64;
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.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 单次令牌校验切面
*
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@Aspect
@Component
public class SingleTokenValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(SingleTokenValidateAdvisor.class);
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 自动化测试忽略单次令牌校验
*/
@Value("${xyqb.auth.singletoken.autotest.enable:false}")
private boolean autoTestSingleTokenEnabled;
/**
* 单次令牌校验切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.token.SingleTokenValidator)")
private void needSingleTokenValidate() {
}
/**
* 在受单次令牌保护的接口方法执行前, 执行单次令牌校验
*
* @throws Throwable
*/
@Around("needSingleTokenValidate()")
private Object doSingleTokenValidate(ProceedingJoinPoint pjp) throws Throwable {
if (autoTestSingleTokenEnabled) {
return pjp.proceed();
}
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");
// 当前请求的SingleToken
String requestToken = phoneTokenMap.get("requestToken");
if (StringUtils.isBlank(phoneNo) || StringUtils.isBlank(requestToken)){
return false;
}
final String key = Constants.TOKEN_SINGLE_KEY_FOR_PHONE + phoneNo;
String singleToken = redisTemplate.opsForValue().get(key);
// SingleToken不应为空值(空白、空格、null)
if (StringUtils.isBlank(singleToken)) {
// 修正规则
if(redisTemplate.hasKey(key)){
redisTemplate.delete(key);
}
return false;
}
boolean valid = Objects.equals(singleToken, requestToken);
// SingleToken校验正确时删除key
if(valid) {
redisTemplate.delete(key);
}else {
LOGGER.info("Token过期,请重新请求, token_single:for_phone:={}, requestToken={}, clientIp={}", phoneNo, requestToken, request.getRemoteAddr());
}
return valid;
}
/**
* 单次令牌参数解析
*
* @param request 当前请求,其首部行必须包含形如【SingleToken 13461067662:0123456789abcdef】的UTF-8编码的Base64加密参数
* @return 令牌参数Map 或 null
*/
private Map<String, String> getHeaderParam(HttpServletRequest request) {
String verificationHeader = "SingleToken ";
String credential = request.getHeader("authorization");
if (StringUtils.isBlank(credential) || !credential.startsWith(verificationHeader)) {
LOGGER.info("令牌参数无效, credential:{}", credential);
return null;
}
credential = credential.substring(verificationHeader.length(), credential.length());
boolean headerParamValid = true;
byte[] buf = Base64.decodeBase64(credential);
try {
credential = new String(buf, "UTF-8");
} catch (UnsupportedEncodingException e) {
headerParamValid = false;
LOGGER.error("不支持的编码{}.", credential, e);
}
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];
// 当前请求的SingleToken
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.aspect.token;
import java.lang.annotation.*;
/**
* 需要单次令牌校验标记
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleTokenValidator {
}
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.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.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 发放单次令牌
*
* @author 任文超
* @version 1.0.0
* @since 2017-10-31
*/
@RestController
@RequestMapping("/token")
public class SingleTokenController implements IBaseController {
private static final Logger LOGGER = LoggerFactory.getLogger(SingleTokenController.class);
private static final Long ONE_HOUR = 1 * 60 * 60L;
@Autowired
@Qualifier("stringRedisTemplate")
private RedisTemplate<String, String> redisTemplate;
/**
* 向指定用户账号(手机号)发放一枚SingleToken
* @param phoneNo 用户账号(手机号)
* @return 单次令牌
*/
@RequestMapping(value = "/single")
public JsonResult newSingleToken(HttpServletRequest request, @ModelAttribute("phoneNo") String phoneNo) {
if (StringUtils.isBlank(phoneNo)){
return JsonResult.buildErrorStateResult("", "fail");
}
String singleToken = UUID.randomUUID().toString();
final String key = Constants.TOKEN_SINGLE_KEY_FOR_PHONE + phoneNo;
redisTemplate.opsForValue().set(key, singleToken, ONE_HOUR, TimeUnit.SECONDS);
return JsonResult.buildSuccessResult("", singleToken);
}
}
......@@ -57,6 +57,8 @@ jr58.notify.userinfo=http://xfd.test.58v5.cn/customer/quantgroup_user_info
# 图形验证码
# 是否启用超级验证码 "__SUPERQG__", 用于测试环境自动化测试, 线上环境可忽略此参数
xyqb.auth.captcha.super.enable=1
# 单次令牌验证, 用于测试环境自动化测试, 线上环境可忽略此参数
xyqb.auth.singletoken.autotest.enable=true
#首参数校验
xyqb.fplock.limit.byhour=3
......
......@@ -39,6 +39,8 @@ jr58.notify.userinfo=http://xfd.test.58v5.cn/customer/quantgroup_user_info
# 图形验证码
# 是否启用超级验证码 "__SUPERQG__", 用于测试环境自动化测试, 线上环境可忽略此参数
xyqb.auth.captcha.super.enable=1
# 单次令牌验证, 用于测试环境自动化测试, 线上环境可忽略此参数
xyqb.auth.singletoken.autotest.enable=true
#首参数校验
xyqb.fplock.limit.byhour=3
xyqb.fplock.limit.byday=5
......
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
public class CommonTests {
@Test
public void compileBooleanAndNull() {
Assert.assertFalse(null instanceof Boolean);
Assert.assertNotEquals(null, Boolean.TRUE);
Assert.assertNotEquals(null, Boolean.FALSE);
}
@Test
public void print() {
System.out.println(null instanceof Boolean);
System.out.println(Boolean.TRUE.equals(null));
System.out.println(Boolean.FALSE.equals(null));
}
}
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