一、需求背景
java后端需要提供接口服务,其中接口服务分为对内网的后台管理系统的接口,对外网的用户接口和对第三方系统的接口。这里主要讲对第三方的接口。
二、接口设计
我们可以参考微信小程序的接口,一般一个系统提供给第三方系统的接口都需要接口权限认证,也就是先获取token,然后通过token再进行接口数据请求。这是为了保障数据的安全性。这是第三方接口设计的基本规范。
其中token的获取是通过appid和秘钥等信息去请求微信的后端,这个appid就相当于是一个账号,秘钥就相当于是一个密码。其原理就是相当于只有登录了系统才能请求系统的接口。
那么如果我们不制作token,使用一种简易的方式做接口设计怎么设计呢。
token其实就是一个有实效的令牌,我们简易的做法可以使用登录账号和密码直接登录,每次去请求接口都传输账号和密码就可以了,但是这里需要注意的是传参一定要加密。
三、接口设计实施
1、我们需要在数据库里建一个接口用户表,表字段包含账号和密码字段等。
-- Drop table
-- DROP TABLE public.m_token_user;
CREATE TABLE public.m_token_user (
id varchar(64) NOT NULL,
user_name varchar(64) NULL,
"password" varchar(64) NULL,
CONSTRAINT m_token_user_pk PRIMARY KEY (id)
);
2、编写校验登录账号代码流程。
实体类:
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 数据库表名称:m_token_user (外部系统用户接口认证表)
*/
@Data
@EqualsAndHashCode()
@TableName(value="m_token_user",schema = "public")
public class MTokenUser implements Serializable{
/**
* 序列
*/
private static final long serialVersionUID = 1L;
/**
*
* 主键:id VARCHAR(64)
*/
@TableId(value = "id" ,type = IdType.ASSIGN_UUID)
@ApiModelProperty(value = "ID")
private String id;
/**
*
* 用户名:user_name VARCHAR(64)
*/
private String userName;
/**
*
* 密码:password VARCHAR(64)
*/
private String password;
}
Service类:
import cn.ctg.common.util.PageUtils;
import cn.ctg.common.util.Query;
import org.springframework.beans.factory.annotation.Autowired;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.ctg.member.entity.MTokenUser;
import cn.ctg.member.dao.MTokenUserMapper;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
/**
* @Description: 外部系统用户接口认证Service
*/
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class MTokenUserService extends ServiceImpl<MTokenUserMapper, MTokenUser>{
@Autowired
private MTokenUserMapper mTokenUserMapper;
/**
* 根据账号和密码查询
* searchByUserName
*/
public MTokenUser searchByUserName(String userName,String password) {
QueryWrapper<MTokenUser> wrapper = new QueryWrapper<MTokenUser>();
wrapper.eq("user_name", userName);
wrapper.eq("password", password);
MTokenUser mTokenUser = mTokenUserMapper.selectOne(wrapper);
return mTokenUser;
}
}
RestFul第三方接口:
Controller类示例
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import cn.ctg.common.encryption.Des3Utils;
import cn.ctg.common.response.ResponseData;
import cn.ctg.member.dto.WXModelDTO;
import cn.ctg.member.entity.MTokenUser;
import cn.ctg.member.service.MTokenUserService;
import cn.ctg.member.service.WeixinAppService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
/**
* @Description:
* @date:2023年5月30日
*/
@Slf4j
@RestController
@RequestMapping("rpcApi")
@Api(tags = "对外API")
@Scope(value = "prototype")
public class RpcApiController {
/**
* 3DES秘钥(通过配置获取)
*/
@Value("${sbx.3desKey:-1}")
private String desKey;
@Autowired
private WeixinAppService weixinAppService;
@Autowired
private MTokenUserService mTokenUserService;
/**
*
* @return Map<String, Object>
*/
@ApiOperation(value = "远程注册用户会员", notes = "远程注册用户会员")
@PostMapping("/regUser")
public ResponseData<String> regUser(@RequestBody Map<String, Object> receiveParam) {
//解密参数
String data = "";
if(receiveParam.containsKey("data")) {
data = receiveParam.get("data").toString();
}else {
return ResponseData.error("参数格式错误");
}
String receiveData = Des3Utils.get3DESDecryptECB(data, desKey);
log.info("3des后receiveData注册数据:" + receiveData);
if(StringUtils.isEmpty(receiveData)) {
return ResponseData.error("参数数据或格式错误");
}
@SuppressWarnings("unchecked")
Map<String, Object> param = JSON.parseObject(receiveData, Map.class);
String phone = "";
String countryCode = "";
String openId = "";
String registerId = "";
String registerName = "";
String inviter = "";
String channelId = "";
String channelName = "";
String company = "";
String account = "";
String password = "";
if(param.containsKey("account")) {
account = param.get("account").toString();
}else {
return ResponseData.error("授权账号为空");
}
if(param.containsKey("password")) {
password = param.get("password").toString();
}else {
return ResponseData.error("授权密码为空");
}
log.info("授权账号:"+account+",授权密码:"+password);
//授权认证
MTokenUser mTokenUser = mTokenUserService.searchByUserName(account, password);
if(null == mTokenUser) {
return ResponseData.error("授权失败,账号或密码错误");
}
if(param.containsKey("phone")) {
phone = param.get("phone").toString();
}else {
return ResponseData.error("手机号不能为空");
}
if(param.containsKey("countryCode")) {
countryCode = param.get("countryCode").toString();
}else {
return ResponseData.error("手机国家号不能为空");
}
if(param.containsKey("openId")) {
openId = param.get("openId").toString();
}else {//拼接生成
openId =countryCode+"-"+phone;
}
if(param.containsKey("registerId")) {
registerId = param.get("registerId").toString();
}
if(param.containsKey("registerName")) {
registerName = param.get("registerName").toString();
}
if(param.containsKey("inviter")) {
inviter = param.get("inviter").toString();
}
if(param.containsKey("channelId")) {
channelId = param.get("channelId").toString();
}
if(param.containsKey("channelName")) {
channelName = param.get("channelName").toString();
}
if(param.containsKey("company")) {
company = param.get("company").toString();
}
WXModelDTO wXModelDTO = weixinAppService.rpcRegUser(phone, countryCode, openId, registerId, registerName, inviter, channelId, channelName, company);
String str = Des3Utils.get3DESEncryptECB(JSON.toJSONString(wXModelDTO), desKey);
return ResponseData.success(str);
}
public static void main(String[] args) {
Map<String, Object> param = new HashMap<String, Object>();
param.put("phone", "13657**3364");
param.put("openId", "432423");
param.put("countryCode", "86");
param.put("account", "测试");
param.put("password", "测试的密码");
String enStr = Des3Utils.get3DESEncryptECB(JSON.toJSONString(param), "秘钥");
String deStr = Des3Utils.get3DESDecryptECB(enStr, "秘钥");
Map map = JSON.parseObject(deStr,Map.class);
System.out.println("enStr:"+enStr);
System.out.println("deStr:"+deStr);
System.out.println("map:"+map);
}
}
加解密工具类Des3Utils.java
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.util.encoders.Hex;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
/**
* @Description:
* @Company:ctg.cn
* @date:2023年5月30日
*/
@Slf4j
public class Des3Utils {
/**
* 加密算法
*/
private static final String KEY_ALGORITHM = "DESede";
private static final String CIPHER_ALGORITHM = "DESede/CBC/PKCS5Padding";
/**
* 3DES 加密
* @param key 秘钥(24位)
* @param iv 偏移量
* @param data 需要加密的字符串
* @return 返回加密的字符串
*/
public static String encrypt(String key, String iv, String data) {
try {
DESedeKeySpec spec = new DESedeKeySpec(key.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
Key deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ips = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.ENCRYPT_MODE, deskey, ips);
byte[] bOut = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
Base64.Encoder encoder = Base64.getMimeEncoder();
return encoder.encodeToString(bOut);
} catch (Exception e) {
e.printStackTrace();
log.error("3DES 解密错误:{}", e);
throw new RuntimeException("3DES 解密错误");
}
}
/**
* 3DES 解密
* @param key 秘钥(24位)
* @param iv 偏移量
* @param data 需要解密的密文
* @return 返回加密的字符串
*/
public static String decrypt(String key, String iv, String data) {
try {
DESedeKeySpec spec = new DESedeKeySpec(key.getBytes(StandardCharsets.UTF_8));
SecretKeyFactory keyfactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
Key deskey = keyfactory.generateSecret(spec);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ips = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, deskey, ips);
byte[] bOut = cipher.doFinal(getBase64Decode(data));
return new String(bOut, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
log.error("3DES 解密错误:{}", e);
throw new RuntimeException("3DES 解密错误");
}
}
/**
* 3DES加密
* @param src
* @param secretKey
* @return
*/
public static String get3DESEncryptECB(String src, String secretKey) {
try {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(1, new SecretKeySpec(build3DesKey(secretKey), "DESede"));
String base64Encode = getBase64Encode(cipher.doFinal(src.getBytes("UTF-8")));
return filter(base64Encode);
} catch (Exception var4) {
return null;
}
}
/**
* 3DES解密
* @param src
* @param secretKey
* @return
*/
public static String get3DESDecryptECB(String src, String secretKey) {
try {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(2, new SecretKeySpec(build3DesKey(secretKey), "DESede"));
byte[] base64DValue = getBase64Decode(src);
byte[] ciphertext = cipher.doFinal(base64DValue);
return new String(ciphertext, "UTF-8");
} catch (Exception var5) {
return null;
}
}
public static byte[] getBase64Decode(String str) {
byte[] src = null;
try {
Base64.Decoder decoder = Base64.getMimeDecoder();
src = decoder.decode(str);
} catch (Exception var3) {
var3.printStackTrace();
}
return src;
}
public static String getBase64Encode(byte[] src) {
String requestValue = "";
try {
Base64.Encoder encoder = Base64.getMimeEncoder();
requestValue = filter(encoder.encodeToString(src));
} catch (Exception var3) {
var3.printStackTrace();
}
return requestValue;
}
public static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException {
byte[] key = new byte[24];
byte[] temp = keyStr.getBytes("UTF-8");
if (key.length > temp.length) {
System.arraycopy(temp, 0, key, 0, temp.length);
} else {
System.arraycopy(temp, 0, key, 0, key.length);
}
return key;
}
private static String filter(String str) {
String output = null;
StringBuffer sb = new StringBuffer();
for(int i = 0; i < str.length(); ++i) {
int asc = str.charAt(i);
if (asc != '\n' && asc != '\r') {
sb.append(str.subSequence(i, i + 1));
}
}
output = new String(sb);
return output;
}
public static String byteToHexString(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for(int i = 0; i < bytes.length; ++i) {
String strHex = Integer.toHexString(bytes[i]);
if (strHex.length() > 3) {
sb.append(strHex.substring(6));
} else if (strHex.length() < 2) {
sb.append("0" + strHex);
} else {
sb.append(strHex);
}
}
return sb.toString();
}
/**
* 将data中的密文Hex后3DES解码
* @param content
* @param key
* @return
*/
public static String reHexAndDecrypt(String content, String key) {
if(StrUtil.isBlank(content)) {
return null;
}
byte[] hexBytes = Hex.decode(content.getBytes(StandardCharsets.UTF_8));
String baseReqStr = getBase64Encode(hexBytes);
String outInfo = Des3Utils.get3DESDecryptECB(baseReqStr, key);
return outInfo;
}
}