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

提交jUnit,当前Demo为单次令牌切面的测试用例,已绿

parent c39f7a6b
......@@ -12,7 +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 TOKEN_ONCE_KEY_FOR_PHONE = "token_once:for_phone:";
String REDIS_CAPTCHA_KEY = "auth:";
String REDIS_CAPTCHA_KEY_PATTERN = REDIS_CAPTCHA_KEY + IMAGE_CAPTCHA_KEY + "*";
......
......@@ -4,7 +4,6 @@ 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;
......@@ -22,6 +21,8 @@ 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;
......@@ -35,9 +36,9 @@ import java.util.Objects;
*/
@Aspect
@Component
public class SingleTokenValidateAdvisor {
public class TokenOnceValidateAdvisor {
private static final Logger LOGGER = LoggerFactory.getLogger(SingleTokenValidateAdvisor.class);
private static final Logger LOGGER = LoggerFactory.getLogger(TokenOnceValidateAdvisor.class);
@Autowired
@Qualifier("stringRedisTemplate")
......@@ -46,14 +47,14 @@ public class SingleTokenValidateAdvisor {
/**
* 自动化测试忽略单次令牌校验
*/
@Value("${xyqb.auth.singletoken.autotest.enable:false}")
private boolean autoTestSingleTokenEnabled;
@Value("${xyqb.auth.tokenonce.autotest.enable:true}")
private boolean autoTestTokenOnceEnabled;
/**
* 单次令牌校验切面
*/
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.token.SingleTokenValidator)")
private void needSingleTokenValidate() {
@Pointcut("@annotation(cn.quantgroup.xyqb.aspect.token.TokenOnceValidator)")
private void needTokenOnceValidate() {
}
/**
......@@ -61,9 +62,9 @@ public class SingleTokenValidateAdvisor {
*
* @throws Throwable
*/
@Around("needSingleTokenValidate()")
private Object doSingleTokenValidate(ProceedingJoinPoint pjp) throws Throwable {
if (autoTestSingleTokenEnabled) {
@Around("needTokenOnceValidate()")
private Object doTokenOnceValidate(ProceedingJoinPoint pjp) throws Throwable {
if (autoTestTokenOnceEnabled) {
return pjp.proceed();
}
boolean checkTokenForPhone = checkTokenForPhone();
......@@ -85,27 +86,27 @@ public class SingleTokenValidateAdvisor {
}
// 当前用户手机号
String phoneNo = phoneTokenMap.get("phoneNo");
// 当前请求的SingleToken
// 当前请求的TokenOnce
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)) {
final String key = Constants.TOKEN_ONCE_KEY_FOR_PHONE + phoneNo;
String tokenOnce = redisTemplate.opsForValue().get(key);
// TokenOnce不应为空值(空白、空格、null)
if (StringUtils.isBlank(tokenOnce)) {
// 修正规则
if(redisTemplate.hasKey(key)){
redisTemplate.delete(key);
}
return false;
}
boolean valid = Objects.equals(singleToken, requestToken);
// SingleToken校验正确时删除key
boolean valid = Objects.equals(tokenOnce, requestToken);
// TokenOnce校验正确时删除key
if(valid) {
redisTemplate.delete(key);
}else {
LOGGER.info("Token过期,请重新请求, token_single:for_phone:={}, requestToken={}, clientIp={}", phoneNo, requestToken, request.getRemoteAddr());
LOGGER.info("Token过期,请重新请求, token_once:for_phone:={}, requestToken={}, clientIp={}", phoneNo, requestToken, request.getRemoteAddr());
}
return valid;
}
......@@ -113,25 +114,19 @@ public class SingleTokenValidateAdvisor {
/**
* 单次令牌参数解析
*
* @param request 当前请求,其首部行必须包含形如【SingleToken 13461067662:0123456789abcdef】的UTF-8编码的Base64加密参数
* @param request 当前请求,其首部行必须包含形如【TokenOnce MTM0NjEwNjc2NjI6NmFjMDY2NWItZTE5Yy00MzkyLWEyNDQtN2I2MTY5MDgzM2Y1】的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)) {
String headerName = "TokenOnce";
String credential = request.getHeader(headerName);
if (StringUtils.isBlank(credential)) {
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);
}
byte[] buf = Base64.getDecoder().decode(credential.getBytes());
credential = new String(buf, Charset.forName("UTF-8"));
if(!headerParamValid){
LOGGER.info("令牌参数无效, credential:{}", credential);
return null;
......@@ -144,7 +139,7 @@ public class SingleTokenValidateAdvisor {
}
// 当前用户手机号
String phoneNo = credentialArr[0];
// 当前请求的SingleToken
// 当前请求的TokenOnce
String requestToken = credentialArr[1];
headerParamValid = headerParamValid && ValidationUtil.validatePhoneNo(phoneNo);
if (!headerParamValid) {
......
......@@ -11,5 +11,5 @@ import java.lang.annotation.*;
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SingleTokenValidator {
public @interface TokenOnceValidator {
}
......@@ -3,6 +3,7 @@ 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;
......@@ -14,6 +15,7 @@ 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;
......@@ -26,9 +28,9 @@ import java.util.concurrent.TimeUnit;
*/
@RestController
@RequestMapping("/token")
public class SingleTokenController implements IBaseController {
public class TokenOnceController implements IBaseController {
private static final Logger LOGGER = LoggerFactory.getLogger(SingleTokenController.class);
private static final Logger LOGGER = LoggerFactory.getLogger(TokenOnceController.class);
private static final Long ONE_HOUR = 1 * 60 * 60L;
@Autowired
......@@ -36,19 +38,22 @@ public class SingleTokenController implements IBaseController {
private RedisTemplate<String, String> redisTemplate;
/**
* 向指定用户账号(手机号)发放一枚SingleToken
* 向指定用户账号(手机号)发放一枚TokenOnce
* TokenOnce用法:其首部行必须包含形如【TokenOnce MTM0NjEwNjc2NjI6NmFjMDY2NWItZTE5Yy00MzkyLWEyNDQtN2I2MTY5MDgzM2Y1】的UTF-8编码的Base64加密参数
* 例如:Base64.getEncoder().encodeToString("13461067662:6ac0665b-e19c-4392-a244-7b61690833f5".getBytes(Charset.forName("UTF-8")));
*
* @param phoneNo 用户账号(手机号)
* @return 单次令牌
*/
@RequestMapping(value = "/single")
public JsonResult newSingleToken(HttpServletRequest request, @ModelAttribute("phoneNo") String phoneNo) {
@RequestMapping(value = "/once")
public JsonResult newTokenOnce(HttpServletRequest request, @ModelAttribute("phoneNo") String phoneNo) {
if (StringUtils.isBlank(phoneNo)){
return JsonResult.buildErrorStateResult("", "fail");
return JsonResult.buildErrorStateResult("获取TokenOnce失败", "");
}
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);
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);
}
}
......@@ -3,6 +3,7 @@ package cn.quantgroup.xyqb.controller.internal.user;
import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.aspect.captcha.CaptchaNewValidator;
import cn.quantgroup.xyqb.aspect.logcaller.LogHttpCaller;
import cn.quantgroup.xyqb.aspect.token.TokenOnceValidator;
import cn.quantgroup.xyqb.controller.IBaseController;
import cn.quantgroup.xyqb.entity.Merchant;
import cn.quantgroup.xyqb.entity.User;
......@@ -296,6 +297,7 @@ public class UserController implements IBaseController {
* @param channelId
* @return
*/
@TokenOnceValidator
@RequestMapping("/register")
public JsonResult register(@RequestParam String phoneNo, @RequestParam String password,
@RequestParam String verificationCode, @RequestParam(required = false) Long channelId,
......
......@@ -58,7 +58,7 @@ 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.auth.tokenonce.autotest.enable=false
#首参数校验
xyqb.fplock.limit.byhour=3
......
......@@ -39,8 +39,6 @@ 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
......
package token;
import cn.quantgroup.xyqb.Bootstrap;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.nio.charset.Charset;
import java.util.Base64;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Bootstrap.class)
@WebAppConfiguration
public class TokenOnceTests {
final String phoneNo = "13461067662";
private MockMvc mvc;
@Autowired
WebApplicationContext webApplicationConnect;
@Before
public void setUp() throws JsonProcessingException {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationConnect).build();
}
/**
* 测试Server是否可达
* @throws Exception
*/
@Test
public void testServer() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
/**
* 测试TokenOnce发放服务
* @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"))
.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");
Assert.assertNotNull(data);
msg = jsonResult.get("msg");
Assert.assertEquals(msg, "");
}
/**
* 测试TokenOnce切面
* @throws Exception
*/
@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))
.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"))));
// 第一次使用TokenOnce
String aspectUri = "/user/register";
mvcResult = mvc.perform(MockMvcRequestBuilders.get(aspectUri).accept(MediaType.APPLICATION_JSON)
.header("TokenOnce", tokenOnce)
.param("phoneNo", phoneNo)
.param("password", "Qg123456")
.param("verificationCode", "1234"))
.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)
.param("phoneNo", phoneNo)
.param("password", "Qg123456")
.param("verificationCode", "1234"))
.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)
.param("phoneNo", phoneNo)
.param("password", "Qg123456")
.param("verificationCode", "1234"))
.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);
}
}
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