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;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import xyz.thewhitedog9487.WebAPI.Configuration.Security.ApiKeyAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
class SpringSecurityConfiguration {
|
||||
@Autowired ApiKeyAuthenticationFilter ApiKeyAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* @see ApiKeyAuthenticationFilter#shouldNotFilter(HttpServletRequest)
|
||||
*/
|
||||
@Bean
|
||||
SecurityFilterChain CustomSecurityFilterChain(HttpSecurity Security) throws Exception {
|
||||
Security
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests(AuthorizationManagerRequestMatcherRegistry -> {
|
||||
AuthorizationManagerRequestMatcherRegistry
|
||||
.requestMatchers("/ip/**", "/message/**")
|
||||
.requestMatchers("/ip/**")
|
||||
.permitAll()
|
||||
.requestMatchers("/", "/v3/api-docs/**","swagger-ui/**", "/swagger-ui.html")
|
||||
.permitAll()
|
||||
.requestMatchers("/message/**")
|
||||
.authenticated()
|
||||
.anyRequest()
|
||||
.denyAll(); });
|
||||
.denyAll(); })
|
||||
.addFilterBefore(ApiKeyAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
return Security.build(); }
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -117,7 +116,6 @@ class Message {
|
||||
@PostMapping("/discord")
|
||||
ResponseEntity<ResponseData> DiscordPush(
|
||||
@Parameter(description = "用于身份验证的API密钥", required = true, example = "ds1858dscc8745sfwe")
|
||||
@RequestHeader(value = "X-API-Key", required = true) String ApiKey,
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(
|
||||
description = "包含频道ID和消息内容的JSON对象",
|
||||
required = true,
|
||||
@@ -125,12 +123,6 @@ class Message {
|
||||
mediaType = MediaType.APPLICATION_JSON_VALUE,
|
||||
schema = @Schema(implementation = PostMessageData.class) ) )
|
||||
@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);
|
||||
log.info("准备向{}发送消息: {}", ChannelID.asString(), RequestBody.Content);
|
||||
|
||||
@@ -5,8 +5,8 @@ import discord4j.core.GatewayDiscordClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import xyz.thewhitedog9487.WebAPI.Configuration.Security.ApiKeyAuthenticationFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -46,4 +46,8 @@ class GlobalSharedBean {
|
||||
log.error("API密钥文件不存在且无法创建API密钥文件。", e1);
|
||||
System.exit(-1); }
|
||||
return null; } }
|
||||
|
||||
@Bean
|
||||
ApiKeyAuthenticationFilter ApiKeyAuthenticationFilter(){
|
||||
return new ApiKeyAuthenticationFilter(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user