diff --git a/api/src/main/java/com/databasir/api/config/oauth2/DatabasirOauth2LoginFilter.java b/api/src/main/java/com/databasir/api/config/oauth2/DatabasirOauth2LoginFilter.java index 5152fa9..d237631 100644 --- a/api/src/main/java/com/databasir/api/config/oauth2/DatabasirOauth2LoginFilter.java +++ b/api/src/main/java/com/databasir/api/config/oauth2/DatabasirOauth2LoginFilter.java @@ -1,8 +1,9 @@ package com.databasir.api.config.oauth2; import com.databasir.api.config.security.DatabasirUserDetailService; -import com.databasir.core.domain.user.data.UserDetailResponse; import com.databasir.core.domain.app.OpenAuthAppService; +import com.databasir.core.domain.log.service.OperationLogService; +import com.databasir.core.domain.user.data.UserDetailResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; @@ -33,6 +34,9 @@ public class DatabasirOauth2LoginFilter extends AbstractAuthenticationProcessing @Autowired private DatabasirUserDetailService databasirUserDetailService; + @Autowired + private OperationLogService operationLogService; + public DatabasirOauth2LoginFilter(AuthenticationManager authenticationManager, OAuth2AuthenticationSuccessHandler auth2AuthenticationSuccessHandler, AuthenticationFailureHandler authenticationFailureHandler) { @@ -50,6 +54,7 @@ public class DatabasirOauth2LoginFilter extends AbstractAuthenticationProcessing UserDetails details = databasirUserDetailService.loadUserByUsername(userDetailResponse.getUsername()); DatabasirOAuth2Authentication authentication = new DatabasirOAuth2Authentication(details); if (!userDetailResponse.getEnabled()) { + operationLogService.saveLoginFailedLog(userDetailResponse.getUsername(), "用户被禁用"); throw new DisabledException("账号已禁用"); } authentication.setAuthenticated(true); diff --git a/api/src/main/java/com/databasir/api/config/oauth2/OAuth2AuthenticationSuccessHandler.java b/api/src/main/java/com/databasir/api/config/oauth2/OAuth2AuthenticationSuccessHandler.java index 0173db3..f6c359e 100644 --- a/api/src/main/java/com/databasir/api/config/oauth2/OAuth2AuthenticationSuccessHandler.java +++ b/api/src/main/java/com/databasir/api/config/oauth2/OAuth2AuthenticationSuccessHandler.java @@ -2,11 +2,12 @@ package com.databasir.api.config.oauth2; import com.databasir.api.config.security.DatabasirUserDetails; import com.databasir.common.JsonData; -import com.databasir.core.domain.login.data.LoginKeyResponse; +import com.databasir.core.domain.log.service.OperationLogService; import com.databasir.core.domain.login.data.UserLoginResponse; import com.databasir.core.domain.login.service.LoginService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.core.Authentication; @@ -21,22 +22,30 @@ import java.nio.charset.StandardCharsets; @Component @RequiredArgsConstructor +@Slf4j public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final LoginService loginService; private final ObjectMapper objectMapper; + private final OperationLogService operationLogService; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { DatabasirUserDetails details = (DatabasirUserDetails) authentication.getPrincipal(); - LoginKeyResponse loginKey = loginService.generate(details.getUserPojo().getId()); + loginService.generate(details.getUserPojo().getId()); UserLoginResponse data = loginService.getUserLoginData(details.getUserPojo().getId()) - .orElseThrow(() -> new CredentialsExpiredException("请重新登陆")); + .orElseThrow(() -> { + operationLogService.saveLoginLog(details.getUserPojo(), false, null); + return new CredentialsExpiredException("请重新登陆"); + }); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); objectMapper.writeValue(response.getWriter(), JsonData.ok(data)); + operationLogService.saveLoginLog(details.getUserPojo(), true, null); } + } diff --git a/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationFailureHandler.java b/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationFailureHandler.java index e341be1..bf268fb 100644 --- a/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationFailureHandler.java +++ b/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationFailureHandler.java @@ -2,8 +2,10 @@ package com.databasir.api.config.security; import com.databasir.common.JsonData; import com.databasir.core.domain.app.exception.DatabasirAuthenticationException; +import com.databasir.core.domain.log.service.OperationLogService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.BadCredentialsException; @@ -20,37 +22,51 @@ import java.nio.charset.StandardCharsets; @Component @RequiredArgsConstructor +@Slf4j public class DatabasirAuthenticationFailureHandler implements AuthenticationFailureHandler { private final ObjectMapper objectMapper; + private final OperationLogService operationLogService; + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + String username = request.getParameter("username"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); if (exception instanceof BadCredentialsException) { JsonData data = JsonData.error("-1", "用户名或密码错误"); + saveLoginFailedLog(username, data.getErrMessage()); String jsonString = objectMapper.writeValueAsString(data); response.setStatus(HttpStatus.OK.value()); response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8)); } else if (exception instanceof DisabledException) { JsonData data = JsonData.error("-1", "用户已禁用"); + saveLoginFailedLog(username, data.getErrMessage()); String jsonString = objectMapper.writeValueAsString(data); response.setStatus(HttpStatus.OK.value()); response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8)); } else if (exception instanceof DatabasirAuthenticationException) { DatabasirAuthenticationException bizException = (DatabasirAuthenticationException) exception; JsonData data = JsonData.error(bizException.getErrCode(), bizException.getErrMessage()); + saveLoginFailedLog(username, data.getErrMessage()); String jsonString = objectMapper.writeValueAsString(data); response.setStatus(HttpStatus.OK.value()); response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8)); } else { JsonData data = JsonData.error("-1", "未登录或未授权用户"); + saveLoginFailedLog(username, data.getErrMessage()); String jsonString = objectMapper.writeValueAsString(data); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8)); } } + + private void saveLoginFailedLog(String username, String message) { + if (username != null) { + operationLogService.saveLoginFailedLog(username, message); + } + } } diff --git a/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationSuccessHandler.java b/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationSuccessHandler.java index b9c3ec0..0e3f2fd 100644 --- a/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationSuccessHandler.java +++ b/api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationSuccessHandler.java @@ -1,11 +1,12 @@ package com.databasir.api.config.security; import com.databasir.common.JsonData; -import com.databasir.core.domain.login.data.LoginKeyResponse; +import com.databasir.core.domain.log.service.OperationLogService; import com.databasir.core.domain.login.data.UserLoginResponse; import com.databasir.core.domain.login.service.LoginService; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.core.Authentication; @@ -20,12 +21,15 @@ import java.nio.charset.StandardCharsets; @Component @RequiredArgsConstructor +@Slf4j public class DatabasirAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final ObjectMapper objectMapper; private final LoginService loginService; + private final OperationLogService operationLogService; + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, @@ -34,9 +38,13 @@ public class DatabasirAuthenticationSuccessHandler implements AuthenticationSucc response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - LoginKeyResponse loginKey = loginService.generate(user.getUserPojo().getId()); + loginService.generate(user.getUserPojo().getId()); UserLoginResponse data = loginService.getUserLoginData(user.getUserPojo().getId()) - .orElseThrow(() -> new CredentialsExpiredException("请重新登陆")); + .orElseThrow(() -> { + operationLogService.saveLoginLog(user.getUserPojo(), false, null); + return new CredentialsExpiredException("请重新登陆"); + }); + operationLogService.saveLoginLog(user.getUserPojo(), true, null); objectMapper.writeValue(response.getWriter(), JsonData.ok(data)); } } diff --git a/api/src/main/java/com/databasir/api/config/security/DatabasirUserDetailService.java b/api/src/main/java/com/databasir/api/config/security/DatabasirUserDetailService.java index 6873433..aa57448 100644 --- a/api/src/main/java/com/databasir/api/config/security/DatabasirUserDetailService.java +++ b/api/src/main/java/com/databasir/api/config/security/DatabasirUserDetailService.java @@ -1,10 +1,12 @@ package com.databasir.api.config.security; +import com.databasir.core.domain.log.service.OperationLogService; import com.databasir.dao.impl.UserDao; import com.databasir.dao.impl.UserRoleDao; import com.databasir.dao.tables.pojos.UserPojo; import com.databasir.dao.tables.pojos.UserRolePojo; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -15,16 +17,22 @@ import java.util.List; @Service @RequiredArgsConstructor +@Slf4j public class DatabasirUserDetailService implements UserDetailsService { private final UserDao userDao; private final UserRoleDao userRoleDao; + private final OperationLogService operationLogService; + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserPojo user = userDao.selectByEmailOrUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("用户名或密码错误")); + .orElseThrow(() -> { + operationLogService.saveLoginFailedLog(username, "用户名不存在"); + return new UsernameNotFoundException("用户名或密码错误"); + }); List roles = userRoleDao.selectByUserIds(Collections.singletonList(user.getId())); return new DatabasirUserDetails(user, roles); } diff --git a/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java b/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java index cdbe291..f0ac6e6 100644 --- a/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java +++ b/core/src/main/java/com/databasir/core/domain/log/annotation/Operation.java @@ -36,6 +36,7 @@ public @interface Operation { String LOGIN_APP = "login_app"; String SETTING = "setting"; String DATABASE_TYPE = "database_type"; + String LOGIN = "login"; } interface Types { diff --git a/core/src/main/java/com/databasir/core/domain/log/service/OperationLogService.java b/core/src/main/java/com/databasir/core/domain/log/service/OperationLogService.java index 70538f5..27350b2 100644 --- a/core/src/main/java/com/databasir/core/domain/log/service/OperationLogService.java +++ b/core/src/main/java/com/databasir/core/domain/log/service/OperationLogService.java @@ -1,5 +1,7 @@ package com.databasir.core.domain.log.service; +import com.databasir.common.JsonData; +import com.databasir.core.domain.log.annotation.Operation; import com.databasir.core.domain.log.converter.OperationLogPojoConverter; import com.databasir.core.domain.log.converter.OperationLogRequestConverter; import com.databasir.core.domain.log.data.OperationLogPageCondition; @@ -14,17 +16,20 @@ import com.databasir.dao.tables.pojos.OperationLogPojo; import com.databasir.dao.tables.pojos.ProjectPojo; import com.databasir.dao.tables.pojos.UserPojo; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; @Service @RequiredArgsConstructor +@Slf4j public class OperationLogService { private final OperationLogDao operationLogDao; @@ -44,6 +49,52 @@ public class OperationLogService { return operationLogDao.insertAndReturnId(pojo); } + public void saveLoginFailedLog(String username, String msg) { + try { + JsonData result = JsonData.error("-1", Objects.requireNonNullElse(msg, "登录失败")); + OperationLogRequest log = OperationLogRequest.builder() + .isSuccess(false) + .operationCode("login") + .operationModule(Operation.Modules.LOGIN) + .operationName("登录") + .operatorNickname(username) + .operatorUsername(username) + .operatorUserId(-1) + .operationResponse(result) + .build(); + OperationLogPojo pojo = operationLogRequestConverter.toPojo(log); + operationLogDao.insertAndReturnId(pojo); + } catch (Exception e) { + log.error("保存登录日志失败", e); + } + } + + public void saveLoginLog(UserPojo user, Boolean success, String msg) { + try { + JsonData result; + if (success) { + result = JsonData.ok(); + } else { + result = JsonData.error("-1", Objects.requireNonNullElse(msg, "登录失败")); + } + OperationLogRequest log = OperationLogRequest.builder() + .isSuccess(success) + .involvedUserId(user.getId()) + .operationCode("login") + .operationModule(Operation.Modules.LOGIN) + .operationName("登录") + .operatorNickname(user.getNickname()) + .operatorUsername(user.getUsername()) + .operatorUserId(user.getId()) + .operationResponse(result) + .build(); + OperationLogPojo pojo = operationLogRequestConverter.toPojo(log); + operationLogDao.insertAndReturnId(pojo); + } catch (Exception e) { + log.error("保存登录日志失败", e); + } + } + public Page list(Pageable page, OperationLogPageCondition condition) { Page pojoData = operationLogDao.selectByPage(page, condition.toCondition());