Commit e67bc01a by 袁伟铭

1.0.0

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