feat:鉴权集中到网关

This commit is contained in:
panxuejie 2026-01-14 09:59:23 +08:00
parent a85a68e8b0
commit 03a37dd51e
4 changed files with 79 additions and 138 deletions

View File

@ -1,6 +1,7 @@
package com.tacit.common.feign.interceptor; package com.tacit.common.feign.interceptor;
import com.tacit.common.constant.CommonConstant; import com.tacit.common.constant.CommonConstant;
import com.tacit.common.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@ -20,44 +21,56 @@ public class FeignAuthInterceptor implements RequestInterceptor {
@Override @Override
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
// 从当前线程获取认证信息 // 先尝试从SecurityUtils获取用户信息推荐方式
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Long userId = SecurityUtils.getCurrentUserId();
String username = SecurityUtils.getCurrentUsername();
String role = SecurityUtils.getCurrentUserRole();
if (authentication != null && !(authentication instanceof org.springframework.security.authentication.AnonymousAuthenticationToken)) { if (userId != null && username != null && role != null) {
// 获取用户名 // 传递用户信息头
String username = authentication.getName(); template.header("X-User-Id", String.valueOf(userId));
template.header("X-Username", username); template.header("X-Username", username);
template.header("X-Role", role);
// 获取角色信息
authentication.getAuthorities().forEach(authority -> {
template.header("X-Role", authority.getAuthority().replace("ROLE_", ""));
});
} else { } else {
// 从当前线程获取认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !(authentication instanceof org.springframework.security.authentication.AnonymousAuthenticationToken)) {
// 获取用户名
String authUsername = authentication.getName();
template.header("X-Username", authUsername);
// 获取角色信息
authentication.getAuthorities().forEach(authority -> {
template.header("X-Role", authority.getAuthority().replace("ROLE_", ""));
});
}
// 从RequestContextHolder中获取请求信息适用于非Feign调用场景 // 从RequestContextHolder中获取请求信息适用于非Feign调用场景
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes) { if (requestAttributes instanceof ServletRequestAttributes) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest(); HttpServletRequest request = servletRequestAttributes.getRequest();
// 传递JWT令牌 // 传递JWT令牌作为后备方案
String authorization = request.getHeader(CommonConstant.JWT_HEADER); String authorization = request.getHeader(CommonConstant.JWT_HEADER);
if (authorization != null && authorization.startsWith(CommonConstant.JWT_PREFIX)) { if (authorization != null && authorization.startsWith(CommonConstant.JWT_PREFIX)) {
template.header(CommonConstant.JWT_HEADER, authorization); template.header(CommonConstant.JWT_HEADER, authorization);
} }
// 传递用户上下文 // 传递用户上下文如果有的话
String userId = request.getHeader("X-User-Id"); String requestUserId = request.getHeader("X-User-Id");
String username = request.getHeader("X-Username"); String requestUsername = request.getHeader("X-Username");
String role = request.getHeader("X-Role"); String requestRole = request.getHeader("X-Role");
if (userId != null) { if (requestUserId != null) {
template.header("X-User-Id", userId); template.header("X-User-Id", requestUserId);
} }
if (username != null) { if (requestUsername != null) {
template.header("X-Username", username); template.header("X-Username", requestUsername);
} }
if (role != null) { if (requestRole != null) {
template.header("X-Role", role); template.header("X-Role", requestRole);
} }
} }
} }

View File

@ -1,10 +1,10 @@
package com.tacit.admin.config; package com.tacit.admin.config;
import com.tacit.common.constant.CommonConstant;
import com.tacit.common.utils.AesPasswordEncoder; import com.tacit.common.utils.AesPasswordEncoder;
import com.tacit.common.utils.JwtUtils; import jakarta.servlet.FilterChain;
import com.tacit.common.redis.utils.RedisUtils; import jakarta.servlet.ServletException;
import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -22,10 +22,6 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -36,9 +32,6 @@ import java.util.HashMap;
@Slf4j @Slf4j
public class SecurityConfig { public class SecurityConfig {
@Resource
private RedisUtils redisUtils;
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http http
@ -46,9 +39,8 @@ public class SecurityConfig {
.cors(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/login", "/auth/register","test/**", "/test/hello", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() // 所有请求都允许通过鉴权由网关完成
.requestMatchers("/test/feign/**").authenticated() .anyRequest().permitAll())
.anyRequest().authenticated())
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
@ -59,62 +51,24 @@ public class SecurityConfig {
return new OncePerRequestFilter() { return new OncePerRequestFilter() {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 先检查是否有JWT令牌直接服务间调用时可能传递JWT // 只从请求头中获取用户信息不做鉴权
String authorization = request.getHeader(CommonConstant.JWT_HEADER); String userIdStr = request.getHeader("X-User-Id");
if (authorization != null && authorization.startsWith(CommonConstant.JWT_PREFIX)) { String username = request.getHeader("X-Username");
String token = authorization.substring(CommonConstant.JWT_PREFIX.length()); String role = request.getHeader("X-Role");
try {
// 验证JWT令牌
if (JwtUtils.validateToken(token)) {
// 从令牌中获取用户信息
Long userId = JwtUtils.getUserIdFromToken(token);
// 构建Redis keytacit:user:token:{userId} if (userIdStr != null && username != null && role != null) {
String redisKey = CommonConstant.REDIS_KEY_TACIT_USER_TOKEN + userId; // 创建认证对象
User principal = new User(username, "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
// 验证Redis中的令牌是否存在且匹配 // 创建用户详情map存储userId等信息
String redisToken = redisUtils.get(redisKey); HashMap<String, Object> userDetails = new HashMap<>();
if (token.equals(redisToken)) { userDetails.put("userId", Long.parseLong(userIdStr));
String username = JwtUtils.getUsernameFromToken(token); userDetails.put("username", username);
String role = JwtUtils.getRoleFromToken(token); userDetails.put("role", role);
// 创建认证对象 var authentication = new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
User principal = new User(username, "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))); authentication.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 创建用户详情map存储userId等信息
HashMap<String, Object> userDetails = new HashMap<>();
userDetails.put("userId", userId);
userDetails.put("username", username);
userDetails.put("role", role);
var authentication = new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
authentication.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
} catch (Exception e) {
log.error("JWT令牌验证失败: {}", e.getMessage());
}
} else {
// 检查是否有用户上下文头网关转发时添加
String userIdStr = request.getHeader("X-User-Id");
String username = request.getHeader("X-Username");
String role = request.getHeader("X-Role");
if (userIdStr != null && username != null && role != null) {
// 创建认证对象
User principal = new User(username, "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
// 创建用户详情map存储userId等信息
HashMap<String, Object> userDetails = new HashMap<>();
userDetails.put("userId", Long.parseLong(userIdStr));
userDetails.put("username", username);
userDetails.put("role", role);
var authentication = new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
authentication.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@ -1,11 +1,7 @@
package com.tacit.app.config; package com.tacit.app.config;
import com.tacit.common.constant.CommonConstant;
import com.tacit.common.redis.utils.RedisUtils;
import com.tacit.common.utils.AesPasswordEncoder; import com.tacit.common.utils.AesPasswordEncoder;
import com.tacit.common.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
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.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@ -27,6 +23,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@ -34,9 +31,6 @@ import java.util.Collections;
@Slf4j @Slf4j
public class AppSecurityConfig { public class AppSecurityConfig {
@Autowired
private RedisUtils redisUtils;
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http http
@ -44,9 +38,8 @@ public class AppSecurityConfig {
.cors(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth .authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/user/info/**").permitAll() // 所有请求都允许通过鉴权由网关完成
.anyRequest().authenticated() .anyRequest().permitAll())
)
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build(); return http.build();
@ -57,45 +50,23 @@ public class AppSecurityConfig {
return new OncePerRequestFilter() { return new OncePerRequestFilter() {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 先检查是否有JWT令牌直接服务间调用时可能传递JWT // 只从请求头中获取用户信息不做鉴权
String authorization = request.getHeader(CommonConstant.JWT_HEADER); String userIdStr = request.getHeader("X-User-Id");
if (authorization != null && authorization.startsWith(CommonConstant.JWT_PREFIX)) { String username = request.getHeader("X-Username");
String token = authorization.substring(CommonConstant.JWT_PREFIX.length()); String role = request.getHeader("X-Role");
try {
// 验证JWT令牌
if (JwtUtils.validateToken(token)) {
// 从令牌中获取用户信息
Long userId = JwtUtils.getUserIdFromToken(token);
// 构建Redis keytacit:user:token:{userId} if (userIdStr != null && username != null && role != null) {
String redisKey = CommonConstant.REDIS_KEY_TACIT_USER_TOKEN + userId; User principal = new User(username, "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
var authentication = new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
// 验证Redis中的令牌是否存在且匹配 // 创建用户详情map存储userId等信息
String redisToken = redisUtils.get(redisKey); HashMap<String, Object> userDetails = new HashMap<>();
if (token.equals(redisToken)) { userDetails.put("userId", Long.parseLong(userIdStr));
String username = JwtUtils.getUsernameFromToken(token); userDetails.put("username", username);
String role = JwtUtils.getRoleFromToken(token); userDetails.put("role", role);
authentication.setDetails(userDetails);
// 创建认证对象 SecurityContextHolder.getContext().setAuthentication(authentication);
User principal = new User(username, "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
var authentication = new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
} catch (Exception e) {
log.error("JWT令牌验证失败: {}", e.getMessage());
}
} else {
// 检查是否有用户上下文头网关转发或服务间调用时添加
String userId = request.getHeader("X-User-Id");
String username = request.getHeader("X-Username");
String role = request.getHeader("X-Role");
if (userId != null && username != null && role != null) {
User principal = new User(username, "", Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())));
var authentication = new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);

View File

@ -10,6 +10,7 @@ import com.tacit.app.service.UserService;
import com.tacit.common.constant.CommonConstant; import com.tacit.common.constant.CommonConstant;
import com.tacit.common.exception.BusinessException; import com.tacit.common.exception.BusinessException;
import com.tacit.common.utils.JwtUtils; import com.tacit.common.utils.JwtUtils;
import com.tacit.common.utils.SecurityUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@ -91,6 +92,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override @Override
public User getUserInfo(Long userId) { public User getUserInfo(Long userId) {
Long currentUserId = SecurityUtils.getCurrentUserId();
System.out.println("currentUserId: " + currentUserId);
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", userId) queryWrapper.eq("id", userId)
.eq("del_flag", 0); .eq("del_flag", 0);