暂存提交
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* 访问令牌会话有效性校验扩展点,用于在 JWT 校验之外确认服务端登录会话状态。
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AccessTokenSessionValidator {
|
||||
void validate(Jwt jwt);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 统一认证用户主体,携带用户、租户、成员身份、应用端、客户端类型及权限信息。
|
||||
*/
|
||||
public record AuthenticatedUser(Long userId, Long tenantId, Long operatorTenantId, Long membershipId,
|
||||
String username, String appCode, String clientType,
|
||||
Set<String> permissions) implements Principal {
|
||||
public AuthenticatedUser {
|
||||
permissions = permissions == null ? Set.of() : Set.copyOf(permissions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 系统 JWT 认证令牌实现,封装已认证用户主体、原始令牌和权限集合。
|
||||
*/
|
||||
public final class BaiyueJwtAuthenticationToken extends AbstractAuthenticationToken {
|
||||
private final AuthenticatedUser principal;
|
||||
private final String tokenValue;
|
||||
|
||||
public BaiyueJwtAuthenticationToken(AuthenticatedUser principal, String tokenValue,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
this.tokenValue = tokenValue;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCredentials() { return tokenValue; }
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser getPrincipal() { return principal; }
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import com.baiyue.common.core.mybatis.AuditorProvider;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* Servlet 服务安全自动配置,建立无状态 JWT 认证链、统一异常响应和审计操作者解析。
|
||||
*/
|
||||
@AutoConfiguration(
|
||||
after = HmacJwtDecoderAutoConfiguration.class,
|
||||
before = SecurityAutoConfiguration.class)
|
||||
@EnableMethodSecurity
|
||||
public class BaiyueSecurityAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JwtClaimsMapper jwtClaimsMapper() { return new JwtClaimsMapper(); }
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AuthenticationEntryPoint.class)
|
||||
AuthenticationEntryPoint jsonAuthenticationEntryPoint(ObjectMapper objectMapper) {
|
||||
return new JsonAuthenticationEntryPoint(objectMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(AccessDeniedHandler.class)
|
||||
AccessDeniedHandler jsonAccessDeniedHandler(ObjectMapper objectMapper) {
|
||||
return new JsonAccessDeniedHandler(objectMapper);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JwtAuthenticationFilter jwtAuthenticationFilter(JwtDecoder jwtDecoder, JwtClaimsMapper claimsMapper,
|
||||
AuthenticationEntryPoint entryPoint,
|
||||
StringRedisTemplate redisTemplate,
|
||||
@Value("${baiyue.security.token-key-prefix:baiyue:auth}") String keyPrefix) {
|
||||
return new JwtAuthenticationFilter(jwtDecoder, claimsMapper, entryPoint,
|
||||
new RedisAccessTokenSessionValidator(redisTemplate, keyPrefix));
|
||||
}
|
||||
|
||||
@Bean
|
||||
FilterRegistrationBean<JwtAuthenticationFilter> disableContainerRegistration(
|
||||
JwtAuthenticationFilter filter) {
|
||||
FilterRegistrationBean<JwtAuthenticationFilter> registration = new FilterRegistrationBean<>(filter);
|
||||
registration.setEnabled(false);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SecurityFilterChain.class)
|
||||
SecurityFilterChain baiyueSecurityFilterChain(
|
||||
HttpSecurity http, JwtAuthenticationFilter jwtFilter,
|
||||
AuthenticationEntryPoint entryPoint, AccessDeniedHandler deniedHandler) throws Exception {
|
||||
return http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.httpBasic(httpBasic -> httpBasic.disable())
|
||||
.formLogin(form -> form.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.exceptionHandling(errors -> errors
|
||||
.authenticationEntryPoint(entryPoint).accessDeniedHandler(deniedHandler))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/actuator/health", "/v3/api-docs/**", "/swagger-ui/**").permitAll()
|
||||
.requestMatchers("/internal/**").permitAll()
|
||||
.requestMatchers("/api/merchant/**").hasAuthority("APP_MERCHANT")
|
||||
.requestMatchers("/api/platform/**").hasAuthority("APP_PLATFORM")
|
||||
.anyRequest().authenticated())
|
||||
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
AuditorProvider securityAuditorProvider() { return new SecurityAuditorProvider(); }
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtValidators;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* HMAC JWT 解码器自动配置,按共享密钥和签发方构建统一令牌验证器。
|
||||
*/
|
||||
@AutoConfiguration(before = BaiyueSecurityAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "baiyue.security.jwt-secret")
|
||||
public class HmacJwtDecoderAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(JwtDecoder.class)
|
||||
JwtDecoder jwtDecoder(@Value("${baiyue.security.jwt-secret}") String jwtSecret,
|
||||
@Value("${baiyue.security.issuer:auth-service}") String issuer) {
|
||||
byte[] secret = jwtSecret.getBytes(StandardCharsets.UTF_8);
|
||||
if (secret.length < 32) throw new IllegalStateException("JWT_SECRET 至少需要 32 字节");
|
||||
NimbusJwtDecoder decoder = NimbusJwtDecoder.withSecretKey(new SecretKeySpec(secret, "HmacSHA256"))
|
||||
.macAlgorithm(MacAlgorithm.HS256).build();
|
||||
decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(issuer));
|
||||
return decoder;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
/**
|
||||
* JWT 认证失败异常,表示令牌无效、过期或对应登录会话已失效。
|
||||
*/
|
||||
public class InvalidJwtAuthenticationException extends AuthenticationException {
|
||||
public InvalidJwtAuthenticationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.web.CorrelationContext;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 访问拒绝处理器,以统一 JSON 结构返回 HTTP 403 错误。
|
||||
*/
|
||||
public class JsonAccessDeniedHandler implements AccessDeniedHandler {
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JsonAccessDeniedHandler(ObjectMapper objectMapper) { this.objectMapper = objectMapper; }
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
AccessDeniedException exception) throws IOException {
|
||||
ResultCode code = ResultCode.FORBIDDEN;
|
||||
response.setStatus(code.httpStatus().value());
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
objectMapper.writeValue(response.getWriter(), ApiResponse.failure(code, code.message(),
|
||||
CorrelationContext.requestId(request), CorrelationContext.traceId(request)));
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.web.CorrelationContext;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 未认证请求处理器,以统一 JSON 结构返回 HTTP 401 错误。
|
||||
*/
|
||||
public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JsonAuthenticationEntryPoint(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException {
|
||||
ResultCode code = ResultCode.UNAUTHORIZED;
|
||||
response.setStatus(code.httpStatus().value());
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
objectMapper.writeValue(response.getWriter(), ApiResponse.failure(code, code.message(),
|
||||
CorrelationContext.requestId(request), CorrelationContext.traceId(request)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Servlet JWT 认证过滤器,解析 Bearer 令牌、校验会话并建立当前请求的安全上下文。
|
||||
*/
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
private static final String BEARER = "Bearer ";
|
||||
private final JwtDecoder jwtDecoder;
|
||||
private final JwtClaimsMapper claimsMapper;
|
||||
private final AuthenticationEntryPoint authenticationEntryPoint;
|
||||
private final AccessTokenSessionValidator sessionValidator;
|
||||
|
||||
public JwtAuthenticationFilter(JwtDecoder jwtDecoder, JwtClaimsMapper claimsMapper,
|
||||
AuthenticationEntryPoint authenticationEntryPoint) {
|
||||
this(jwtDecoder, claimsMapper, authenticationEntryPoint, jwt -> { });
|
||||
}
|
||||
|
||||
public JwtAuthenticationFilter(JwtDecoder jwtDecoder, JwtClaimsMapper claimsMapper,
|
||||
AuthenticationEntryPoint authenticationEntryPoint,
|
||||
AccessTokenSessionValidator sessionValidator) {
|
||||
this.jwtDecoder = jwtDecoder;
|
||||
this.claimsMapper = claimsMapper;
|
||||
this.authenticationEntryPoint = authenticationEntryPoint;
|
||||
this.sessionValidator = sessionValidator;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (header == null || !header.regionMatches(true, 0, BEARER, 0, BEARER.length())) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
if (SecurityContextHolder.getContext().getAuthentication() != null) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String tokenValue = header.substring(BEARER.length()).trim();
|
||||
try {
|
||||
Jwt jwt = jwtDecoder.decode(tokenValue);
|
||||
sessionValidator.validate(jwt);
|
||||
Authentication authentication = claimsMapper.map(jwt);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (JwtException | IllegalArgumentException exception) {
|
||||
SecurityContextHolder.clearContext();
|
||||
authenticationEntryPoint.commence(request, response,
|
||||
new InvalidJwtAuthenticationException("Invalid or expired access token", exception));
|
||||
} finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* JWT 声明映射器,将令牌中的用户、租户、端类型和权限声明转换为认证主体与授权集合。
|
||||
*/
|
||||
public class JwtClaimsMapper {
|
||||
public BaiyueJwtAuthenticationToken map(Jwt jwt) {
|
||||
Set<String> permissions = new LinkedHashSet<>();
|
||||
addClaimValues(permissions, jwt.getClaims().get("authorities"));
|
||||
addClaimValues(permissions, jwt.getClaims().get("permissions"));
|
||||
addClaimValues(permissions, jwt.getClaims().get("scope"));
|
||||
addRoles(permissions, jwt.getClaims().get("roles"));
|
||||
|
||||
String appCode = jwt.getClaimAsString("app_code");
|
||||
if (appCode != null && !appCode.isBlank()) {
|
||||
permissions.add("APP_" + appCode.toUpperCase(java.util.Locale.ROOT));
|
||||
}
|
||||
String username = firstNonBlank(jwt.getClaimAsString("username"),
|
||||
jwt.getClaimAsString("preferred_username"), jwt.getSubject());
|
||||
Long userId = longClaim(jwt, "user_id");
|
||||
AuthenticatedUser user = new AuthenticatedUser(
|
||||
userId == null ? longValue(jwt.getSubject()) : userId,
|
||||
longClaim(jwt, "tenant_id"),
|
||||
longClaim(jwt, "operator_tenant_id"),
|
||||
longClaim(jwt, "membership_id"),
|
||||
username,
|
||||
jwt.getClaimAsString("app_code"),
|
||||
jwt.getClaimAsString("client_type"),
|
||||
permissions);
|
||||
List<SimpleGrantedAuthority> authorities = permissions.stream()
|
||||
.map(SimpleGrantedAuthority::new).toList();
|
||||
return new BaiyueJwtAuthenticationToken(user, jwt.getTokenValue(), authorities);
|
||||
}
|
||||
|
||||
private void addClaimValues(Set<String> target, Object value) {
|
||||
if (value instanceof Collection<?> collection) {
|
||||
collection.stream().map(Object::toString).filter(s -> !s.isBlank()).forEach(target::add);
|
||||
} else if (value instanceof String text) {
|
||||
for (String item : text.split("[ ,]+")) {
|
||||
if (!item.isBlank()) target.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addRoles(Set<String> target, Object value) {
|
||||
Set<String> roles = new LinkedHashSet<>();
|
||||
addClaimValues(roles, value);
|
||||
roles.stream().map(role -> role.startsWith("ROLE_") ? role : "ROLE_" + role).forEach(target::add);
|
||||
}
|
||||
|
||||
private Long longValue(String value) {
|
||||
if (value == null || value.isBlank()) return null;
|
||||
try { return Long.valueOf(value); }
|
||||
catch (NumberFormatException ignored) { return null; }
|
||||
}
|
||||
|
||||
private Long longClaim(Jwt jwt, String name) {
|
||||
Object value = jwt.getClaims().get(name);
|
||||
if (value instanceof Number number) return number.longValue();
|
||||
if (value instanceof String text) return longValue(text);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String firstNonBlank(String... values) {
|
||||
for (String value : values) if (value != null && !value.isBlank()) return value;
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
/**
|
||||
* Redis 访问令牌会话校验器,根据 JWT ID 判断登录会话是否仍然有效。
|
||||
*/
|
||||
public final class RedisAccessTokenSessionValidator implements AccessTokenSessionValidator {
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final String keyPrefix;
|
||||
|
||||
public RedisAccessTokenSessionValidator(StringRedisTemplate redisTemplate, String keyPrefix) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.keyPrefix = RedisTokenKeys.normalize(keyPrefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Jwt jwt) {
|
||||
String jwtId = jwt.getId();
|
||||
if (jwtId == null || jwtId.isBlank()) {
|
||||
throw new IllegalArgumentException("Access token has no jti");
|
||||
}
|
||||
try {
|
||||
if (!Boolean.TRUE.equals(redisTemplate.hasKey(RedisTokenKeys.access(keyPrefix, jwtId)))) {
|
||||
throw new IllegalArgumentException("Access token session is revoked or expired");
|
||||
}
|
||||
} catch (org.springframework.dao.DataAccessException exception) {
|
||||
throw new IllegalArgumentException("Token session store is unavailable", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
/**
|
||||
* Redis 令牌键工具,规范访问令牌会话键前缀及键名拼装规则。
|
||||
*/
|
||||
public final class RedisTokenKeys {
|
||||
private RedisTokenKeys() { }
|
||||
|
||||
public static String access(String prefix, String jwtId) {
|
||||
return normalize(prefix) + ":access:" + jwtId;
|
||||
}
|
||||
|
||||
public static String refresh(String prefix, String refreshHash) {
|
||||
return normalize(prefix) + ":refresh:" + refreshHash;
|
||||
}
|
||||
|
||||
public static String normalize(String prefix) {
|
||||
String value = prefix == null || prefix.isBlank() ? "baiyue:auth" : prefix.trim();
|
||||
while (value.endsWith(":")) {
|
||||
value = value.substring(0, value.length() - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
import com.baiyue.common.core.mybatis.AuditorProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* Spring Security 审计操作者提供器,从当前认证主体提取用户 ID 供持久化审计字段自动填充。
|
||||
*/
|
||||
public class SecurityAuditorProvider implements AuditorProvider {
|
||||
@Override
|
||||
public Long currentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
return authentication != null && authentication.getPrincipal() instanceof AuthenticatedUser user
|
||||
? user.userId() : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user