Commit e67bc01a by 袁伟铭

1.0.0

parent 4809b2b9
......@@ -19,7 +19,6 @@ import com.zq.admin.modules.security.service.OnlineUserService;
import com.zq.admin.modules.security.service.UserCacheManager;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import com.zq.common.utils.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
......
......@@ -18,7 +18,6 @@ package com.zq.admin.config.security;
import com.zq.admin.modules.security.service.OnlineUserService;
import com.zq.admin.modules.security.service.UserCacheManager;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
......
......@@ -19,7 +19,6 @@ import cn.hutool.core.util.StrUtil;
import com.zq.admin.modules.security.service.OnlineUserService;
import com.zq.admin.modules.security.service.UserCacheManager;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import com.zq.common.context.ContextUtils;
import com.zq.common.vo.OnlineUserDto;
import io.jsonwebtoken.ExpiredJwtException;
......
// package com.zq.admin.config.security;/*
// * Copyright 2019-2020 Zheng Jie
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
// import cn.hutool.core.date.DateField;
// import cn.hutool.core.date.DateUtil;
// import cn.hutool.core.util.IdUtil;
// import com.zq.common.config.redis.RedisUtils;
// import com.zq.common.config.security.SecurityProperties;
// import io.jsonwebtoken.Claims;
// import io.jsonwebtoken.Jwts;
// import io.jsonwebtoken.SignatureAlgorithm;
// import lombok.RequiredArgsConstructor;
// import lombok.extern.slf4j.Slf4j;
// import org.apache.commons.lang3.StringUtils;
// import org.springframework.beans.factory.InitializingBean;
// import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
// import org.springframework.security.core.Authentication;
// import org.springframework.security.core.userdetails.User;
// import org.springframework.stereotype.Component;
//
// import javax.crypto.spec.SecretKeySpec;
// import javax.servlet.http.HttpServletRequest;
// import javax.xml.bind.DatatypeConverter;
// import java.security.Key;
// import java.util.ArrayList;
// import java.util.Date;
// import java.util.concurrent.TimeUnit;
//
// /**
// * @author /
// */
// @Slf4j
// @Component
// @RequiredArgsConstructor
// public class TokenProvider implements InitializingBean {
//
// private final RedisUtils redisUtils;
// private final SecurityProperties properties;
//
// public static final String AUTHORITIES_KEY = "auth";
// private static Key key;
// private static SignatureAlgorithm signatureAlgorithm;
//
// @Override
// public void afterPropertiesSet() {
// signatureAlgorithm = SignatureAlgorithm.HS512;
// byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
// key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
// }
//
// public static String createToken(Authentication authentication) {
// return Jwts.builder()
// .claim(AUTHORITIES_KEY, authentication.getName())
// .setSubject(authentication.getName())
// .signWith(signatureAlgorithm, key)
// // 加入ID确保生成的 Token 都不一致
// .setId(IdUtil.simpleUUID())
// .compact();
// }
//
// public Claims getClaims(String token) {
// return Jwts.parser()
// .setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
// .parseClaimsJws(token)
// .getBody();
// }
//
// public Authentication getAuthentication(String token) {
// Claims claims = getClaims(token);
// User principal = new User(claims.getSubject(), "******", new ArrayList<>());
// return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
// }
//
// /**
// * @param token 需要检查的token
// */
// public void checkRenewal(String token) {
// // 判断是否续期token,计算token的过期时间
// long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
// Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// // 判断当前时间与过期时间的时间差
// long differ = expireDate.getTime() - System.currentTimeMillis();
// // 如果在续期检查的范围内,则续期
// if (differ <= properties.getDetect()) {
// long renew = time + properties.getRenew();
// redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
// }
// }
//
// public String getToken(HttpServletRequest request) {
// String bearerToken = request.getHeader(properties.getHeader());
// if (StringUtils.isBlank(bearerToken)) {
// return null;
// }
// if (bearerToken.startsWith(properties.getTokenStartWith())) {
// // 去掉令牌前缀
// return bearerToken.replace(properties.getTokenStartWith(), "");
// } else {
// log.debug("非法Token:{}", bearerToken);
// }
// return null;
// }
//
// }
package com.zq.admin.config.security;/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @author /
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {
private final RedisUtils redisUtils;
private final SecurityProperties properties;
public static final String AUTHORITIES_KEY = "auth";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(Authentication authentication) {
return Jwts.builder()
.claim(AUTHORITIES_KEY, authentication.getName())
.setSubject(authentication.getName())
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.compact();
}
public Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
}
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
User principal = new User(claims.getSubject(), "******", new ArrayList<>());
return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.isBlank(bearerToken)) {
return null;
}
if (bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}
......@@ -20,6 +20,7 @@ import com.wf.captcha.base.Captcha;
import com.zq.admin.config.RsaProperties;
import com.zq.admin.config.bean.LoginCodeEnum;
import com.zq.admin.config.bean.LoginProperties;
import com.zq.admin.config.security.TokenProvider;
import com.zq.admin.exception.BadRequestException;
import com.zq.admin.modules.security.service.OnlineUserService;
import com.zq.admin.modules.security.service.dto.AuthUserDto;
......@@ -31,7 +32,6 @@ import com.zq.common.annotation.rest.AnonymousGetMapping;
import com.zq.common.annotation.rest.AnonymousPostMapping;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
......
package com.zq.api.config;
import com.zq.common.constant.FeignHeader;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
......@@ -55,7 +56,7 @@ public class FeignConfig {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
template.header("X-App-Token", request.getParameter("token"));
template.header(FeignHeader.API_TOKEN, request.getParameter("token"));
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
......
......@@ -17,6 +17,7 @@ import feign.FeignException;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
......@@ -43,7 +44,7 @@ public class ApiController {
* 2016年10月3日 下午1:38:27
*/
@RequestMapping("/action")
public ApiResp action(HttpServletRequest request, ApiForm form) {
public ApiResp action(HttpServletRequest request, ApiForm form, @RequestHeader(required = false) String token) {
long start = System.currentTimeMillis();
// 不处理Request Method:OPTIONS的请求
......@@ -51,6 +52,7 @@ public class ApiController {
return ApiUtils.getSuccessResp(form);
}
form.setType(2);
//解析业务参数
if (!form.parseBizContent()) {
return ApiUtils.getParamError(form);
......@@ -84,7 +86,12 @@ public class ApiController {
// 调用接口方法
ApiResp resp;
try {
resp = apiService.action(form);
// 身份验证
resp = apiService.auth(form, token);
if (resp.isSuccess()) {
// 调用接口方法
resp = apiService.action(form);
}
} catch (Exception e) {
log.error("调用方法异常:{}", e.getMessage());
stackTrace = ThrowableUtil.getStackTrace(e);
......@@ -95,6 +102,8 @@ public class ApiController {
resp = ApiUtils.toApiResp(form, ResultVo.fail(404, "NotFound"));
} else if (stackTrace.contains("Load balancer does not have available server for client")) {
resp = ApiUtils.getServiceNotAvailableError(form);
} else if (stackTrace.contains("Connection refused: connect executing")) {
resp = ApiUtils.getServiceNotAvailableError(form);
} else {
resp = ApiUtils.getMethodHandlerError(form);
}
......@@ -102,7 +111,7 @@ public class ApiController {
// 没有数据输出空
resp = resp == null ? new ApiResp(form) : resp;
String logType = resp.isSuccess() ? "INFO" : "ERROR";
String logType = resp.isSuccess() ? "INFO" : "400".equals(resp.getCode()) ? "WARN" : "ERROR";
// 如果是500错误, 服务会返回错误的堆栈信息
if (resp.getCode().equals(ApiCodeEnum.SERVER_ERROR.code())) {
......
package com.zq.api.controller;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.zq.api.constant.ApiCodeEnum;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.service.AppService;
import com.zq.api.utils.ApiUtils;
import com.zq.common.config.security.ApiTokenUtils;
import com.zq.common.utils.ThrowableUtil;
import com.zq.common.vo.ApiTokenVo;
import com.zq.common.vo.ResultVo;
import feign.FeignException;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @author wilmiam
* @since 2021-08-16 15:37
*/
@Api(tags = "APP接口")
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/app")
public class AppController {
private final AppService appService;
/**
* 允许用户未登录状态下执行的方法名
*/
private final String[] allowMethod = {"apiLogin", "signinWxLogin", "getSigninWxPhone", "wxLogin", "getWxPhone"};
/**
* 获取信息入口
* <p>
* 2016年10月3日 下午1:38:27
*/
@RequestMapping("/action")
public ApiResp action(HttpServletRequest request, ApiForm form) {
long start = System.currentTimeMillis();
// 不处理Request Method:OPTIONS的请求
if ("OPTIONS".equals(request.getMethod())) {
return ApiUtils.getSuccessResp(form);
}
form.setType(1);
//解析业务参数
if (!form.parseBizContent()) {
return ApiUtils.getParamError(form);
}
String method = form.getMethod();
if (StrUtil.isBlank(method)) {
method = request.getParameter("method");
form.setMethod(method);
}
if (StrUtil.isBlank(form.getToken())) {
boolean contains = Arrays.asList(allowMethod).contains(method);
if (!contains) {
return ApiUtils.getLoginValidError(form);
}
}
ApiTokenVo tokenVo = ApiTokenUtils.getAppTokenVo(form.getToken());
if (tokenVo == null) {
boolean contains = Arrays.asList(allowMethod).contains(method);
if (!contains) {
return ApiUtils.getLoginValidError(form);
}
} else {
form.setUserId(String.valueOf(tokenVo.getUserId()));
form.setApiTokenVo(tokenVo);
}
String stackTrace = "";
// 调用接口方法
ApiResp resp;
try {
resp = appService.action(form);
} catch (Exception e) {
log.error("调用方法异常:{}", e.getMessage());
stackTrace = ThrowableUtil.getStackTrace(e);
// 判断指定异常是否来自或者包含指定异常
if (ExceptionUtil.isFromOrSuppressedThrowable(e, FeignException.Unauthorized.class)) {
resp = ApiUtils.toApiResp(form, ResultVo.fail(401, "Unauthorized"));
} else if (ExceptionUtil.isFromOrSuppressedThrowable(e, FeignException.NotFound.class)) {
resp = ApiUtils.toApiResp(form, ResultVo.fail(404, "NotFound"));
} else if (stackTrace.contains("Load balancer does not have available server for client")) {
resp = ApiUtils.getServiceNotAvailableError(form);
} else if (stackTrace.contains("Connection refused: connect executing")) {
resp = ApiUtils.getServiceNotAvailableError(form);
} else {
resp = ApiUtils.getMethodHandlerError(form);
}
}
// 没有数据输出空
resp = resp == null ? new ApiResp(form) : resp;
String logType = resp.isSuccess() ? "INFO" : "400".equals(resp.getCode()) ? "WARN" : "ERROR";
// 如果是500错误, 服务会返回错误的堆栈信息
if (resp.getCode().equals(ApiCodeEnum.SERVER_ERROR.code())) {
stackTrace = resp.getMsg();
resp.setMsg(ApiCodeEnum.SERVER_ERROR.msg());
}
// 调试日志
if (ApiUtils.DEBUG) {
System.out.println("API DEBUG ACTION \n[from=" + form + "]"
+ "\n[resp=" + JSON.toJSONString(resp) + "]"
+ "\n[time=" + (System.currentTimeMillis() - start) + "ms]");
}
String clientIp = ServletUtil.getClientIP(request);
appService.addLog(form, clientIp, logType, resp.getMsg(), stackTrace, System.currentTimeMillis() - start);
return resp;
}
/**
* 开关调试日志
* <p>
* 2016年10月3日 下午5:47:46
*/
@RequestMapping("/debug")
public ApiResp debug(HttpServletRequest request) {
ApiForm from = ServletUtil.toBean(request, ApiForm.class, true);
ApiUtils.DEBUG = !ApiUtils.DEBUG;
return new ApiResp(from).setData(ApiUtils.DEBUG);
}
}
......@@ -37,6 +37,8 @@ public class ApiForm {
private String nonce; // 随机字串(建议使用UUID)
private String version; // 接口版本
private String apiNo; // 接口码
private Integer type; // 1-app;2-api,内部定
private String clientType; // 客户端类型 xcx-小程序,H5,api
private String bizContent; // 请求业务参数
private JSONObject bizContentJson; // 请求业务的json对象
private MultipartFile file; // 上传文件用
......@@ -44,12 +46,19 @@ public class ApiForm {
public boolean parseBizContent() {
try {
// API参数是否加密
boolean flag = ConfigCache.getValueToBoolean("API.PARAM.ENCRYPT");
if (StrUtil.isNotBlank(bizContent) && flag) {
bizContent = ApiUtils.decode(bizContent);
if (type == 1) {
// API参数是否加密
boolean flag = ConfigCache.getValueToBoolean("API.PARAM.ENCRYPT");
if (StrUtil.isNotBlank(bizContent) && flag) {
bizContent = ApiUtils.decode(bizContent, "");
}
} else {
if (StrUtil.isNotBlank(bizContent)) {
bizContent = ApiUtils.decode(bizContent, "BASE64");
}
}
if (StrUtil.isBlank(bizContent)) {
if (bizContent == null) {
bizContent = "";
}
bizContentJson = JSON.parseObject(bizContent);
......@@ -148,8 +157,9 @@ public class ApiForm {
public TreeMap<String, String> getSignTreeMap() {
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("appId", this.appId);
treeMap.put("apiNo", this.apiNo);
treeMap.put("timestamp", this.timestamp);
treeMap.put("nonce", this.nonce);
treeMap.put("method", this.method);
treeMap.put("version", this.version);
String bizContent = StrUtil.isBlank(this.bizContent) ? "" : this.bizContent;
......@@ -157,4 +167,17 @@ public class ApiForm {
return treeMap;
}
public String getSignStr(String key) {
TreeMap<String, String> treeMap = getSignTreeMap();
// 原始请求串
StringBuilder src = new StringBuilder();
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
src.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
src.append("key=").append(key);
return src.toString();
}
}
package com.zq.api.service;
import cn.hutool.core.date.DateUtil;
import com.zq.api.config.ConfigCache;
import com.zq.api.constant.ApiCodeEnum;
import cn.hutool.core.util.StrUtil;
import com.zq.api.dao.ApiLogDao;
import com.zq.api.entity.ApiLog;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.utils.ApiUtils;
import com.zq.api.utils.ReflectionUtils;
import com.zq.common.config.redis.BaseCacheKeys;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.vo.ApiTokenVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
......@@ -16,6 +18,7 @@ import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Slf4j
......@@ -24,6 +27,10 @@ import java.util.List;
public class ApiService {
private final ApiLogDao apiLogDao;
private final RedisUtils redisUtils;
// 允许用户未登录状态下执行的方法名
private final String[] allowMethod = {"getApiToken"};
private static final List<String> METHOD_LIST;
......@@ -48,20 +55,42 @@ public class ApiService {
return methodList;
}
/**
* 身份验证
*
* @param form
* @return
*/
public ApiResp auth(ApiForm form, String token) {
boolean contains = Arrays.asList(allowMethod).contains(form.getMethod());
if (contains) {
return ApiUtils.getSuccessResp(form);
} else if (StrUtil.isBlank(token)) {
return ApiUtils.getLoginValidError(form);
}
// 验证认证信息
ApiTokenVo tokenVo = redisUtils.getObj(BaseCacheKeys.PREFIX + "ApiToken." + token, ApiTokenVo.class);
if (tokenVo == null) {
return ApiUtils.getLoginValidError(form);
}
// 验证签名
String sign = ApiUtils.getSign(form.getSignStr(tokenVo.getSessionKey() == null ? "" : tokenVo.getSessionKey()));
if (!sign.equals(form.getSign())) {
return ApiUtils.getCheckSignValidError(form);
}
form.setUserId(tokenVo.getUserId());
form.setApiTokenVo(tokenVo);
return ApiUtils.getSuccessResp(form);
}
public ApiResp action(ApiForm form) throws Exception {
if (!METHOD_LIST.contains(form.getMethod())) {
return ApiUtils.getMethodError(form);
}
// 签名验证标识
boolean validFlag = ConfigCache.getValueToBoolean("API.SIGN.VALID");
IApiLogic apiLogic = getApiLogic(form);
if (validFlag) {
// 验证签名
ApiResp validResp = apiLogic.signValid(form);
if (!validResp.getCode().equals(ApiCodeEnum.SUCCESS.code())) {
return validResp;
}
}
// 调用接口方法,利用反射更简洁
return (ApiResp) ReflectionUtils.invokeMethod(apiLogic, form.getMethod(), new Class<?>[]{ApiForm.class}, new Object[]{form});
......
package com.zq.api.service;
import cn.hutool.core.date.DateUtil;
import com.zq.api.config.ConfigCache;
import com.zq.api.constant.ApiCodeEnum;
import com.zq.api.dao.ApiLogDao;
import com.zq.api.entity.ApiLog;
import com.zq.api.form.ApiForm;
import com.zq.api.form.ApiResp;
import com.zq.api.utils.ApiUtils;
import com.zq.api.utils.ReflectionUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author wilmiam
* @since 2022/5/30 12:33
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AppService {
private final ApiLogDao apiLogDao;
private static final List<String> METHOD_LIST;
static {
METHOD_LIST = methodList();
}
public IApiLogic getApiLogic(ApiForm form) {
IApiLogic apiLogic = ApiUtils.getApiLogic(form);
return apiLogic;
}
public static List<String> methodList() {
List<String> methodList = new ArrayList<>();
Method[] methods = IApiLogic.class.getMethods();
for (Method method : methods) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 1 && (params[0] == ApiForm.class)) {
methodList.add(method.getName());
}
}
return methodList;
}
public ApiResp action(ApiForm form) throws Exception {
if (!METHOD_LIST.contains(form.getMethod())) {
return ApiUtils.getMethodError(form);
}
// 签名验证标识
boolean validFlag = ConfigCache.getValueToBoolean("API.SIGN.VALID");
IApiLogic apiLogic = getApiLogic(form);
if (validFlag) {
// 验证签名
ApiResp validResp = apiLogic.signValid(form);
if (!validResp.getCode().equals(ApiCodeEnum.SUCCESS.code())) {
return validResp;
}
}
// 调用接口方法,利用反射更简洁
return (ApiResp) ReflectionUtils.invokeMethod(apiLogic, form.getMethod(), new Class<?>[]{ApiForm.class}, new Object[]{form});
}
@Async
public void addLog(ApiForm form, String ip, String logType, String respMsg, String errorInfo, Long timeCost) {
apiLogDao.insert(ApiLog.builder()
.appId(form.getAppId())
.userId(form.getUserId())
.method(form.getMethod())
.version(form.getVersion())
.bizContent(form.getBizContent())
.ip(ip)
.logType(logType)
.respMsg(respMsg)
.stackTrace(errorInfo)
.timeCost(timeCost)
.createTime(DateUtil.date().toJdkDate())
.build());
}
}
......@@ -27,7 +27,7 @@ public abstract class BaseApiLogic implements IApiLogic {
return ApiUtils.getCheckSignValidError(form);
}
String serverSign = ApiUtils.getSign(form.getSignTreeMap());
String serverSign = ApiUtils.getSign(form.getSignStr(""));
if (!serverSign.equals(form.getSign())) {
return ApiUtils.getCheckSignValidError(form);
}
......
......@@ -13,12 +13,8 @@ import com.zq.common.vo.ResultVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* @author wilmiam
......@@ -172,11 +168,17 @@ public class ApiUtils {
*
* @param params
* @return
* @throws UnsupportedEncodingException
*/
public static String decode(String params) throws UnsupportedEncodingException {
params = URLDecoder.decode(params, "utf-8");
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.privateKey);
public static String decode(String params, String encryptType) {
if (StringUtils.isBlank(params)) {
return "";
}
params = EncryptUtils.urlDecode(params, "UTF-8");
if ("RSA".equals(encryptType)) {
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.privateKey);
} else {
params = EncryptUtils.base64Decode(params);
}
return params;
}
......@@ -187,14 +189,17 @@ public class ApiUtils {
*
* @param params
* @return
* @throws UnsupportedEncodingException
*/
public static String encode(String params) throws UnsupportedEncodingException {
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.publicKey);
public static String encode(String params, String encryptType) {
if (StringUtils.isBlank(params)) {
return "";
}
params = URLEncoder.encode(params, "utf-8");
if ("RSA".equals(encryptType)) {
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.publicKey);
} else {
params = EncryptUtils.base64Decode(params);
}
params = EncryptUtils.urlEncode(params, "UTF-8");
return params;
}
......@@ -203,16 +208,11 @@ public class ApiUtils {
* <p>
* 2017年3月15日 下午3:14:27
*
* @param paramMaps
* @param content
* @return
*/
public static String getSign(TreeMap<String, String> paramMaps) {
// 原始请求串
StringBuilder src = new StringBuilder();
for (Map.Entry<String, String> entry : paramMaps.entrySet()) {
src.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
return MD5.create().digestHex(src.toString());
public static String getSign(String content) {
return MD5.create().digestHex(content).toUpperCase();
}
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.common.constant;
package com.zq.logging.constant;
/**
* 常用静态常量
......@@ -21,7 +21,7 @@ package com.zq.common.constant;
* @author Zheng Jie
* @date 2018-12-26
*/
public class CloudConstant {
public class Constant {
/**
* 用于IP定位转换
......
......@@ -3,8 +3,8 @@ package com.zq.logging.utils;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.zq.common.config.base.SpringContextHolder;
import com.zq.common.constant.CloudConstant;
import com.zq.logging.config.AdminProperties;
import com.zq.logging.constant.Constant;
import net.dreamlu.mica.ip2region.core.Ip2regionSearcher;
import net.dreamlu.mica.ip2region.core.IpInfo;
import nl.basjes.parse.useragent.UserAgent;
......@@ -46,7 +46,7 @@ public class RequestUtils {
* 根据ip获取详细地址
*/
public static String getHttpCityInfo(String ip) {
String api = String.format(CloudConstant.Url.IP_URL, ip);
String api = String.format(Constant.Url.IP_URL, ip);
cn.hutool.json.JSONObject object = JSONUtil.parseObj(HttpUtil.get(api));
return object.get("addr", String.class);
}
......
......@@ -18,7 +18,6 @@ package com.zq.user.config;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import com.zq.common.utils.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
......
......@@ -17,7 +17,6 @@ package com.zq.user.config;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
......
......@@ -19,7 +19,6 @@ import cn.hutool.core.util.StrUtil;
import com.zq.common.config.redis.BaseCacheKeys;
import com.zq.common.config.redis.RedisUtils;
import com.zq.common.config.security.SecurityProperties;
import com.zq.common.config.security.TokenProvider;
import com.zq.common.context.ContextUtils;
import com.zq.common.vo.OnlineUserDto;
import io.jsonwebtoken.ExpiredJwtException;
......
package com.zq.common.config.base;
import com.zq.common.constant.FeignHeader;
import com.zq.common.constant.SystemName;
import com.zq.common.exception.BusinessException;
import com.zq.common.utils.ThrowableUtil;
import com.zq.common.vo.ResultVo;
......@@ -94,8 +96,9 @@ public class UnifiedExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public ResultVo handleDataAccessException(DataAccessException ex, HttpServletRequest request) {
log.error(">> 访问数据失败 " + request.getRequestURI(), ex);
String header = request.getHeader(FeignHeader.SERVER_NAME);
String error = "服务器繁忙";
if (request.getRequestURI().contains("/app/")) {
if (StringUtils.isNotBlank(header) && SystemName.API.equals(header)) {
error = ThrowableUtil.getStackTrace(ex);
}
return ResultVo.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), error);
......@@ -104,8 +107,9 @@ public class UnifiedExceptionHandler {
@ExceptionHandler(value = Exception.class)
public ResultVo defaultErrorHandler(Exception ex, HttpServletRequest request) {
log.error(">> 服务器内部错误 " + request.getRequestURI(), ex);
String header = request.getHeader(FeignHeader.SERVER_NAME);
String error = "服务器繁忙";
if (request.getRequestURI().contains("/app/")) {
if (StringUtils.isNotBlank(header) && SystemName.API.equals(header)) {
error = ThrowableUtil.getStackTrace(ex);
}
return ResultVo.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), error);
......
package com.zq.common.config.security;/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zq.common.config.redis.RedisUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author /
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {
private final RedisUtils redisUtils;
private final SecurityProperties properties;
public static final String AUTHORITIES_KEY = "auth";
private static Key key;
private static SignatureAlgorithm signatureAlgorithm;
@Override
public void afterPropertiesSet() {
signatureAlgorithm = SignatureAlgorithm.HS512;
byte[] keyBytes = DatatypeConverter.parseBase64Binary(properties.getBase64Secret());
key = new SecretKeySpec(keyBytes, signatureAlgorithm.getJcaName());
}
public static String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(signatureAlgorithm, key)
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.compact();
}
public Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(properties.getBase64Secret()))
.parseClaimsJws(token)
.getBody();
}
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
// fix bug: 当前用户如果没有任何权限时,在输入用户名后,刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
Collection<? extends GrantedAuthority> authorities =
ObjectUtil.isNotEmpty(authoritiesStr) ?
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
User principal = new User(claims.getSubject(), "******", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
/**
* @param token 需要检查的token
*/
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.isBlank(bearerToken)) {
return null;
}
if (bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
}
package com.zq.common.constant;
/**
* feign要添加的请求头
*
* @author wilmiam
* @since 2021/9/6 9:43
*/
public class FeignHeader {
/**
* feign添加服务名的请求头字段
*/
public static final String SERVER_NAME = "X-Server-Name";
/**
* feign添加api-token的请求头字段
*/
public static final String API_TOKEN = "X-Api-Token";
}
package com.zq.common.constant;
/**
* @author wilmiam
* @since 2021-09-06 09:16
*/
public class SystemName {
public static final String ADMIN = "admin";
public static final String API = "api";
}
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