设备和token
This commit is contained in:
@@ -49,6 +49,13 @@
|
||||
<groupId>com.dm</groupId>
|
||||
<artifactId>DmJdbcDriver</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.sunyard.chsm.auth;
|
||||
|
||||
|
||||
import com.sunyard.chsm.model.R;
|
||||
import com.sunyard.chsm.service.AppLoginService;
|
||||
import com.sunyard.chsm.utils.JsonUtils;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @version V1.0
|
||||
* @since 2023/8/4
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AppTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
public static final Collection<String> WHITE_URL = Arrays.asList(
|
||||
"/appUser/getAppToken",
|
||||
"/appUser/getAppTokenTest"
|
||||
);
|
||||
|
||||
private final AppLoginService appLoginService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String requestURI = request.getRequestURI();
|
||||
if (WHITE_URL.contains(requestURI)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String tokenValue = extractToken(request);
|
||||
if (ObjectUtils.isEmpty(tokenValue)) {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("请先获取token后再访问")));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
AppUser user = appLoginService.verifyToken(tokenValue);
|
||||
request.setAttribute("APP_USER", user);
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (ExpiredJwtException ex) {
|
||||
log.warn("token已过期: {}", requestURI);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("token已过期")));
|
||||
} catch (JwtException ex) {
|
||||
log.warn("token格式错误: {}", requestURI);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("token格式错误")));
|
||||
} catch (Exception ex) {
|
||||
log.error("未知异常: {}", requestURI, ex);
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.getOutputStream().write(JsonUtils.toJsonBytes(R.error("发生异常")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected String extractToken(HttpServletRequest request) {
|
||||
// first check the header...
|
||||
String token = extractHeaderToken(request);
|
||||
|
||||
// bearer type allows a request parameter as well
|
||||
if (token == null) {
|
||||
logger.debug("Token not found in headers. Trying request parameters.");
|
||||
token = request.getParameter("access_token");
|
||||
if (token == null) {
|
||||
logger.debug("Token not found in request parameters. Not an OAuth2 request.");
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
private static final String BEARER_TYPE = "Bearer";
|
||||
|
||||
private String extractHeaderToken(HttpServletRequest request) {
|
||||
Enumeration<String> headers = request.getHeaders("Authorization");
|
||||
while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)
|
||||
String value = headers.nextElement();
|
||||
if (value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) {
|
||||
String authHeaderValue = value.substring(BEARER_TYPE.length()).trim();
|
||||
int commaIndex = authHeaderValue.indexOf(',');
|
||||
if (commaIndex > 0) {
|
||||
authHeaderValue = authHeaderValue.substring(0, commaIndex);
|
||||
}
|
||||
return authHeaderValue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sunyard.chsm.auth;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/12/6
|
||||
*/
|
||||
@Data
|
||||
public class AppUser {
|
||||
|
||||
private Long appId;
|
||||
private String name;
|
||||
private List<Long> serviceIds;
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.sunyard.chsm.config;
|
||||
package com.sunyard.chsm.auth;
|
||||
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sunyard.chsm.auth;
|
||||
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/12/6
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
|
||||
public static AppUser getCurrentUser() {
|
||||
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
return (AppUser) requestAttributes.getAttribute("APP_USER", RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.sunyard.chsm.config;
|
||||
|
||||
import com.sunyard.chsm.model.R;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.IllegalFormatException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 全局controller异常处理
|
||||
*
|
||||
* @auther: ZS
|
||||
* @description:
|
||||
* @param:
|
||||
* @return:
|
||||
* @date: 2020/5/25 16:24
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionResolver {
|
||||
|
||||
/**
|
||||
* 校验器
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseBody
|
||||
public R<?> handleBindException(BindException e) {
|
||||
log.info("校验器异常", e);
|
||||
FieldError fieldError = e.getBindingResult().getFieldError();
|
||||
return R.error(400, fieldError.getDefaultMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public R<?> validExceptionHandler(MethodArgumentNotValidException ex) {
|
||||
return Optional.of(ex)
|
||||
.map(MethodArgumentNotValidException::getFieldError)
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.map(message -> R.error(400, message))
|
||||
.get();
|
||||
}
|
||||
|
||||
private static final List<Class<? extends Exception>> EX_CLASS = Arrays.asList(
|
||||
IllegalArgumentException.class, IllegalStateException.class,
|
||||
IllegalFormatException.class,
|
||||
UnsupportedOperationException.class);
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public R<?> exceptionHandler(Exception ex, HttpServletRequest request) {
|
||||
String errorMessage = buildErrorMessage(ex);
|
||||
for (Class<? extends Exception> eClass : EX_CLASS) {
|
||||
Exception exception = findException(eClass, ex);
|
||||
if (exception == null) {
|
||||
continue;
|
||||
}
|
||||
log.warn("Validation failed -> {} ", errorMessage);
|
||||
return R.error(400, exception.getMessage());
|
||||
}
|
||||
log.error("request: {} - {}, 系统异常 -> ", request.getMethod(), request.getRequestURI(), ex);
|
||||
return R.error("系统异常");
|
||||
}
|
||||
|
||||
public static <T extends Exception> T findException(Class<T> exClass, Throwable ex) {
|
||||
for (int i = 0; ex != null && i < 3; i++) {
|
||||
if (exClass.isAssignableFrom(ex.getClass())) {
|
||||
//noinspection unchecked
|
||||
return (T) ex;
|
||||
}
|
||||
ex = ex.getCause();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String buildErrorMessage(Throwable ex) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(ex.getMessage());
|
||||
StackTraceElement[] stackTrace = ex.getStackTrace();
|
||||
int i = 0;
|
||||
for (StackTraceElement stackTraceElement : stackTrace) {
|
||||
if (i++ >= 5) {
|
||||
break;
|
||||
}
|
||||
sb.append("\n\t").append(stackTraceElement.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,49 @@
|
||||
package com.sunyard.chsm.controller;
|
||||
|
||||
import com.sunyard.chsm.param.AppTokenReq;
|
||||
import com.sunyard.chsm.param.AppTokenResp;
|
||||
import com.sunyard.chsm.service.AppLoginService;
|
||||
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 javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 应用Token服务接口
|
||||
*
|
||||
* @author liulu
|
||||
* @version V1.0
|
||||
* @since 2023/8/4
|
||||
* @since 2024/12/4
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public class AppLoginController {
|
||||
|
||||
@Resource
|
||||
private AppLoginService appLoginService;
|
||||
|
||||
|
||||
/**
|
||||
* 获取应用Token接口
|
||||
* 密码服务平台为各个接入应用提供生成应用授权Token,
|
||||
* 接入应用需在密码服务平台获取了到平台分配的appKey和appSecret
|
||||
*
|
||||
* @param appTokenReq 请求参数
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/appUser/getAppToken")
|
||||
public AppTokenResp getAppToken(@Valid @RequestBody AppTokenReq appTokenReq) {
|
||||
return appLoginService.getAppToken(appTokenReq);
|
||||
}
|
||||
|
||||
@PostMapping("/appUser/getAppTokenTest")
|
||||
public AppTokenResp getAppTokenForTest(@Valid @RequestBody AppTokenReq appTokenReq) {
|
||||
return appLoginService.getAppTokenForTest(appTokenReq);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.sunyard.chsm.controller;
|
||||
|
||||
import com.sunyard.chsm.model.R;
|
||||
import com.sunyard.chsm.model.dto.KeyInfoDTO;
|
||||
import com.sunyard.chsm.service.KeyInfoService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
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 javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 密钥管理类接口
|
||||
*
|
||||
* @author liulu
|
||||
* @since 2024/12/6
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/key/manage")
|
||||
public class KeyManageController {
|
||||
|
||||
@Resource
|
||||
private KeyInfoService keyInfoService;
|
||||
|
||||
/**
|
||||
* 启用密钥
|
||||
*
|
||||
* @param param 密钥id
|
||||
* @return id
|
||||
*/
|
||||
@PostMapping("/enable")
|
||||
public R<Void> enableKey(@Validated @RequestBody KeyInfoDTO.IDs param) {
|
||||
keyInfoService.enableKey(param.getIds());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.sunyard.chsm.service;
|
||||
|
||||
import com.sunyard.chsm.auth.AppUser;
|
||||
import com.sunyard.chsm.constant.SecurityConstant;
|
||||
import com.sunyard.chsm.enums.EnableStatus;
|
||||
import com.sunyard.chsm.mapper.ApplicationMapper;
|
||||
import com.sunyard.chsm.model.entity.Application;
|
||||
import com.sunyard.chsm.param.AppTokenReq;
|
||||
import com.sunyard.chsm.param.AppTokenResp;
|
||||
import com.sunyard.chsm.utils.CodecUtils;
|
||||
import com.sunyard.chsm.utils.gm.BCSM3Utils;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/12/5
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AppLoginService {
|
||||
|
||||
/**
|
||||
* token 过期时间, 分钟
|
||||
*/
|
||||
@Value("${chsm.token.expireTime:720}")
|
||||
private Integer tokenExpireTime;
|
||||
@Resource
|
||||
private ApplicationMapper applicationMapper;
|
||||
|
||||
public AppTokenResp getAppToken(AppTokenReq req) {
|
||||
Long random = req.getRandom();
|
||||
long now = System.currentTimeMillis();
|
||||
Assert.isTrue(now - random <= 5 * 60 * 1000, "请求已超时");
|
||||
String appKey = req.getAppKey();
|
||||
Application application = applicationMapper.selectByAppKey(appKey);
|
||||
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(application.getStatus()), "此应用已停用");
|
||||
String data = appKey + random + application.getAppSecret();
|
||||
byte[] hmac = BCSM3Utils.hmac(application.getAppSecret().getBytes(), data.getBytes());
|
||||
String serverHmac = CodecUtils.encodeHex(hmac);
|
||||
if (!Objects.equals(req.getHmac(), serverHmac)) {
|
||||
log.warn("appKey: {}, req hmac: {}, server hmac: {}", appKey, req.getHmac(), serverHmac);
|
||||
throw new IllegalArgumentException("应用认证失败");
|
||||
}
|
||||
AppTokenResp resp = new AppTokenResp();
|
||||
resp.setToken(genToken(application));
|
||||
return resp;
|
||||
}
|
||||
|
||||
private String genToken(Application app) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("appId", app.getId());
|
||||
claims.put("name", app.getName());
|
||||
claims.put("serviceIds", Collections.singletonList(app.getId()));
|
||||
Date now = new Date();
|
||||
return Jwts.builder()
|
||||
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
|
||||
.setClaims(claims)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(new Date(now.getTime() + tokenExpireTime * 60 * 1000))
|
||||
.setId(UUID.randomUUID().toString())
|
||||
.signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public AppTokenResp getAppTokenForTest(@Valid AppTokenReq req) {
|
||||
|
||||
// Long random = req.getRandom();
|
||||
// long now = System.currentTimeMillis();
|
||||
// Assert.isTrue(now - random <= 5 * 60 * 1000, "请求已超时");
|
||||
Application application = applicationMapper.selectByAppKey(req.getAppKey());
|
||||
Assert.isTrue(EnableStatus.ENABLED.getCode().equals(application.getStatus()), "此应用已停用");
|
||||
if (!Objects.equals(req.getHmac(), application.getAppSecret())) {
|
||||
log.warn("appKey: {}, req hmac: {},", req.getAppKey(), req.getHmac());
|
||||
throw new IllegalArgumentException("应用认证失败");
|
||||
}
|
||||
AppTokenResp resp = new AppTokenResp();
|
||||
resp.setToken(genToken(application));
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
||||
public AppUser verifyToken(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(SecurityConstant.JWT_SIGN_KEY)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
AppUser user = new AppUser();
|
||||
user.setAppId(claims.get("appId", Long.class));
|
||||
user.setName(claims.get("name", String.class));
|
||||
user.setServiceIds(claims.get("serviceIds", List.class));
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@ spring:
|
||||
# 数据源
|
||||
datasource:
|
||||
driverClassName: dm.jdbc.driver.DmDriver
|
||||
url: jdbc:dm://172.16.18.44:5236?schema=SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
|
||||
url: jdbc:dm://172.16.18.44:5236?schema=SUNYARD_SSP&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=UTF-8
|
||||
username: SUNYARD
|
||||
# Jasypt加密 可到common-utils中找到JasyptUtil加解密工具类生成加密结果 格式为ENC(加密结果)
|
||||
password: 123456
|
||||
|
||||
Reference in New Issue
Block a user