方案
这是一个UML设计方案
代码实现
第一步
,接口调用目的是返回token
@Slf4j
@RestController
@RequestMapping("/dispatch")
public class InAuthenticationController {
@Autowired
private OpenService service;
@Autowired
private AccountService accountService;
@ApiOperation(value = "外部系统统一入口", notes = "外部系统统一入口")
@PostMapping("/open")
public Map<String, Object> authentication(@RequestBody String json, @RequestParam("sign") String sign,
HttpServletRequest request) {
log.info("request info json:{} sign:{}", json, sign);
return service.process(json, sign, IpUtil.getIpAddress(request));
}
@ApiOperation(value = " 对账Get入口", notes = " 对账Get入口")
@GetMapping("/open")
public void authenticationGet(@RequestParam("fileId") String fileId, @RequestParam("sign") String sign,
HttpServletRequest request, HttpServletResponse response) {
log.info("request info fileId:{} sign:{}", fileId, sign);
service.getAccountFile(fileId, sign, IpUtil.getIpAddress(request),response);
}
}
理解:工厂模式是用接口调用实现(创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象)
策略模式:用接口+抽象类写抽象过程实现,抽象过程方法内又可以用组合模式,避免多重分支的if…else…和switch语句(创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法)
责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象能够处理它
第二步
调用service,公共鉴权(对公共参数进行校验,验证签名等)
@Slf4j
@Service
public class OpenService {
**//责任链模式(Chain of Responsibility)**
@Autowired
private ServiceChain chain;
@Autowired
private RedisService redisService;
@Autowired
private AccountService accountService;
**//设置验签开关**
@Value("${xxx.dispatch.sign.check.enable:true}")
private boolean isSignCheck;
public Map<String, Object> process(String json, String sign, String remoteIp) {
JSONObject publicInfo = null;
try {
// 校验参数是否为空
if (StringUtils.isBlank(sign) || StringUtils.isBlank(json)) {
log.error("request message error have null,json:{} sign:{}", json, sign);
return this.getResponse(null, this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "签名或请求报文为空"));
}
JSONObject busiInfo = null;
JSONObject jsonObj = null;
try {
// 字符串转换成对象,字符串格式不符合json格式就会抛出异常,即参数错误,返回错误信息
jsonObj = JSON.parseObject(json);
publicInfo = jsonObj.getJSONObject("public");
busiInfo = jsonObj.getJSONObject("busiInfo");
} catch (Exception e) {
log.error("request message format error,remoteIp:{},json:{} sign:{}", remoteIp, json, sign);
return this.getResponse(null, this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "请求报文格式不正确"));
}
if (publicInfo == null || busiInfo == null) {
log.error("request message format error,remoteIp:{},json:{} sign:{}", remoteIp, json, sign);
return this.getResponse(publicInfo != null ? publicInfo.getString("transactionId") : null,
this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "请求报文格式不正确"));
}
// 公共参数校验
Map<String, Object> result = this.validateParam(publicInfo);
if (result != null) {
log.error("validate pafram fail,remoteIp:{},json:{} sign:{}", remoteIp, json, sign);
return this.getResponse(publicInfo.getString("transactionId"), result);
}
// 进行能力鉴权
ResultCodeEnum vr = this.auth(json, sign, publicInfo.getString("system"),
publicInfo.getString("messageType"), remoteIp);
if (vr != null) {
log.error("auth fail,remoteIp:{},json:{} sign:{}", remoteIp, json, sign);
return this.getResponse(publicInfo.getString("transactionId"),
this.getResult(vr.getKey(), vr.getDesc()));
}
jsonObj.put("remoteIp", remoteIp);
// 调用对应的服务
return chain.execute(json, sign, publicInfo.getString("messageType"), jsonObj);
} catch (Exception e) {
log.error(e.getMessage(), e);
return this.getResponse(publicInfo != null ? publicInfo.getString("transactionId") : null, this
.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), ResultCodeEnum.RESULT_EXCEPTION.getDesc()));
}
}
private Map<String, Object> getResponse(String transactionId, Map<String, Object> result) {
result.put("transactionId", transactionId);
Map<String, Object> ret = new HashMap<>(2);
ret.put("result", result);
return ret;
}
private Map<String, Object> getResult(String code, String message) {
Map<String, Object> result = new HashMap<>(5);
result.put("transactionTime", DateUtil.getDateTimeString());
result.put("code", code);
result.put("message", message);
return result;
}
/**
* 公共参数校验
*
* @param publicInfo
* @return
*/
protected Map<String, Object> validateParam(JSONObject publicInfo) {
// 验签是必须的,所以先进行验签,通过即进行下面操作
// system 就是对外的网元代码
String nodeCode = publicInfo.getString("system");
if (StringUtils.isBlank(nodeCode)) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "来源系统为空");
} else if (nodeCode.length() > 10) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "来源系统长度大于10位");
}
String abilityCode = publicInfo.getString("messageType");
if (StringUtils.isBlank(abilityCode)) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "接口编码为空");
}
String transactionId = publicInfo.getString("transactionId");
if (StringUtils.isBlank(transactionId)) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "请求流水为空");
} else if (transactionId.length() > 64) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "请求流水长度大于64位");
}
String transactionTime = publicInfo.getString("transactionTime");
if (StringUtils.isBlank(transactionTime)) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "请求时间为空");
} else if (transactionTime.length() != 14) {
return this.getResult(ResultCodeEnum.RESULT_EXCEPTION.getKey(), "请求时间错误");
}
return null;
}
/**
* 鉴权
*
* @param stringJson
* @param sign
* @param nodeCode
* @return
*/
private ResultCodeEnum auth(String stringJson, String sign, String nodeCode, String abilityCode, String ip) {
NodeVO node = (NodeVO) redisService.hget(MmcsRedisCons.NODE_HASH_KEY_CODE,
MessageFormat.format(MmcsRedisCons.NODE_OBJECT_KEY, nodeCode));
// 如果网元信息为空,并且状态为弃用状态,返回错误信息
if (null == node || node.getStatus() == 0) {
return ResultCodeEnum.RESULT_UNKNOWN_NODE;
}
if (isSignCheck) {
String outPublicKey = node.getOutPublicKey();
// 1:先验签
boolean verifySuccess = RsaUtil.verify(stringJson.getBytes(), outPublicKey, sign);
if (!verifySuccess) {
return ResultCodeEnum.RESULT_ILLEGAL;
}
}
// 判断是否还需要进行ip鉴权
int anthType = node.getAuthType();
if (AuthTypeEnum.AUTH_ABILITY_IP_TYPE.getPosition() == anthType) {
String addrs = node.getInAddrIp();
if (StringUtils.isBlank(addrs)) {
return ResultCodeEnum.RESULT_UNAUTH_IP;
}
String[] addr = addrs.split(MmcsCons.COMMA);
if (!Arrays.asList(addr).contains(ip)) {
return ResultCodeEnum.RESULT_UNAUTH_IP;
}
}
AbilityVO ability = (AbilityVO) redisService.hget(MmcsRedisCons.ABILITY_CODE_KEY,
MessageFormat.format(MmcsRedisCons.ABILITY_CODE_FILED, abilityCode));
if (null == ability || ability.getStatus() == 0) {
return ResultCodeEnum.RESULT_CLOSE_ABILITY;
}
AbilityAuthVO abilityAuth = (AbilityAuthVO) redisService.hget(MmcsRedisCons.ABILITY_AUTH_KEY,
MessageFormat.format(MmcsRedisCons.ABILITY_AUTH_FILED, ability.getAbilityId(), node.getNodeId()));
if (null == abilityAuth || abilityAuth.getStatus() == 0) {
return ResultCodeEnum.RESULT_UNAUTH_ABILITY;
}
// 流控
boolean fluxControl = fluxControl(nodeCode, abilityCode, node, abilityAuth);
if (!fluxControl) {
return ResultCodeEnum.RESULT_OVER_FLUX;
}
return null;
}
/**
* 流量控制
*
* @param nodeCode
* @param abilityCode
* @return
*/
private boolean fluxControl(String nodeCode, String abilityCode, NodeVO node, AbilityAuthVO abilityAuth) {
Integer perSecNodeNum = node.getFluxQuotaPerSec();
Integer perDayNodeNum = node.getFluxQuotaPerDay();
String dayTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DateUtil.DATE_FORMAT));
String secTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DateUtil.DATETIME_FORMAT));
Long nodeDayNum =
redisService.incrementNum(KeyEnum.NODE_DAY_REQUEST_NUM.getCode() + nodeCode + dayTime, 24 * 3600L);
Long nodeSecNum = redisService.incrementNum(KeyEnum.NODE_SEC_REQUEST_NUM.getCode() + nodeCode + secTime, 1L);
if (nodeSecNum > perSecNodeNum && perSecNodeNum != 0) {
return false;
}
if (nodeDayNum > perDayNodeNum && perDayNodeNum != 0) {
return false;
}
Integer perSecAbilityNum = abilityAuth.getFluxQuotaPerSec();
Integer perDayAlilityNum = abilityAuth.getFluxQuotaPerDay();
Long abilityDayNum = redisService.incrementNum(
KeyEnum.ABILITY_DAY_REQUEST_NUM.getCode() + nodeCode + MmcsCons.COLON + abilityCode + dayTime,
24 * 3600L);
Long abilitySecNum = redisService.incrementNum(
KeyEnum.ABILITY_SEC_REQUEST_NUM.getCode() + nodeCode + MmcsCons.COLON + abilityCode + secTime, 1L);
if (abilitySecNum > perSecAbilityNum && perSecAbilityNum != 0) {
return false;
}
if (abilityDayNum > perDayAlilityNum && perDayAlilityNum != 0) {
return false;
}
return true;
}
public void getAccountFile(String fileId, String sign, String remoteIp, HttpServletResponse response) {
try {
Map<String, Object> ret = null;
// 校验参数是否为空
if (StringUtils.isBlank(sign) || StringUtils.isBlank(fileId)) {
log.error("request message error have null,json:{} sign:{}", fileId, sign);
ret = this.getResponse(null, this.getResult(ResultCodeEnum.RESULT_ILLEGAL.getKey(), "签名或请求报文为空"));
} else {
// 调用对账get方法
ret = accountService.getfileId(fileId, sign, remoteIp, response);
if (ret == null) {
return;
}
}
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.write(JsonUtil.toString(ret));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
@Slf4j
public class RsaUtil {
/**
* 算法
*/
private static final String ALGORITHM = "RSA";
/**
* 签名算法
*/
private static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";
/**
* 转换:算法/模式/填充
*/
private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";
private RsaUtil() {
}
/**
* 签名
*
* @param data
* @param privateKey
* @return
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
* @throws SignatureException
* @throws InvalidKeyException
*/
public static String sign(byte[] data, String privateKey)
throws InvalidKeySpecException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
PKCS8EncodedKeySpec pkcs = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
PrivateKey key = factory.generatePrivate(pkcs);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(key);
signature.update(data);
return Base64.encodeBase64String(signature.sign());
}
/**
* 签名校验
*
* @param data
* @param publicKey
* @param sign
* @return
*/
public static boolean verify(byte[] data, String publicKey, String sign) {
try {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
PublicKey key = factory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(key);
signature.update(data);
return signature.verify(Base64.decodeBase64(sign));
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return false;
}
/**
* 公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密数据
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey)
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
PublicKey key = factory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* 私钥解密
*
* @param data 加密数据
* @param privateKey 私钥
* @return 解密数据
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static byte[] decryptByPrivateKey(byte[] data, String privateKey)
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
PrivateKey key = factory.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* 私钥加密
*
* @param data 待加密数据
* @param privateKey 私钥
* @return 加密数据
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey)
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
PrivateKey key = factory.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* 公钥解密
*
* @param data 加密数据
* @param publicKey 公钥
* @return 解密数据
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
*/
public static byte[] decryptByPublicKey(byte[] data, String publicKey)
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
PublicKey key = factory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(data);
}
}
第三步
匹配责任链,寻找具体方法
@Service
public class ServiceChain {
private static final List<AbstractSubscriptionService> LIST = new ArrayList<>();
public void addService(AbstractSubscriptionService service) {
LIST.add(service);
}
public Map<String, Object> execute(String json, String sign, String messageType, JSONObject jsonObj) {
for (AbstractSubscriptionService service : LIST) {
//如果去掉匹配,是不是就是责任链模式了(子类全跑一遍)
if (service.isMatch(messageType)) {
//依据messageType匹配具体的实现
return service.execute(json, sign, jsonObj);
}
}
return this.getResponse(jsonObj);
}
private Map<String, Object> getResponse(JSONObject jsonObj) {
Result result = new Result();
result.setCode(ResultCodeEnum.RESULT_EXCEPTION.getKey());
result.setMessage("接口编码错误");
result.setTransactionId(jsonObj.getJSONObject("public").getString("transactionId"));
result.setTransactionTime(DateUtil.getDateTimeString());
Map<String, Object> ret = new HashMap<>(2);
ret.put("result", result);
return ret;
}
}
下一步
public abstract class AbstractSubscriptionService {
@Autowired
private ServiceChain chain;
@Autowired
protected RunLoggerChain logChain;
@PostConstruct
private void init() {
chain.addService(this);
}
/**
* 是否匹配
*
* @param messageType
* @return
*/
public abstract boolean isMatch(String messageType);
/**
* 逻辑处理
*
* @param json
* @param sign
* @param jsonObj
* @return
*/
public abstract Map<String, Object> execute(String json, String sign, JSONObject jsonObj);
protected Map<String, Object> getDefaultResponse(String transactionId, String code, String message) {
Map<String, Object> ret = new HashMap<>(2);
ret.put("result", this.getResult(transactionId, code, message));
return ret;
}
protected Map<String, Object> getResult(String transactionId, String code, String message) {
Map<String, Object> result = new HashMap<>(6);
result.put("transactionId", transactionId);
result.put("transactionTime", DateUtil.getDateTimeString());
result.put("code", StringUtils.hasText(code) ? code : ResultCodeEnum.RESULT_EXCEPTION.getKey());
result.put("message", message);
return result;
}
}
@Service
@Slf4j
public class UnifiedPlatformTokenService extends AbstractSubscriptionService {
@Autowired
private CmsBackendClient cmsBackendClient;
@Override
public boolean isMatch(String messageType) {
return InterfaceTypeEnum.UNIFIED_PLATFORM_TOKEN.getCode().equals(messageType);
}
@Override
public Map<String, Object> execute(String json, String sign, JSONObject jsonObj) {
// 记录第三方的请求运行日志
logChain.logRequest(InterfaceTypeEnum.UNIFIED_PLATFORM_TOKEN, jsonObj, sign);
JSONObject busiInfo = jsonObj.getJSONObject("busiInfo");
JSONObject publicInfo = jsonObj.getJSONObject("public");
String transactionId = publicInfo.getString("transactionId");
String transactionTime = publicInfo.getString("transactionTime");
String system = publicInfo.getString("system");
Map<String, Object> ret;
String user = busiInfo.getString("user");
String passwd = busiInfo.getString("passwd");
String operatorId = busiInfo.getString("operatorId");
String vr = this.checkParam(user, passwd, transactionTime,operatorId);
if (vr != null) {
log.error("validate param fail,json:{},sign:{}", json, sign);
ret = this.getDefaultResponse(transactionId, ValidateMessageEnum.VALIDATE_FAIL.getCode(), vr);
} else {
//fegin的调用第三方或底层获取token
CallResult<String> callResult = cmsBackendClient.queryLoginToken(user, passwd, system,operatorId);
log.info("queryLoginToken response:{}", callResult);
if (callResult != null) {
if (callResult.isSuccess()) {
ret = getResponse(transactionId, ResultCodeEnum.RESULT_SUCCESS.getKey(),
ResultCodeEnum.RESULT_SUCCESS.getDesc(), callResult.getData());
} else {
String code = callResult.getCode() == null ? ResultCodeEnum.RESULT_EXCEPTION.getKey() : callResult.getCode();
ret = getResponse(transactionId, code, callResult.getMessage(), callResult.getData());
}
} else {
ret = getResponse(transactionId, ResultCodeEnum.RESULT_EXCEPTION.getKey(),
ResultCodeEnum.RESULT_EXCEPTION.getDesc(), null);
}
}
//记录响应运行日志
logChain.logResponse(InterfaceTypeEnum.UNIFIED_PLATFORM_TOKEN, ret, null);
return ret;
}
private String checkParam(String user, String passwd, String transactionTime,String operatorId) {
if (!StringUtils.hasText(user)) {
return "登录用户名校验失败";
}
if (!StringUtils.hasText(passwd)) {
return "登录密码校验失败";
}
// 请求参数中的时间和当前时间比较,如果超过当前时间5分钟,则请求失效
if (!StringUtils.hasText(transactionTime) || !isOverNowTime(transactionTime)) {
return "请求时间超过有效期";
}
if (!StringUtils.hasText(operatorId)) {
return "操作员工号校验失败";
}
return null;
}
/**
* 是否超过当前时间5分钟
*
* @param transactionTime
* @return
*/
private boolean isOverNowTime(String transactionTime) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
Date now = new Date();
//5分钟
long time = 5L * 60L * 1000L;
//5分钟前的时间
Date beforeDate = new Date(now.getTime() - time);
String beforeTime = sdf.format(beforeDate);
boolean flag = transactionTime.compareTo(beforeTime) >= 0 ? true : false;
return flag;
}
private Map<String, Object> getResponse(String transactionId, String code, String message, String returnInfo) {
Map<String, Object> ret = new HashMap<>(4);
ret.put("result", this.getResult(transactionId, code, message));
ret.put("returnInfo", this.getResultInfo(returnInfo));
return ret;
}
protected Map<String, Object> getResultInfo(String accessToken) {
Map<String, Object> result = new HashMap<>(2);
result.put("accessToken", accessToken);
return result;
}
}
底层获取token
忽略fegin的写法,直接写service
@Slf4j
@Service
public class CustomerLoginServiceImpl implements CustomerLoginService {
@Autowired
RSADataSecurityImpl rsaDataSecurity;
@Autowired
private RedisService redisService;
/**
* token获取地址
*/
@Value("${ssss.web.sso.getToken}")
protected String getTokenUrl;
/**
* 登录获取sessionID地址
*/
@Value("${ssss.web.sso.verify}")
protected String verifyUrl;
/**
* 登录重定向地址bas
*/
@Value("${aspire.web.sso.redirectUrl}")
protected String redirectUrl;
@Override
public CallResult<String> queryLoginToken(String user, String passwd, String system, String operatorId) {
CallResult<String> result = new CallResult<>();
//解密密码
NodeVO node = (NodeVO) redisService.hget(MmcsRedisCons.NODE_HASH_KEY_CODE,
MessageFormat.format(MmcsRedisCons.NODE_OBJECT_KEY, system));
// 如果网元信息为空,并且状态为弃用状态,返回错误信息
if (null == node || node.getStatus() == 0) {
log.error("查询网元信息失败!!!system:{}", system);
result.setCode(ResultCodeEnum.RESULT_UNKNOWN_NODE.getKey());
result.setMessage(ResultCodeEnum.RESULT_UNKNOWN_NODE.getDesc());
return result;
}
try {
passwd = new String(RsaUtil.decryptByPublicKey(Base64.decodeBase64(passwd), node.getOutPublicKey()));
} catch (Exception e) {
log.error("获取网页token,解密失败");
result.setMessage("获取网页token,解密失败");
return result;
}
//对用户名进行加密
user = rsaDataSecurity.encrypt(user);
**//获取token**
String token = sendPostMessage(user);
//请求第二个登录
passwd = rsaDataSecurity.encrypt(passwd);
String sessionId = sendPostVerify(token, user, passwd);
if (!StringUtils.isEmpty(sessionId)) {
result.setSuccess(true);
result.setCode(ResultCodeEnum.RESULT_SUCCESS.getKey());
result.setMessage(ResultCodeEnum.RESULT_SUCCESS.getDesc());
}else{
result.setCode(ResultCodeEnum.RESULT_ERROR_USER_PASSWORD.getKey());
result.setMessage(ResultCodeEnum.RESULT_ERROR_USER_PASSWORD.getDesc());
}
result.setData(sessionId);
//操作员工号设置到redis中
String operatorIdField = MessageFormat.format(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID_FILED, sessionId);
/**
* 操作员工号配置
* String CUSTOMER_LOGIN_OPERATOR_ID_KEY = "customer_login_operator_id";
*/
/**
* 操作员工号配置filed 0:sessionID
* String CUSTOMER_LOGIN_OPERATOR_ID_FILED = "{0}";
*/
redisService.hset(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID_KEY, operatorIdField, operatorId);
//操作员工号设置到redis中
String systemField = MessageFormat.format(MmcsRedisCons.CUSTOMER_LOGIN_SYSTEM_FILED, sessionId);
redisService.hset(MmcsRedisCons.CUSTOMER_LOGIN_SYSTEM_KEY, systemField, system);
return result;
}
/**
* 请求发消息
*
* @param user
*/
private String sendPostMessage(String user) {
String token = null;
HttpRequest request = new HttpRequest();
request.setHeader("Content-Type", "application/json");
request.setUrl(getTokenUrl);
request.setBody(this.getPushBody(user));
log.info("get sso request: {}", request);
HttpResponse resp = HttpUtil.doPost(request);
log.info("get sso response: {}", resp);
if (resp.isSuccess()) {
JSONObject obj = JSON.parseObject(resp.getBody());
String status = "status";
if (!SsoCons.SSO_RETURN_SUCCESS.equals(obj.getString(status))) {
log.error("send post message error,next send username:{}", user);
throw new RuntimeException("请求WEB_BAS token失败");
}
token = obj.getJSONObject("data").getString("token");
}
return token;
}
/**
* 请求发消息,登录
*
* @param user
*/
private String sendPostVerify(String token, String user, String passwd) {
String sessionId = null;
HttpRequest request = new HttpRequest();
request.setUrl(verifyUrl);
request.getParameter().put("token", token);
request.getParameter().put("username", user);
request.getParameter().put("password", passwd);
request.getParameter().put("domain", "admin");
request.getParameter().put("redirectUrl", redirectUrl);
log.info("get sso verify request: {}", request);
HttpResponse resp = HttpUtil.doGet(request);
log.info("get sso verify response: {}", resp);
if (resp.isSuccess()) {
if (!SsoCons.SSO_RETURN_SUCCESS.equals(String.valueOf(resp.getStatusCode()))) {
log.error("send post message error,next send username:{}", user);
throw new RuntimeException("请求WEB_BAS verify失败");
}
sessionId = resp.getHeader().get("sessionId");
}
return sessionId;
}
/**
* 推送消息包
*
* @param user
* @return
*/
private String getPushBody(String user) {
Map<String, Object> bodyMap = new HashMap<>(3);
bodyMap.put("username", user);
bodyMap.put("domain", "admin");
return JsonUtil.toString(bodyMap);
}
}
请求post方法,不同的post请求,可设置不同的参数,Cookie的设置
到时候就可以取出来
/**
* @author xxg
*/
@Slf4j
public class HttpUtil {
/**
* url与请求参数间的分隔符
*/
private static final String URL_PARAMETER_SEPARATOR = "?";
/**
* 名称与值间的分隔符
*/
private static final String NAME_VALUE_SEPARATOR = "=";
/**
* 参数间分隔符
*/
private static final String PARAMETER_SEPARATOR = "&";
private HttpUtil() {}
/***
* get请求
*
* @param request
* @throws Exception
*/
public static HttpResponse doGet(HttpRequest request) {
try {
HttpGet get = new HttpGet(getUrl(request));
get.setConfig(getRequestConfig(request));
setHeader(request, get);
return execute(request, get);
} catch (Exception e) {
log.error("http get exception", e);
return getErrorHttpResponse(e);
}
}
/***
* post请求
*
* @param request
* @throws Exception
*/
public static HttpResponse doPost(HttpRequest request) {
try {
HttpPost post = new HttpPost(getUrl(request));
post.setConfig(getRequestConfig(request));
setHeader(request, post);
StringEntity entity = new StringEntity(request.getBody(), request.getCharset());
post.setEntity(entity);
return execute(request, post);
} catch (Exception e) {
log.error("http doPost exception", e);
return getErrorHttpResponse(e);
}
}
/**
* 发送请求数据
*
* @param request
* @param httpUriRequest
* @return
*/
private static HttpResponse execute(HttpRequest request, HttpUriRequest httpUriRequest) {
CloseableHttpClient httpclient = null;
try {
CookieStore httpCookieStore = new BasicCookieStore();
httpclient = HttpClientBuilder.create().setDefaultCookieStore(httpCookieStore).build();
CloseableHttpResponse response = httpclient.execute(httpUriRequest);
try {
StatusLine statusLine = response.getStatusLine();
HttpResponse resp = new HttpResponse();
resp.setSuccess(statusLine.getStatusCode() == HttpStatus.SC_OK);
resp.setStatusCode(statusLine.getStatusCode());
resp.setBody(EntityUtils.toString(response.getEntity(), request.getCharset()));
//get cookie
List<Cookie> cookies = httpCookieStore.getCookies();
for (int i = 0; i < cookies.size(); i++) {
//这里是读取Cookie['JSESSIONID']的值存在静态变量中,保证每次都是同一个值
if ("JSESSIONID".equals(cookies.get(i).getName())) {
Map<String ,String> map = new HashMap<>(2);
map.put("sessionId",cookies.get(i).getValue());
resp.setHeader(map);
}
}
return resp;
} finally {
response.close();
}
} catch (Exception e) {
log.error("http execute exception,request:{}", request, e);
return getErrorHttpResponse(e);
} finally {
try {
if (httpclient != null) {
httpclient.close();
}
} catch (IOException e) {
log.error("httpclient close exception,request:{}", request, e);
}
}
}
/**
* 获取url
*
* @param request
* @return
* @throws UnsupportedEncodingException
*/
private static String getUrl(HttpRequest request) throws UnsupportedEncodingException {
if (request.getParameter() == null || request.getParameter().size() == 0) {
return request.getUrl();
}
StringBuilder result = new StringBuilder(request.getUrl());
if (request.getUrl().contains(URL_PARAMETER_SEPARATOR)) {
result.append("&");
} else {
result.append(URL_PARAMETER_SEPARATOR);
}
result.append(formatParameterEntity(request.getParameter(), request.getCharset()));
return result.toString();
}
/**
* http连接参数配置
*
* @param request
* @return
*/
private static RequestConfig getRequestConfig(HttpRequest request) {
return RequestConfig.custom().setConnectTimeout(getSocketConnectTimeout(request))
.setSocketTimeout(getSocketTimeout(request)).build();
}
/**
* 格式化键值对数据(urlencode)
*
* @param parameterMap
* @return
* @throws UnsupportedEncodingException
*/
private static String formatParameterEntity(Map<String, String> parameterMap, String charset)
throws UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
for (Entry<String, String> entry : parameterMap.entrySet()) {
if (result.length() > 0) {
result.append(PARAMETER_SEPARATOR);
}
result.append(URLEncoder.encode(entry.getKey(), charset));
result.append(NAME_VALUE_SEPARATOR);
result.append(entry.getValue() != null ? URLEncoder.encode(entry.getValue(), charset) : "");
}
return result.toString();
}
/**
* 设置http请求头
*
* @param request
* @param httpMessage
*/
private static void setHeader(HttpRequest request, HttpMessage httpMessage) {
for (Entry<String, String> entry : request.getHeader().entrySet()) {
httpMessage.setHeader(entry.getKey(), entry.getValue());
}
}
/**
* 获取socket读写超时时间
*
* @param request
* @return
*/
private static int getSocketConnectTimeout(HttpRequest request) {
String timeout = request.getConfig().get(HttpConfigConst.SOCKET_CONNECT_TIMEOUT);
return StringUtils.hasText(timeout) ? Integer.parseInt(timeout) : 3000;
}
/**
* 获取socket连接建立超时时间
*
* @param request
* @return
*/
private static int getSocketTimeout(HttpRequest request) {
String timeout = request.getConfig().get(HttpConfigConst.SOCKET_TIMEOUT);
return StringUtils.hasText(timeout) ? Integer.parseInt(timeout) : 3000;
}
/**
* 内部异常响应
*
* @return
*/
private static HttpResponse getErrorHttpResponse(Exception e) {
HttpResponse resp = new HttpResponse();
if (e instanceof SocketTimeoutException) {
resp.setStatusCode(HttpStatus.SC_OK);
} else {
resp.setStatusCode(999);
}
return resp;
}
}
取数据,设置到cookit中,看实现
/**
* 获取加密后的号段
* @param section
* @param request
* @return
*/
private String getDecryptSectionNumber(String section, HttpServletRequest request) {
String sessionId = request.getSession().getId();
if (StringUtils.isEmpty(sessionId)) {
return null;
}
//取系统编号
String system = (String) redisService.hget(MmcsRedisCons.CUSTOMER_LOGIN_SYSTEM_KEY,
MessageFormat.format(MmcsRedisCons.CUSTOMER_LOGIN_SYSTEM_FILED, sessionId));
//解密密码
NodeVO node = (NodeVO) redisService.hget(MmcsRedisCons.NODE_HASH_KEY_CODE,
MessageFormat.format(MmcsRedisCons.NODE_OBJECT_KEY, system));
if (null == node || node.getStatus() == 0) {
log.error("查询网元信息失败!!!system:{}", system);
return null;
}
try {
section = new String(RsaUtil.decryptByPublicKey(Base64.decodeBase64(section), node.getOutPublicKey()));
} catch (Exception e) {
log.error("获取操作员工号,解密失败");
return null;
}
return section;
}
/**
* 获取操作员工号 从session中取,取不到再去数据库取
* @param request
* @return
*/
private String getOperatorId(HttpServletRequest request) {
String sessionId = request.getSession().getId();
String operatorId= (String) request.getSession().getAttribute(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID);
if(StringUtils.isEmpty(operatorId)){
operatorId = (String) redisService.hget(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID_KEY,
MessageFormat.format(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID_FILED, sessionId));
if (StringUtils.isNotEmpty(operatorId)) {
request.getSession().setAttribute(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID, operatorId);
//获取后删除缓存
redisService.hdel(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID_KEY, MessageFormat.format(MmcsRedisCons.CUSTOMER_LOGIN_OPERATOR_ID_FILED, sessionId));
}
}
return operatorId;
}
远程调试:https://www.jianshu.com/p/302dc10217c0
遇到了问题
iframe中嵌套其他资源,引起跨域,在IE和Chrome中不可以把sessionId或token设置到header或cookit中,火狐可以。
a.一体化中嵌入了我们的页面,iframe中带了token,b.请求到-Nginx-我们系统。c.Nginx到我们系统前,我们会把sessionId或token取出放到cookit中,d.这样浏览器就安全鉴权通过,到后台。–问题就是,c步骤在浏览器中设置可以,嵌套在iframe时,无法设置,浏览器禁止。
(,用的token传入,nginx会去取这个值)
所以:
问题解决,就是,利用b步骤,Nginx做转发,到我们系统的时候,在Nginx中设置cookit,就是浏览器不让设置,Nginx里面设置。
nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
--加了日志打印,看是否能在nginx中拿到请求参数和值
log_format main '$http_cookie $request';
upstream mmcsc{
server 127.0.0.1:8014;
}
server {
--这是入口,地址,
listen 30340;
server_name 127.0.0.1;
location ~/web {
--转发到这个,就是下面的
proxy_pass http://10.1.123.65:8016;
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
}
location ~/mmcsm/ {
proxy_pass http://10.1.123.65:8016;
--打印日志,创建test.log,打印main的值
access_log logs/test.log main;
--增加header,往这里吗设置参数
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
--关键的是,加了这个。1、设置JessionId,2防止注入,正则匹配数字和字母,3、在cookit中设置,jsessionId,页面再取,看index.html
set $JSESSIONID "";
if ( $query_string ~* "token=([0-9A-Z]+)" ) {
set $JSESSIONID $1;
}
proxy_set_header Cookie "JSESSIONID=$JSESSIONID;$http_cookie";
}
location ~/mmcs/ {
proxy_pass http://127.0.0.1:8016;
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
}
location ~/mmcsc/api{
proxy_pass http://mmcsc;
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
}
location ~/mmcsc/xw{
proxy_pass http://mmcsc;
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
}
location ~/mmcsc/order{
proxy_pass http://mmcsc;
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
}
location ~/mmcsc/login{
proxy_pass http://mmcsc;
add_header From localhost;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;
client_max_body_size 1000m;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
页面取值
msisdnKey = msisdnKey.replace(/\+/g,"%2B")
post('/cms/queryDecryptMsisdn.ajax?token='+localStorage.getItem('JSESSIONID')+'&msisdn='+msisdnKey).then(res => {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>查询</title>
<!--引入 element-ui 的样式-->
<script src="js/babel.min.js"></script>
<script src="js/polyfill.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/element.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="js/element.js"></script>
<script src="js/axios.js"></script>
<script src="js/util.js" type="text/babel"></script>
<script src="vendor/base64.min.js" type="text/babel"></script>
<!-- <script type="text/babel" src="directive/index.js"></script> -->
<style>
.el-table__header{ width: 100% !important; } .el-table__body{ width: 100% !important; }
</style>
</head>
<body>
<div id="interest" class="webbas">
<div class="list-page-content-css">
<section class="list-query-area-css" style="font-size:14px">
<el-form :inline="true">
<el-row :gutter="20">
<el-col :span="7">
<el-form-item label="电话号码:">
<el-input v-model="msisdn" placeholder="请输入手机号码" class="input-with-select" :disabled="true">
<el-button slot="append" icon="el-icon-search" @click="getTableData(true)"></el-button>
</el-input>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item>
<span style="color:blue" @click="toHistory()">全部领取历史记录</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="list-table-css" style="margin-top:20px">
<el-table
v-loading="tableLoading"
:data="tableData"
min-height="300"
>
<el-table-column prop="interestsName" label="产品名称"></el-table-column>
<el-table-column prop="serviceName" label="权益名称">
<template slot-scope="scope">
<template v-if="scope.row.orderState === '03'">
<labal v-for="(item) in scope.row.subServiceInfoList" :key="item.subServiceId" type="text" disabled>
<template v-if="item.subServiceId === scope.row.selectServiceId">
{{ item.icpName }}
</template>
</labal>
</template>
<el-select v-else v-model="scope.row.subServiceId" placeholder="请选择权益" >
<el-option v-for="(item) in scope.row.subServiceInfoList" :key="item.subServiceId" :label="item.serviceName" :value="item.subServiceId+','+item.recvDataInput"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="orderTime" label="生效时间"></el-table-column>
<el-table-column prop="expireTime" label="失效时间"></el-table-column>
<el-table-column label="代领取">
<template slot-scope="scope">
<el-button v-if="scope.row.orderState === '03'" type="text" disabled>已领取</el-button>
<!-- <el-button v-hasAuth="doAuth({btnCode:'mmcs_030105'})" v-else type="text" @click="recv(scope.row.subscriptionId,scope.row.subServiceId,scope.row.orderTime)">领取</el-button> -->
</template>
</el-table-column>
<el-table-column label="领取记录">
<template slot-scope="scope">
<span style="color:blue" @click="toHistory(scope.row.serviceId,scope.row.subscriptionId)">领取记录</span>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="paginationStatus" class="list-pagination-css">
<el-pagination
:current-page.sync="pagination.page"
:page-size="pagination.pagesize"
:page-sizes="pagination.selectPagesizes"
:total="pagination.total"
:layout="paginationLayout"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
</div>
</section>
</div>
</div>
</body>
<script type="text/babel">
new Vue({
el: '#interest',
data(){
return{
msisdn: '',
tableData: [],
tableLoading: false,
paginationStatus: false,
interestNames:[],
pagination:{
page: 1,
total: 0,
pagesize: 10,
selectPagesizes: [10,20,30,40,50]
},
paginationLayout:"total, prev, pager, next, sizes, jumper"
}
},
created(){
// setCookie('JSESSIONID',GetQueryString('token'),1)
localStorage.setItem('JSESSIONID',GetQueryString('token'))
let msisdnKey = GetQueryString('msisdn')
if(msisdnKey){
this.decryptMsisdn(msisdnKey)
}
let recvInfo = sessionStorage.getItem('recv:searchAndTableInfo')
if(recvInfo){
let infoObj = JSON.parse(recvInfo)
this.msisdn = infoObj.msisdn
this.tableData = infoObj.tableData
this.pagination = infoObj.pagination
this.paginationStatus = true
}
},
methods: {
/**
* 按钮权限
* @method doAuth
* @param {object} opt
* @return {object}
*/
doAuth(opt) {
let routeMeta = this.$route.meta
return {menuId: routeMeta.id, btnCode: opt.btnCode}
},
decryptMsisdn(msisdnKey){
msisdnKey = msisdnKey.replace(/\+/g,"%2B")
post('/cms/queryDecryptMsisdn.ajax?token='+localStorage.getItem('JSESSIONID')+'&msisdn='+msisdnKey).then(res => {
if(res.status.toString() !== '200'){
if(res.status.toString() === '401'){
window.location.href = './login.html'
}
this.$message.error(res.message)
return
}
this.msisdn = res.data
this.getTableData(true)
})
},
getTableData(flag){
this.paginationStatus = false
if(flag){
this.pagination.page = 1
}
if(!this.msisdn){
this.$message.error('请输入手机号!')
return
}
this.queryMsisdn().then(res => {
if(res.status.toString() !== '200'){
if(res.status.toString() === '401'){
window.location.href = './login.html'
}
this.$message.error(res.message)
return
}
var param = {
msisdn:this.msisdn,
page : this.pagination.page,
rows : this.pagination.pagesize
}
this.tableLoading = true
post('/cms/querySubscription.ajax?token='+localStorage.getItem('JSESSIONID'),param).then(res => {
if(res.status.toString() !== '200'){
this.$message.error(res.message)
return
}
if(res.total > 0){
this.paginationStatus = true
}
this.tableData = res.data
this.pagination.total = res.total
this.tableLoading = false
})
})
},
recv(subscriptionId,subServiceId,orderTime){
if(!subServiceId){
this.$message.error('权益名称不能为空!')
return
}
let subServiceParam = subServiceId.split(',')
if(subServiceParam[1] !== "0"){
this.$message.error('该权益暂不支持代领取!')
return
}
let param = {
subscriptionId:subscriptionId,
selectServiceId:subServiceParam[0],
orderTime:orderTime
}
post('/cms/receiveCoupon.ajax',param).then(res =>{
if(res.status.toString() !== '200'){
this.$message.error(res.message)
return
}
this.$message.success(res.message)
})
},
queryMsisdn(){
let param = {
section:this.msisdn,
provinceCode: '025'
}
return post('/cms/queryMsisdn.ajax?token='+localStorage.getItem('JSESSIONID'),param)
},
toHistory(serviceId,subscriptionId){
if(!this.msisdn){
this.$message.error('请输入手机号!')
return
}
let historyUrl = 'history.html?msisdn='+Base64.encode(this.msisdn)
if(serviceId){
historyUrl += '&serviceId='+Base64.encode(serviceId)+'&subscriptionId='+Base64.encode(subscriptionId)
}
let recvInfo = {
msisdn:this.msisdn,
tableData:this.tableData,
pagination:this.pagination
}
sessionStorage.setItem('recv:searchAndTableInfo',JSON.stringify(recvInfo))
window.location.href = historyUrl
},
handleSizeChange(val){
this.pagination.pagesize = val
this.getTableData(true)
},
handleCurrentChange(val) {
this.pagination.page = val
this.getTableData(false)
}
}
})
</script>
</html>