mirror of
https://github.com/TheWhiteDog9487/WebAPI.git
synced 2026-08-11 23:01:30 +08:00
将API密钥鉴权逻辑抽取到一个单独的过滤器内
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
|||||||
|
package xyz.thewhitedog9487.WebAPI.Configuration.Security;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import xyz.thewhitedog9487.WebAPI.Controller.ResponseData;
|
||||||
|
import xyz.thewhitedog9487.WebAPI.Data.Entity.AccessLog;
|
||||||
|
import xyz.thewhitedog9487.WebAPI.Data.Repository.AccessLogRepository;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired List<String> ApiKeyList;
|
||||||
|
@Autowired AccessLogRepository AccessLogRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
|
var ServletPath = request.getServletPath();
|
||||||
|
var PermitPrefix = List.of(
|
||||||
|
"/ip/",
|
||||||
|
"/v3/api-docs",
|
||||||
|
"/swagger-ui/" );
|
||||||
|
var FullyMatchList = List.of(
|
||||||
|
"/",
|
||||||
|
"/swagger-ui.html" );
|
||||||
|
for (String Prefix : PermitPrefix) {
|
||||||
|
if ( ServletPath.startsWith(Prefix) ) {
|
||||||
|
return true; } }
|
||||||
|
for (String FullyMatch : FullyMatchList) {
|
||||||
|
if ( ServletPath.equals(FullyMatch) ) {
|
||||||
|
return true; } }
|
||||||
|
return false; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
String ApiKey = request.getHeader("X-API-Key");
|
||||||
|
if (ApiKey == null) {
|
||||||
|
log.warn("API密钥验证失败,未传递X-API-Key请求头");
|
||||||
|
var ResponseBody = new ResponseData(
|
||||||
|
HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"API密钥验证失败,未传递X-API-Key请求头");
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write(ResponseBody.ToJson());
|
||||||
|
var Log = new AccessLog(
|
||||||
|
null,
|
||||||
|
request.getRequestId(),
|
||||||
|
request.getHeader("CF-Connecting-IP".toLowerCase() ),
|
||||||
|
request.getHeader("X-Forwarded-For".toLowerCase() ),
|
||||||
|
request.getRemoteAddr(),
|
||||||
|
request.getHeader("CF-IPCountry".toLowerCase() ),
|
||||||
|
( request.getHeader("CF-IPCountry".toLowerCase() ) == null ) ? null : Locale.of(Locale.PRC.getLanguage(), request.getHeader("CF-IPCountry".toLowerCase() ), Locale.SIMPLIFIED_CHINESE.getVariant()).getISO3Country(),
|
||||||
|
request.getHeader("User-Agent"),
|
||||||
|
request.getMethod(),
|
||||||
|
request.getScheme(),
|
||||||
|
request.getProtocol(),
|
||||||
|
request.getRequestURL().toString(),
|
||||||
|
request.getQueryString(),
|
||||||
|
Collections.list( request.getHeaderNames() )
|
||||||
|
.stream()
|
||||||
|
.map( name -> name + ": " + Collections.list(request.getHeaders(name)) )
|
||||||
|
.reduce( ( a, b ) -> a + "\n" + b )
|
||||||
|
.orElse(""),
|
||||||
|
Instant.now(),
|
||||||
|
HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
ResponseBody.ToJson());
|
||||||
|
AccessLogRepository.save(Log);
|
||||||
|
return; }
|
||||||
|
if (ApiKeyList.contains(ApiKey) == true) {
|
||||||
|
var Auth = new UsernamePasswordAuthenticationToken(
|
||||||
|
"",
|
||||||
|
ApiKey,
|
||||||
|
List.of(new SimpleGrantedAuthority("API")));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(Auth);
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
log.warn("API密钥验证失败,密钥:{}", ApiKey);
|
||||||
|
var ResponseBody = new ResponseData(
|
||||||
|
HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
"API密钥验证失败",
|
||||||
|
Map.of("提供的密钥:", ApiKey));
|
||||||
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write(ResponseBody.ToJson());
|
||||||
|
var Log = new AccessLog(
|
||||||
|
null,
|
||||||
|
request.getRequestId(),
|
||||||
|
request.getHeader("CF-Connecting-IP".toLowerCase() ),
|
||||||
|
request.getHeader("X-Forwarded-For".toLowerCase() ),
|
||||||
|
request.getRemoteAddr(),
|
||||||
|
request.getHeader("CF-IPCountry".toLowerCase() ),
|
||||||
|
( request.getHeader("CF-IPCountry".toLowerCase() ) == null ) ? null : Locale.of(Locale.PRC.getLanguage(), request.getHeader("CF-IPCountry".toLowerCase() ), Locale.SIMPLIFIED_CHINESE.getVariant()).getISO3Country(),
|
||||||
|
request.getHeader("User-Agent"),
|
||||||
|
request.getMethod(),
|
||||||
|
request.getScheme(),
|
||||||
|
request.getProtocol(),
|
||||||
|
request.getRequestURL().toString(),
|
||||||
|
request.getQueryString(),
|
||||||
|
Collections.list( request.getHeaderNames() )
|
||||||
|
.stream()
|
||||||
|
.map( name -> name + ": " + Collections.list(request.getHeaders(name)) )
|
||||||
|
.reduce( ( a, b ) -> a + "\n" + b )
|
||||||
|
.orElse(""),
|
||||||
|
Instant.now(),
|
||||||
|
HttpStatus.UNAUTHORIZED.value(),
|
||||||
|
ResponseBody.ToJson() );
|
||||||
|
AccessLogRepository.save(Log);
|
||||||
|
return; } } }
|
||||||
|
|
||||||
+13
-2
@@ -1,25 +1,36 @@
|
|||||||
package xyz.thewhitedog9487.WebAPI.Configuration;
|
package xyz.thewhitedog9487.WebAPI.Configuration;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import xyz.thewhitedog9487.WebAPI.Configuration.Security.ApiKeyAuthenticationFilter;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class SpringSecurityConfiguration {
|
class SpringSecurityConfiguration {
|
||||||
|
@Autowired ApiKeyAuthenticationFilter ApiKeyAuthenticationFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ApiKeyAuthenticationFilter#shouldNotFilter(HttpServletRequest)
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain CustomSecurityFilterChain(HttpSecurity Security) throws Exception {
|
SecurityFilterChain CustomSecurityFilterChain(HttpSecurity Security) throws Exception {
|
||||||
Security
|
Security
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.authorizeHttpRequests(AuthorizationManagerRequestMatcherRegistry -> {
|
.authorizeHttpRequests(AuthorizationManagerRequestMatcherRegistry -> {
|
||||||
AuthorizationManagerRequestMatcherRegistry
|
AuthorizationManagerRequestMatcherRegistry
|
||||||
.requestMatchers("/ip/**", "/message/**")
|
.requestMatchers("/ip/**")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.requestMatchers("/", "/v3/api-docs/**","swagger-ui/**", "/swagger-ui.html")
|
.requestMatchers("/", "/v3/api-docs/**","swagger-ui/**", "/swagger-ui.html")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
|
.requestMatchers("/message/**")
|
||||||
|
.authenticated()
|
||||||
.anyRequest()
|
.anyRequest()
|
||||||
.denyAll(); });
|
.denyAll(); })
|
||||||
|
.addFilterBefore(ApiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
return Security.build(); }
|
return Security.build(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -117,7 +116,6 @@ class Message {
|
|||||||
@PostMapping("/discord")
|
@PostMapping("/discord")
|
||||||
ResponseEntity<ResponseData> DiscordPush(
|
ResponseEntity<ResponseData> DiscordPush(
|
||||||
@Parameter(description = "用于身份验证的API密钥", required = true, example = "ds1858dscc8745sfwe")
|
@Parameter(description = "用于身份验证的API密钥", required = true, example = "ds1858dscc8745sfwe")
|
||||||
@RequestHeader(value = "X-API-Key", required = true) String ApiKey,
|
|
||||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||||
description = "包含频道ID和消息内容的JSON对象",
|
description = "包含频道ID和消息内容的JSON对象",
|
||||||
required = true,
|
required = true,
|
||||||
@@ -125,12 +123,6 @@ class Message {
|
|||||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||||
schema = @Schema(implementation = PostMessageData.class) ) )
|
schema = @Schema(implementation = PostMessageData.class) ) )
|
||||||
@RequestBody PostMessageData RequestBody){
|
@RequestBody PostMessageData RequestBody){
|
||||||
if ( ApiKeyList.contains(ApiKey) == false ) {
|
|
||||||
log.warn("API密钥验证失败,密钥:{}", ApiKey);
|
|
||||||
return new ResponseEntity<>(new ResponseData(
|
|
||||||
HttpStatus.UNAUTHORIZED.value(),
|
|
||||||
"API密钥验证失败",
|
|
||||||
Map.of("提供的密钥", ApiKey)), HttpStatus.UNAUTHORIZED); }
|
|
||||||
|
|
||||||
var ChannelID = Snowflake.of(RequestBody.ChannelID);
|
var ChannelID = Snowflake.of(RequestBody.ChannelID);
|
||||||
log.info("准备向{}发送消息: {}", ChannelID.asString(), RequestBody.Content);
|
log.info("准备向{}发送消息: {}", ChannelID.asString(), RequestBody.Content);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import discord4j.core.GatewayDiscordClient;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import xyz.thewhitedog9487.WebAPI.Configuration.Security.ApiKeyAuthenticationFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -46,4 +46,8 @@ class GlobalSharedBean {
|
|||||||
log.error("API密钥文件不存在且无法创建API密钥文件。", e1);
|
log.error("API密钥文件不存在且无法创建API密钥文件。", e1);
|
||||||
System.exit(-1); }
|
System.exit(-1); }
|
||||||
return null; } }
|
return null; } }
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ApiKeyAuthenticationFilter ApiKeyAuthenticationFilter(){
|
||||||
|
return new ApiKeyAuthenticationFilter(); }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user