140 lines
5.8 KiB
Java
140 lines
5.8 KiB
Java
|
|
package com.ga.api.auth;
|
||
|
|
|
||
|
|
import com.ga.common.auth.JwtTokenProvider;
|
||
|
|
import com.ga.common.exception.BizException;
|
||
|
|
import com.ga.common.exception.ErrorCode;
|
||
|
|
import com.ga.common.system.SystemConfigService;
|
||
|
|
import com.ga.common.system.SystemLogMapper;
|
||
|
|
import com.ga.common.util.SecurityUtil;
|
||
|
|
import com.ga.core.mapper.user.UserMapper;
|
||
|
|
import com.ga.core.vo.user.UserResp;
|
||
|
|
import com.ga.core.vo.user.UserVO;
|
||
|
|
import io.jsonwebtoken.Claims;
|
||
|
|
import lombok.RequiredArgsConstructor;
|
||
|
|
import lombok.extern.slf4j.Slf4j;
|
||
|
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||
|
|
import org.springframework.stereotype.Service;
|
||
|
|
import org.springframework.transaction.annotation.Transactional;
|
||
|
|
|
||
|
|
import java.util.List;
|
||
|
|
|
||
|
|
@Slf4j
|
||
|
|
@Service
|
||
|
|
@RequiredArgsConstructor
|
||
|
|
public class AuthService {
|
||
|
|
|
||
|
|
private final UserMapper userMapper;
|
||
|
|
private final PasswordEncoder passwordEncoder;
|
||
|
|
private final JwtTokenProvider tokenProvider;
|
||
|
|
private final SystemConfigService configService;
|
||
|
|
private final SystemLogMapper systemLogMapper;
|
||
|
|
|
||
|
|
@Transactional
|
||
|
|
public LoginResp login(LoginReq req, String ipAddress, String userAgent) {
|
||
|
|
UserVO user = userMapper.selectByLoginId(req.getLoginId());
|
||
|
|
int failLimit = configService.getInt("LOGIN_FAIL_LIMIT", 5);
|
||
|
|
|
||
|
|
if (user == null) {
|
||
|
|
saveLoginLog(null, req.getLoginId(), "FAIL", "USER_NOT_FOUND", ipAddress, userAgent);
|
||
|
|
throw new BizException(ErrorCode.LOGIN_FAIL);
|
||
|
|
}
|
||
|
|
if ("LOCKED".equals(user.getStatus())) {
|
||
|
|
saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "ACCOUNT_LOCKED", ipAddress, userAgent);
|
||
|
|
throw new BizException(ErrorCode.ACCOUNT_LOCKED);
|
||
|
|
}
|
||
|
|
if ("RETIRED".equals(user.getStatus())) {
|
||
|
|
throw new BizException(ErrorCode.ACCOUNT_RETIRED);
|
||
|
|
}
|
||
|
|
if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
|
||
|
|
userMapper.incrementFailCount(user.getUserId());
|
||
|
|
int newFail = (user.getFailCount() == null ? 0 : user.getFailCount()) + 1;
|
||
|
|
if (newFail >= failLimit) {
|
||
|
|
userMapper.updateStatus(user.getUserId(), "LOCKED");
|
||
|
|
}
|
||
|
|
saveLoginLog(user.getUserId(), req.getLoginId(), "FAIL", "BAD_PASSWORD", ipAddress, userAgent);
|
||
|
|
throw new BizException(ErrorCode.LOGIN_FAIL);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 로그인 성공
|
||
|
|
userMapper.updateLastLogin(user.getUserId());
|
||
|
|
List<String> roles = userMapper.selectRoleCodes(user.getUserId());
|
||
|
|
String access = tokenProvider.createAccessToken(user.getUserId(), user.getLoginId(), roles);
|
||
|
|
String refresh = tokenProvider.createRefreshToken(user.getUserId(), user.getLoginId());
|
||
|
|
saveLoginLog(user.getUserId(), req.getLoginId(), "SUCCESS", null, ipAddress, userAgent);
|
||
|
|
|
||
|
|
return LoginResp.builder()
|
||
|
|
.accessToken(access)
|
||
|
|
.refreshToken(refresh)
|
||
|
|
.accessTokenExpiresIn(tokenProvider.getAccessExpire())
|
||
|
|
.userId(user.getUserId())
|
||
|
|
.loginId(user.getLoginId())
|
||
|
|
.userName(user.getUserName())
|
||
|
|
.roles(roles)
|
||
|
|
.build();
|
||
|
|
}
|
||
|
|
|
||
|
|
public LoginResp refresh(String refreshToken) {
|
||
|
|
Claims claims = tokenProvider.parse(refreshToken);
|
||
|
|
if (!"REFRESH".equals(claims.get("type"))) {
|
||
|
|
throw new BizException(ErrorCode.TOKEN_INVALID);
|
||
|
|
}
|
||
|
|
Long userId = claims.get("uid", Long.class);
|
||
|
|
UserVO user = userMapper.selectById(userId);
|
||
|
|
if (user == null || !"ACTIVE".equals(user.getStatus())) {
|
||
|
|
throw new BizException(ErrorCode.UNAUTHORIZED);
|
||
|
|
}
|
||
|
|
List<String> roles = userMapper.selectRoleCodes(userId);
|
||
|
|
String newAccess = tokenProvider.createAccessToken(userId, user.getLoginId(), roles);
|
||
|
|
return LoginResp.builder()
|
||
|
|
.accessToken(newAccess)
|
||
|
|
.accessTokenExpiresIn(tokenProvider.getAccessExpire())
|
||
|
|
.userId(userId)
|
||
|
|
.loginId(user.getLoginId())
|
||
|
|
.userName(user.getUserName())
|
||
|
|
.roles(roles)
|
||
|
|
.build();
|
||
|
|
}
|
||
|
|
|
||
|
|
public UserResp me() {
|
||
|
|
Long userId = SecurityUtil.getCurrentUserId();
|
||
|
|
if (userId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
|
||
|
|
UserResp resp = userMapper.selectDetailById(userId);
|
||
|
|
if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
|
||
|
|
resp.setRoleCodes(userMapper.selectRoleCodes(userId));
|
||
|
|
return resp;
|
||
|
|
}
|
||
|
|
|
||
|
|
@Transactional
|
||
|
|
public void changePassword(String oldPassword, String newPassword) {
|
||
|
|
Long userId = SecurityUtil.getCurrentUserId();
|
||
|
|
if (userId == null) throw new BizException(ErrorCode.UNAUTHORIZED);
|
||
|
|
UserVO user = userMapper.selectById(userId);
|
||
|
|
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
|
||
|
|
throw new BizException(ErrorCode.LOGIN_FAIL, "기존 비밀번호가 일치하지 않습니다");
|
||
|
|
}
|
||
|
|
validatePasswordPolicy(newPassword);
|
||
|
|
userMapper.updatePassword(userId, passwordEncoder.encode(newPassword));
|
||
|
|
}
|
||
|
|
|
||
|
|
public void logout() {
|
||
|
|
Long userId = SecurityUtil.getCurrentUserId();
|
||
|
|
saveLoginLog(userId, SecurityUtil.getCurrentLoginId(), "LOGOUT", null, null, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void saveLoginLog(Long userId, String loginId, String type, String reason, String ip, String ua) {
|
||
|
|
try {
|
||
|
|
systemLogMapper.insertLoginLog(userId, loginId, type, reason, ip, ua);
|
||
|
|
} catch (Exception e) {
|
||
|
|
log.warn("Login log save failed: {}", e.getMessage());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void validatePasswordPolicy(String password) {
|
||
|
|
int min = configService.getInt("PASSWORD_MIN_LEN", 8);
|
||
|
|
if (password == null || password.length() < min) {
|
||
|
|
throw new BizException(ErrorCode.PASSWORD_POLICY,
|
||
|
|
"비밀번호는 " + min + "자 이상이어야 합니다");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|