有循环依赖
The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | jwtAuthenticationFilter defined in file [D:\work\java-space\deploy-ease-platform\backend\target\classes\com\qqchen\deploy\backend\common\security\filter\JwtAuthenticationFilter.class] ↑ ↓ | securityConfig defined in file [D:\work\java-space\deploy-ease-platform\backend\target\classes\com\qqchen\deploy\backend\common\security\config\SecurityConfig.class] └─────┘
This commit is contained in:
parent
68d42ce6f0
commit
06aaead053
@ -24,6 +24,8 @@
|
||||
<lombok.version>1.18.30</lombok.version>
|
||||
<spring-security.version>6.2.0</spring-security.version>
|
||||
<jjwt.version>0.12.3</jjwt.version>
|
||||
<springdoc.version>2.2.0</springdoc.version>
|
||||
<hutool.version>5.8.23</hutool.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -140,6 +142,18 @@
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<!-- Swagger -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<!-- Hutool -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -4,12 +4,15 @@ import com.qqchen.deploy.backend.common.controller.BaseController;
|
||||
import com.qqchen.deploy.backend.common.api.Response;
|
||||
import com.qqchen.deploy.backend.common.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.converter.UserConverter;
|
||||
import com.qqchen.deploy.backend.dto.request.LoginRequest;
|
||||
import com.qqchen.deploy.backend.dto.response.LoginResponse;
|
||||
import com.qqchen.deploy.backend.entity.User;
|
||||
import com.qqchen.deploy.backend.dto.query.UserQuery;
|
||||
import com.qqchen.deploy.backend.dto.request.UserRequest;
|
||||
import com.qqchen.deploy.backend.dto.response.UserResponse;
|
||||
import com.qqchen.deploy.backend.dto.request.UserRegisterRequest;
|
||||
import com.qqchen.deploy.backend.service.IUserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@ -29,13 +32,18 @@ public class UserApiController extends BaseController<User, Long, UserQuery, Use
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Operation(summary = "用户登录")
|
||||
@PostMapping("/login")
|
||||
public Response<LoginResponse> login(@Validated @RequestBody LoginRequest request) {
|
||||
return Response.success(userService.login(request));
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public Response<UserResponse> register(@Validated @RequestBody UserRegisterRequest request) {
|
||||
// 基础的注册逻辑
|
||||
if (!request.getPassword().equals(request.getConfirmPassword())) {
|
||||
return Response.error(ResponseCode.USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
User user = converter.toEntity(request);
|
||||
User savedUser = userService.register(user);
|
||||
return Response.success(converter.toResponse(savedUser));
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
package com.qqchen.deploy.backend.common.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TenantFilter {
|
||||
/**
|
||||
* 是否启用租户过滤
|
||||
*/
|
||||
boolean value() default true;
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.qqchen.deploy.backend.common.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
|
||||
javaTimeModule.addSerializer(LocalDateTime.class,
|
||||
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
|
||||
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class,
|
||||
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.qqchen.deploy.backend.common.context;
|
||||
|
||||
public class TenantContext {
|
||||
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
|
||||
|
||||
public static void setTenantId(String tenantId) {
|
||||
CURRENT_TENANT.set(tenantId);
|
||||
}
|
||||
|
||||
public static String getTenantId() {
|
||||
return CURRENT_TENANT.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
CURRENT_TENANT.remove();
|
||||
}
|
||||
|
||||
public static boolean isSystemTenant() {
|
||||
String tenantId = getTenantId();
|
||||
return tenantId == null || "0".equals(tenantId);
|
||||
}
|
||||
}
|
||||
@ -13,9 +13,10 @@ public enum ResponseCode {
|
||||
CONFLICT(409, "response.conflict"),
|
||||
|
||||
// 业务错误码
|
||||
USER_NOT_FOUND(1001, "user.not.found"),
|
||||
USERNAME_EXISTS(1002, "user.username.exists"),
|
||||
EMAIL_EXISTS(1003, "user.email.exists");
|
||||
TENANT_NOT_FOUND(1001, "tenant.not.found"),
|
||||
USER_NOT_FOUND(2001, "user.not.found"),
|
||||
USERNAME_EXISTS(2002, "user.username.exists"),
|
||||
EMAIL_EXISTS(2003, "user.email.exists");
|
||||
|
||||
private final int code;
|
||||
|
||||
|
||||
@ -6,9 +6,11 @@ import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
public abstract class DomainEvent {
|
||||
|
||||
private final String eventId;
|
||||
|
||||
private final LocalDateTime occurredOn;
|
||||
|
||||
|
||||
protected DomainEvent() {
|
||||
this.eventId = java.util.UUID.randomUUID().toString();
|
||||
this.occurredOn = LocalDateTime.now();
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package com.qqchen.deploy.backend.common.exception;
|
||||
|
||||
import com.qqchen.deploy.backend.common.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.common.utils.MessageUtils;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final ResponseCode errorCode;
|
||||
|
||||
public BusinessException(ResponseCode errorCode) {
|
||||
super(MessageUtils.getMessage(errorCode.getMessageKey()));
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
|
||||
public BusinessException(ResponseCode errorCode, Throwable cause) {
|
||||
super(MessageUtils.getMessage(errorCode.getMessageKey()), cause);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.qqchen.deploy.backend.common.exception;
|
||||
|
||||
import com.qqchen.deploy.backend.common.api.Response;
|
||||
import com.qqchen.deploy.backend.common.enums.ResponseCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
// @ExceptionHandler(AuthenticationException.class)
|
||||
// public ApiResult<String> handleAuthenticationException(AuthenticationException e) {
|
||||
// log.error("认证异常", e);
|
||||
// if (e instanceof BadCredentialsException) {
|
||||
// return ApiResult.error(401, "用户名或密码错误");
|
||||
// }
|
||||
// return ApiResult.error(401, "认证失败:" + e.getMessage());
|
||||
// }
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Response<?> handleApiException(BusinessException e) {
|
||||
return Response.error(e.getErrorCode());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Response<?> handleException(Exception e) {
|
||||
log.error("系统错误", e);
|
||||
return Response.error(ResponseCode.ERROR);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package com.qqchen.deploy.backend.common.filter;
|
||||
|
||||
import com.qqchen.deploy.backend.common.context.TenantContext;
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@Order(1)
|
||||
public class TenantFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
try {
|
||||
// 从请求头中获取租户ID
|
||||
String tenantId = req.getHeader("X-Tenant-ID");
|
||||
if (tenantId != null) {
|
||||
TenantContext.setTenantId(tenantId);
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
} finally {
|
||||
// 清理租户上下文
|
||||
TenantContext.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.qqchen.deploy.backend.common.interceptor;
|
||||
|
||||
import com.qqchen.deploy.backend.common.context.TenantContext;
|
||||
import com.qqchen.deploy.backend.common.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.common.exception.BusinessException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class TenantInterceptor implements HandlerInterceptor {
|
||||
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
|
||||
// 不需要进行租户隔离的路径
|
||||
private final List<String> excludePaths = Arrays.asList(
|
||||
"/api/v1/users/login",
|
||||
"/api/system/tenants/list",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**"
|
||||
);
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String path = request.getRequestURI();
|
||||
// 如果是白名单路径,不进行租户校验
|
||||
if (isExcludePath(path)) {
|
||||
TenantContext.clear();
|
||||
return true;
|
||||
}
|
||||
// 获取租户ID
|
||||
String tenantId = request.getHeader("X-Tenant-ID");
|
||||
if (tenantId == null || tenantId.trim().isEmpty()) {
|
||||
throw new BusinessException(ResponseCode.TENANT_NOT_FOUND);
|
||||
}
|
||||
TenantContext.setTenantId(tenantId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
TenantContext.clear();
|
||||
}
|
||||
|
||||
private boolean isExcludePath(String path) {
|
||||
return excludePaths.stream()
|
||||
.anyMatch(pattern -> pathMatcher.match(pattern, path));
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,8 @@
|
||||
package com.qqchen.deploy.backend.common.security.config;
|
||||
|
||||
import com.qqchen.deploy.backend.common.security.filter.JwtAuthenticationFilter;
|
||||
import com.qqchen.deploy.backend.common.security.handler.CustomAuthenticationEntryPoint;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@ -14,6 +17,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
@ -22,8 +26,13 @@ import java.util.Arrays;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
private final CustomAuthenticationEntryPoint authenticationEntryPoint;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
@ -31,10 +40,14 @@ public class SecurityConfig {
|
||||
.csrf(csrf -> csrf.disable()) // 禁用CSRF
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 禁用session
|
||||
.exceptionHandling(exceptions -> exceptions
|
||||
.authenticationEntryPoint(authenticationEntryPoint)
|
||||
)
|
||||
.formLogin(form -> form.disable()) // 禁用form登录
|
||||
.httpBasic(basic -> basic.disable()) // 禁用basic认证
|
||||
.logout(logout -> logout.disable()) // 禁用logout
|
||||
.anonymous(anonymous -> {}) // 允许匿名访问
|
||||
.anonymous(anonymous -> {
|
||||
}) // 允许匿名访问
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// 公开接口
|
||||
// .requestMatchers("/api/v1/users/register").permitAll()
|
||||
@ -47,7 +60,8 @@ public class SecurityConfig {
|
||||
.anyRequest().permitAll()
|
||||
// 生产环境建议改为需要认证
|
||||
//.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@ -23,13 +25,25 @@ import java.io.IOException;
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
// 添加白名单路径
|
||||
private static final List<String> WHITELIST = Arrays.asList(
|
||||
"/api/v1/users/login",
|
||||
"/api/v1/users/register",
|
||||
"/swagger-ui/**",
|
||||
"/v3/api-docs/**"
|
||||
);
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
String path = request.getServletPath();
|
||||
return WHITELIST.stream().anyMatch(path::startsWith);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
try {
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
String token = null;
|
||||
@ -39,10 +53,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
token = authHeader.substring(7);
|
||||
username = jwtTokenUtil.getUsernameFromToken(token);
|
||||
}
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
if (jwtTokenUtil.validateToken(token, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.getAuthorities());
|
||||
@ -50,10 +62,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot set user authentication", e);
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.qqchen.deploy.backend.common.security.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.qqchen.deploy.backend.common.api.Response;
|
||||
import com.qqchen.deploy.backend.common.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.common.utils.MessageUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Component
|
||||
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
Response<?> result;
|
||||
if (authException instanceof BadCredentialsException) {
|
||||
result = Response.error(ResponseCode.UNAUTHORIZED, MessageUtils.getMessage(ResponseCode.SUCCESS.getMessageKey()));
|
||||
} else if (authException instanceof InternalAuthenticationServiceException) {
|
||||
result = Response.error(ResponseCode.USER_NOT_FOUND, MessageUtils.getMessage(ResponseCode.USER_NOT_FOUND.getMessageKey()));
|
||||
} else {
|
||||
result = Response.error(ResponseCode.UNAUTHORIZED, MessageUtils.getMessage(ResponseCode.SUCCESS.getMessageKey()));
|
||||
}
|
||||
|
||||
response.getWriter().write(objectMapper.writeValueAsString(result));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package com.qqchen.deploy.backend.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.qqchen.deploy.backend.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LoginResponse extends UserResponse {
|
||||
|
||||
private String token;
|
||||
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
package com.qqchen.deploy.backend.service;
|
||||
|
||||
import com.qqchen.deploy.backend.common.service.IBaseService;
|
||||
import com.qqchen.deploy.backend.dto.request.LoginRequest;
|
||||
import com.qqchen.deploy.backend.dto.response.LoginResponse;
|
||||
import com.qqchen.deploy.backend.entity.User;
|
||||
|
||||
public interface IUserService extends IBaseService<User, Long> {
|
||||
@ -8,4 +10,6 @@ public interface IUserService extends IBaseService<User, Long> {
|
||||
User findByUsername(String username);
|
||||
boolean checkUsernameExists(String username);
|
||||
boolean checkEmailExists(String email);
|
||||
|
||||
LoginResponse login(LoginRequest request);
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.qqchen.deploy.backend.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.entity.User;
|
||||
import com.qqchen.deploy.backend.repository.IUserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
|
||||
private final IUserRepository userRepository; // 直接使用Repository
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
// 直接查询数据库,不要通过Controller或其他Service
|
||||
return userRepository.findByUsernameAndDeletedFalse(username)
|
||||
.map(this::createUserDetails)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
}
|
||||
|
||||
private UserDetails createUserDetails(User user) {
|
||||
return org.springframework.security.core.userdetails.User
|
||||
.withUsername(user.getUsername())
|
||||
.password(user.getPassword())
|
||||
.authorities(Collections.emptyList()) // 或者从数据库加载权限
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,35 @@
|
||||
package com.qqchen.deploy.backend.service.impl;
|
||||
|
||||
import com.qqchen.deploy.backend.common.enums.ResponseCode;
|
||||
import com.qqchen.deploy.backend.common.exception.BusinessException;
|
||||
import com.qqchen.deploy.backend.common.security.util.JwtTokenUtil;
|
||||
import com.qqchen.deploy.backend.common.service.impl.BaseServiceImpl;
|
||||
import com.qqchen.deploy.backend.dto.request.LoginRequest;
|
||||
import com.qqchen.deploy.backend.dto.response.LoginResponse;
|
||||
import com.qqchen.deploy.backend.entity.User;
|
||||
import com.qqchen.deploy.backend.repository.IUserRepository;
|
||||
import com.qqchen.deploy.backend.service.IUserService;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@Slf4j
|
||||
public class UserServiceImpl extends BaseServiceImpl<User, Long> implements IUserService {
|
||||
|
||||
private final IUserRepository userRepository;
|
||||
|
||||
@Resource
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Resource
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
|
||||
@Autowired
|
||||
public UserServiceImpl(IUserRepository userRepository) {
|
||||
super(userRepository);
|
||||
@ -47,4 +63,24 @@ public class UserServiceImpl extends BaseServiceImpl<User, Long> implements IUse
|
||||
return userRepository.existsByEmail(email);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
|
||||
);
|
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
String token = jwtTokenUtil.generateToken(userDetails);
|
||||
User user = userRepository.findByUsernameAndDeletedFalse(userDetails.getUsername()).orElseThrow(() -> new BusinessException(ResponseCode.USER_NOT_FOUND));
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setId(user.getId());
|
||||
response.setUsername(user.getUsername());
|
||||
response.setNickname(user.getNickname());
|
||||
response.setEmail(user.getEmail());
|
||||
response.setPhone(user.getPhone());
|
||||
response.setToken(token);
|
||||
log.info("用户 {} ({}) 登录成功", user.getUsername(), user.getNickname());
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@ -19,7 +19,7 @@ spring:
|
||||
jdbc:
|
||||
time_zone: Asia/Shanghai
|
||||
mvc:
|
||||
log-request-details: true # \u6253\u5370\u8BF7\u6C42\u8BE6\u60C5
|
||||
log-request-details: true
|
||||
messages:
|
||||
basename: messages
|
||||
encoding: UTF-8
|
||||
@ -35,8 +35,8 @@ logging:
|
||||
level:
|
||||
org.springframework.web: DEBUG
|
||||
org.springframework.context.i18n: DEBUG
|
||||
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping: TRACE # \u6253\u5370\u6240\u6709\u6CE8\u518C\u7684\u63A5\u53E3\u8DEF\u5F84
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE # \u663E\u793ASQL\u53C2\u6570
|
||||
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping: TRACE
|
||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||
org.hibernate.type.descriptor.sql: TRACE
|
||||
com.qqchen.deploy.backend.common.utils.EntityPathResolver: DEBUG
|
||||
jwt:
|
||||
|
||||
@ -7,6 +7,9 @@ response.forbidden=\u7981\u6B62\u8BBF\u95EE
|
||||
response.not.found=\u8D44\u6E90\u672A\u627E\u5230
|
||||
response.conflict=\u8D44\u6E90\u51B2\u7A81
|
||||
|
||||
|
||||
tenant.not.found=\u79DF\u6237\u4E0D\u5B58\u5728
|
||||
|
||||
user.not.found=\u7528\u6237\u4E0D\u5B58\u5728
|
||||
user.username.exists=\u7528\u6237\u540D\u5DF2\u5B58\u5728
|
||||
user.email.exists=\u90AE\u7BB1\u5DF2\u5B58\u5728
|
||||
@ -7,6 +7,9 @@ response.forbidden=Forbidden
|
||||
response.not.found=Resource Not Found
|
||||
response.conflict=Resource Conflict
|
||||
|
||||
|
||||
tenant.not.found=Tenant not found
|
||||
|
||||
user.not.found=User not found
|
||||
user.username.exists=Username already exists
|
||||
user.email.exists=Email already exists
|
||||
@ -7,6 +7,8 @@ response.forbidden=\u7981\u6B62\u8BBF\u95EE
|
||||
response.not.found=\u8D44\u6E90\u672A\u627E\u5230
|
||||
response.conflict=\u8D44\u6E90\u51B2\u7A81
|
||||
|
||||
tenant.not.found=\u79DF\u6237\u4E0D\u5B58\u5728
|
||||
|
||||
user.not.found=\u7528\u6237\u4E0D\u5B58\u5728
|
||||
user.username.exists=\u7528\u6237\u540D\u5DF2\u5B58\u5728
|
||||
user.email.exists=\u90AE\u7BB1\u5DF2\u5B58\u5728
|
||||
Loading…
Reference in New Issue
Block a user