feat: 额外对国培AI对话接口的鉴权

11111
winter 9 months ago
parent 9aec66d1a7
commit acf448e980

@ -32,6 +32,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.6.0</version>
</dependency>
<!-- Spring Security OAuth2 resource server -->
<dependency>
<groupId>org.springframework.boot</groupId>

@ -2,7 +2,7 @@ package cn.teammodel.config.exception;
import cn.teammodel.common.ErrorCode;
import cn.teammodel.common.R;
import cn.teammodel.manager.NotificationService;
import cn.teammodel.manager.notification.NotificationService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;

@ -3,7 +3,7 @@ package cn.teammodel.config.intercepter;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.jwt.JWTUtil;
import cn.teammodel.manager.NotificationService;
import cn.teammodel.manager.notification.NotificationService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -11,6 +11,8 @@ import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@ -32,13 +34,22 @@ import java.util.List;
@Component
@Slf4j
public class UploadApiLogInterceptor implements HandlerInterceptor {
@Value("${spring.env}")
private String env;
@Resource
private NotificationService notificationService;
private final ObjectMapper mapper = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
if (!env.equals("dev")) {
doUploadLog(request);
}
return true;
}
private void doUploadLog(@NotNull HttpServletRequest request) throws IOException {
String ip = ServletUtil.getClientIP(request);
String client = "";
String tokenSha = "";
@ -115,7 +126,7 @@ public class UploadApiLogInterceptor implements HandlerInterceptor {
okHttpClient.newCall(okRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
log.error("UploadApiLogIntercepter error" ) ;
log.error("UploadApiLogIntercepter error") ;
notificationService.send("日志上传告警: 请求发送失败,检查请求发送客户端");
}
@ -129,6 +140,5 @@ public class UploadApiLogInterceptor implements HandlerInterceptor {
}
}
});
return true;
}
}

@ -20,7 +20,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("public/ai")
@RequestMapping("/ai/api")
@Api(tags = "AI 能力")
public class AiController {
@Resource

@ -1,44 +1,44 @@
package cn.teammodel.manager;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
*
* @author winter
* @create 2023-11-14 10:11
*/
@Component
@Slf4j
public class DingAlertNotifier implements NotificationService{
private final DingTalkClient client;
@Autowired
public DingAlertNotifier(@Value("${ding.server-url}") String dingServerUrl) {
this.client = new DefaultDingTalkClient(dingServerUrl);
}
@Override
public void send(String message) {
OapiRobotSendRequest request = new OapiRobotSendRequest();
// 文本消息
request.setMsgtype("text");
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(message);
request.setText(text);
// at 管理员提醒异常
OapiRobotSendRequest.At atAll = new OapiRobotSendRequest.At();
atAll.setIsAtAll(true);
request.setAt(atAll);
try {
client.execute(request);
} catch (ApiException e) {
log.error("钉钉 robot 推送消息渠道异常: {}", e.getMessage());
}
}
}
package cn.teammodel.manager.notification;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
*
* @author winter
* @create 2023-11-14 10:11
*/
@Component
@Slf4j
public class DingAlertNotifier implements NotificationService{
private final DingTalkClient client;
@Autowired
public DingAlertNotifier(@Value("${ding.server-url}") String dingServerUrl) {
this.client = new DefaultDingTalkClient(dingServerUrl);
}
@Override
public void send(String message) {
OapiRobotSendRequest request = new OapiRobotSendRequest();
// 文本消息
request.setMsgtype("text");
OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
text.setContent(message);
request.setText(text);
// at 管理员提醒异常
OapiRobotSendRequest.At atAll = new OapiRobotSendRequest.At();
atAll.setIsAtAll(true);
request.setAt(atAll);
try {
client.execute(request);
} catch (ApiException e) {
log.error("钉钉 robot 推送消息渠道异常: {}", e.getMessage());
}
}
}

@ -1,16 +1,16 @@
package cn.teammodel.manager;
/**
*
* @author winter
* @create 2023-11-14 10:08
*/
public interface NotificationService {
/**
*
* @author: winter
* @date: 2023/11/14 10:09
*/
void send(String message);
}
package cn.teammodel.manager.notification;
/**
*
* @author winter
* @create 2023-11-14 10:08
*/
public interface NotificationService {
/**
*
* @author: winter
* @date: 2023/11/14 10:09
*/
void send(String message);
}

@ -0,0 +1,12 @@
package cn.teammodel.manager.wx;
import org.springframework.stereotype.Service;
/**
* @author winter
* @create 2024-03-26 11:08
*/
@Service
public class MiniProgramSevice {
}

@ -1,10 +1,12 @@
package cn.teammodel.security;
import cn.teammodel.security.filter.ApiAuthTokenFilter;
import cn.teammodel.security.filter.AuthInnerTokenFilter;
import cn.teammodel.security.handler.RestAccessDeniedHandler;
import cn.teammodel.security.handler.RestAuthenticationEntryPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -31,8 +33,11 @@ public class SecurityConfiguration {
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Resource
private AuthInnerTokenFilter authInnerTokenFilter;
@Resource
private ApiAuthTokenFilter apiAuthTokenFilter;
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF禁用因为不使用session
@ -64,7 +69,18 @@ public class SecurityConfiguration {
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.accessDeniedHandler(restAccessDeniedHandler);
return http.build();
}
@Bean
@Order(1)
public SecurityFilterChain outterApiFilterChain(HttpSecurity http) throws Exception {
http.
antMatcher("/ai/api/**")
.authorizeRequests(authorizeRequests ->
authorizeRequests
.anyRequest().authenticated()
)
.addFilterAfter(apiAuthTokenFilter, BearerTokenAuthenticationFilter.class);
return http.build();
}
@ -86,8 +102,8 @@ public class SecurityConfiguration {
}
/**
*
*/
*
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

@ -0,0 +1,54 @@
package cn.teammodel.security.filter;
import cn.teammodel.security.utils.JwtTokenUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* AI chat
* @author winter
* @create 2023-11-09 10:43
*/
@Component
@Slf4j
public class ApiAuthTokenFilter extends OncePerRequestFilter {
@Resource
private JwtTokenUtil jwtTokenUtil;
// todo: 修改 context 的值 + 写一下多过滤器链的复盘
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
SecurityContext context = SecurityContextHolder.getContext();
// 验证 authToken 合法
String token = request.getHeader("token");
if (StringUtils.isBlank(token)) {
filterChain.doFilter(request, response);
return;
}
Claims claims = jwtTokenUtil.validAndGetClaims(token, "fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=", 315360000);
if (claims == null) {
SecurityContextHolder.clearContext(); // 验证失败不应该在此处抛出异常,应该维护好 context 的值,以便整个过滤器链正常运行
filterChain.doFilter(request, response);
return;
}
// 组装 authToken 的 jwt 进 authentication
UsernamePasswordAuthenticationToken finalAuthentication = new UsernamePasswordAuthenticationToken(claims, null, null);
context.setAuthentication(finalAuthentication);
filterChain.doFilter(request, response);
}
}

@ -74,11 +74,15 @@ public class JwtTokenUtil {
* @return claimsnull
*/
private Claims getClaimsFromToken(String token) {
return validAndGetClaims(token, secret, NEVER_EXPIRE);
}
public Claims validAndGetClaims(String token, String key, long expire) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
.setAllowedClockSkewSeconds(NEVER_EXPIRE)
.setSigningKey(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
.setAllowedClockSkewSeconds(expire)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {

@ -1,4 +1,5 @@
spring:
env: dev
mvc:
pathmatch:
matching-strategy: ant_path_matcher

@ -3,7 +3,7 @@ package cn.teammodel;
import cn.teammodel.common.PK;
import cn.teammodel.controller.admin.service.AdminAppraiseService;
import cn.teammodel.repository.*;
import cn.teammodel.manager.DingAlertNotifier;
import cn.teammodel.manager.notification.DingAlertNotifier;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.admin.appraise.UpdateAchievementRuleDto;
import cn.teammodel.model.entity.appraise.*;

@ -1,6 +1,5 @@
package cn.teammodel;
import cn.hutool.jwt.JWTUtil;
import cn.teammodel.common.FiveEducations;
import cn.teammodel.model.entity.appraise.Appraise;
import cn.teammodel.model.entity.appraise.AppraiseTreeNode;
@ -12,9 +11,13 @@ import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.taobao.api.ApiException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@ -33,8 +36,20 @@ public class TestWithoutSpring {
@Test
public void testJwtDecode() {
// 解析 JWT不验证签
Object roles = JWTUtil.parseToken("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlR4OFo1d2lkT2VSdDVCSGdWX2lvdXpzbjd6OCJ9.eyJhdWQiOiI3MjY0MzcwNC1iMmU3LTRiMjYtYjg4MS1iZDU4NjVlN2E3YTUiLCJpc3MiOiJodHRwczovL2xvZ2luLnBhcnRuZXIubWljcm9zb2Z0b25saW5lLmNuLzQ4MDdlOWNmLTg3YjgtNDE3NC1hYTViLWU3NjQ5N2Q3MzkyYi92Mi4wIiwiaWF0IjoxNzEwNzQ0MzA4LCJuYmYiOjE3MTA3NDQzMDgsImV4cCI6MTcxMDc0ODIwOCwiYWlvIjoiNDJaZ1lPQS9KL0t0ZTIzdWFzN2dEMGFPdW80WHpmdjlmMXhUREp6ZlZsNzk0VWkwWndVQSIsImF6cCI6ImM3MzE3Zjg4LTdjZWEtNGU0OC1hYzU3LWExNjA3MWY3Yjg4NCIsImF6cGFjciI6IjEiLCJvaWQiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJyaCI6IjAuREFJQXota0hTTGlIZEVHcVctZGtsOWM1S3dRM1pITG5zaVpMdUlHOVdHWG5wNlVCQUFBLiIsInJvbGVzIjpbIklFUyJdLCJzdWIiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJ0aWQiOiI0ODA3ZTljZi04N2I4LTQxNzQtYWE1Yi1lNzY0OTdkNzM5MmIiLCJ1dGkiOiJ6MGhKdDg0c1UwMlpxNTJNdGNRS0FBIiwidmVyIjoiMi4wIn0.U9KWPccJQ4ZgfzHBNTK6swuEJvkZM8ebfrOpAl7uDoNynlVEsp8CcQ2a1PIO3V8X8tcNassCplc08qbCqcL49R3S5vP2bWTiucT7c_fyCogxMYpmRM0RmGz8RF1tFF3VAxG4_q1Gt9LgzLbNhxxCQcbgJuZhUMKAP246esHlJrkue88FwgmMxf_rwxDH8VwbAchHtSuIRbj3zYSzgKU0Lpmibrr88g_gQZ061f4RyRezTmKLoOqhoGjEosOOnXOdI67YyxpdMjm64uzc92fNUyC9PHvu8XbTTbdUlkU2lPgrk5xFfyVus9SdxWQXB4PCIDhcBsoVQQlWGmY-kvw_8w").getPayload("roles");
System.out.println(roles);
// Object roles = JWTUtil.parseToken("eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlR4OFo1d2lkT2VSdDVCSGdWX2lvdXpzbjd6OCJ9.eyJhdWQiOiI3MjY0MzcwNC1iMmU3LTRiMjYtYjg4MS1iZDU4NjVlN2E3YTUiLCJpc3MiOiJodHRwczovL2xvZ2luLnBhcnRuZXIubWljcm9zb2Z0b25saW5lLmNuLzQ4MDdlOWNmLTg3YjgtNDE3NC1hYTViLWU3NjQ5N2Q3MzkyYi92Mi4wIiwiaWF0IjoxNzEwNzQ0MzA4LCJuYmYiOjE3MTA3NDQzMDgsImV4cCI6MTcxMDc0ODIwOCwiYWlvIjoiNDJaZ1lPQS9KL0t0ZTIzdWFzN2dEMGFPdW80WHpmdjlmMXhUREp6ZlZsNzk0VWkwWndVQSIsImF6cCI6ImM3MzE3Zjg4LTdjZWEtNGU0OC1hYzU3LWExNjA3MWY3Yjg4NCIsImF6cGFjciI6IjEiLCJvaWQiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJyaCI6IjAuREFJQXota0hTTGlIZEVHcVctZGtsOWM1S3dRM1pITG5zaVpMdUlHOVdHWG5wNlVCQUFBLiIsInJvbGVzIjpbIklFUyJdLCJzdWIiOiJkNDliYjc4Mi02ZGMyLTQ5MzktODY3OC1kMzNjMjY3MTliZjkiLCJ0aWQiOiI0ODA3ZTljZi04N2I4LTQxNzQtYWE1Yi1lNzY0OTdkNzM5MmIiLCJ1dGkiOiJ6MGhKdDg0c1UwMlpxNTJNdGNRS0FBIiwidmVyIjoiMi4wIn0.U9KWPccJQ4ZgfzHBNTK6swuEJvkZM8ebfrOpAl7uDoNynlVEsp8CcQ2a1PIO3V8X8tcNassCplc08qbCqcL49R3S5vP2bWTiucT7c_fyCogxMYpmRM0RmGz8RF1tFF3VAxG4_q1Gt9LgzLbNhxxCQcbgJuZhUMKAP246esHlJrkue88FwgmMxf_rwxDH8VwbAchHtSuIRbj3zYSzgKU0Lpmibrr88g_gQZ061f4RyRezTmKLoOqhoGjEosOOnXOdI67YyxpdMjm64uzc92fNUyC9PHvu8XbTTbdUlkU2lPgrk5xFfyVus9SdxWQXB4PCIDhcBsoVQQlWGmY-kvw_8w").getPayload("roles");
// System.out.println(roles);
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(new SecretKeySpec("fXO6ko/qyXeYrkecPeKdgXnuLXf9vMEtnBC9OB3s+aA=".getBytes(StandardCharsets.UTF_8), "HmacSHA256"))
.setAllowedClockSkewSeconds(315360000)
.parseClaimsJws("eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ3d3cud2ludGVhY2guY24iLCJzdWIiOiI1NTUzMzMwNzQ1OTU4NzY4NjQiLCJleHAiOiIxNzExNjE1MDIxIiwibmFtZSI6IuWRqOWTjeWGmyIsInBpY3R1cmUiOm51bGwsImNkanhqeSI6eyJuaWNrbmFtZSI6IuWRqOWTjeWGmyIsInBpY3R1cmUiOm51bGwsImJpbmRJZCI6IjE4NDgyMTMzMDk0Iiwib3BlbmlkIjpudWxsLCJ1bmlvbmlkIjoiMWE4ZWFkNDYtY2JkMS00Y2VlLTg2NzEtZTU0NmMwZmQ4NTMyIn19._6ltuihVcWopQQqfgpSLo3aMVKE3MJhb28NQBSL_w7s")
.getBody();
System.out.println(claims);
} catch (Exception e) {
log.warn("token 解析出错:{}",e.getMessage());
}
}
@Test

Loading…
Cancel
Save