[Auth] Changed JWT into Cookie
This commit is contained in:
parent
78d836dfa3
commit
68233402fc
|
|
@ -0,0 +1,109 @@
|
|||
package com.goi.erp.auth;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class AuthCookieService {
|
||||
|
||||
public static final String AUTH_COOKIE_NAME = "AUTH_TOKEN";
|
||||
public static final String REFRESH_COOKIE_NAME = "REFRESH_TOKEN";
|
||||
|
||||
@Value("${auth.cookie.secure:true}")
|
||||
private boolean secure;
|
||||
|
||||
@Value("${auth.cookie.same-site:Lax}")
|
||||
private String sameSite;
|
||||
|
||||
@Value("${auth.cookie.path:/}")
|
||||
private String path;
|
||||
|
||||
// ===================== Access Token =====================
|
||||
|
||||
public void addAuthCookie(HttpServletResponse response, String jwt) {
|
||||
addCookie(response, AUTH_COOKIE_NAME, jwt, -1);
|
||||
}
|
||||
|
||||
public void clearAuthCookie(HttpServletResponse response) {
|
||||
addCookie(response, AUTH_COOKIE_NAME, null, 0);
|
||||
}
|
||||
|
||||
public Optional<String> extractJwt(HttpServletRequest request) {
|
||||
return extractCookie(request, AUTH_COOKIE_NAME);
|
||||
}
|
||||
|
||||
// ===================== Refresh Token =====================
|
||||
|
||||
public void addRefreshCookie(HttpServletResponse response, String refreshToken) {
|
||||
addCookie(response, REFRESH_COOKIE_NAME, refreshToken, -1);
|
||||
}
|
||||
|
||||
public void clearRefreshCookie(HttpServletResponse response) {
|
||||
addCookie(response, REFRESH_COOKIE_NAME, null, 0);
|
||||
}
|
||||
|
||||
public Optional<String> extractRefreshToken(HttpServletRequest request) {
|
||||
return extractCookie(request, REFRESH_COOKIE_NAME);
|
||||
}
|
||||
|
||||
// ===================== 내부 공통 =====================
|
||||
|
||||
private void addCookie(
|
||||
HttpServletResponse response,
|
||||
String name,
|
||||
String value,
|
||||
int maxAge
|
||||
) {
|
||||
Cookie cookie = new Cookie(name, value);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setSecure(secure);
|
||||
cookie.setPath(path);
|
||||
cookie.setMaxAge(maxAge);
|
||||
|
||||
response.addCookie(cookie);
|
||||
addSameSiteAttribute(response, cookie);
|
||||
}
|
||||
|
||||
private Optional<String> extractCookie(HttpServletRequest request, String name) {
|
||||
if (request.getCookies() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Arrays.stream(request.getCookies())
|
||||
.filter(c -> name.equals(c.getName()))
|
||||
.map(Cookie::getValue)
|
||||
.filter(v -> v != null && !v.isBlank())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* SameSite 수동 세팅 (Servlet Cookie API 한계)
|
||||
*/
|
||||
private void addSameSiteAttribute(HttpServletResponse response, Cookie cookie) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(cookie.getName()).append("=");
|
||||
sb.append(cookie.getValue() == null ? "" : cookie.getValue());
|
||||
sb.append("; Path=").append(cookie.getPath());
|
||||
sb.append("; HttpOnly");
|
||||
|
||||
if (cookie.getSecure()) {
|
||||
sb.append("; Secure");
|
||||
}
|
||||
|
||||
if (sameSite != null && !sameSite.isBlank()) {
|
||||
sb.append("; SameSite=").append(sameSite);
|
||||
}
|
||||
|
||||
if (cookie.getMaxAge() == 0) {
|
||||
sb.append("; Max-Age=0");
|
||||
}
|
||||
|
||||
response.addHeader("Set-Cookie", sb.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
@ -16,27 +13,37 @@ import java.io.IOException;
|
|||
@RequiredArgsConstructor
|
||||
public class AuthenticationController {
|
||||
|
||||
private final AuthenticationService service;
|
||||
private final AuthenticationService authenticationService;
|
||||
|
||||
// @PostMapping("/register")
|
||||
// public ResponseEntity<AuthenticationResponse> register(
|
||||
// @RequestBody RegisterRequest request
|
||||
// ) {
|
||||
// return ResponseEntity.ok(service.register(request));
|
||||
// }
|
||||
@PostMapping("/authenticate")
|
||||
public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticationRequest request) {
|
||||
return ResponseEntity.ok(service.authenticate(request));
|
||||
}
|
||||
// ===================== 로그인 =====================
|
||||
|
||||
@PostMapping("/refresh-token")
|
||||
public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
service.refreshToken(request, response);
|
||||
}
|
||||
|
||||
@PostMapping("/authenticate/system")
|
||||
public AuthenticationResponse authenticateSystem(@RequestBody SystemAuthenticationRequestDto request) {
|
||||
return service.authenticateSystem(request);
|
||||
}
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthenticationResponse> authenticate(
|
||||
@RequestBody AuthenticationRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
AuthenticationResponse authResponse = authenticationService.authenticate(request, response);
|
||||
return ResponseEntity.ok(authResponse);
|
||||
}
|
||||
|
||||
// ===================== Refresh Token =====================
|
||||
|
||||
@PostMapping("/token/refresh")
|
||||
public void refreshToken(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) throws IOException {
|
||||
authenticationService.refreshToken(request, response);
|
||||
}
|
||||
|
||||
// ===================== System Token =====================
|
||||
|
||||
@PostMapping("/login/system")
|
||||
public ResponseEntity<SystemAuthenticationResponseDto> authenticateSystem(
|
||||
@RequestBody SystemAuthenticationRequestDto request
|
||||
) {
|
||||
return ResponseEntity.ok(
|
||||
authenticationService.authenticateSystem(request)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
package com.goi.erp.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AuthenticationResponse {
|
||||
|
||||
@JsonProperty("access_token")
|
||||
private String accessToken;
|
||||
@JsonProperty("refresh_token")
|
||||
private String refreshToken;
|
||||
private boolean authenticated;
|
||||
|
||||
private String loginId;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
||||
private List<String> roles;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,190 +1,204 @@
|
|||
package com.goi.erp.auth;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.goi.erp.common.exception.InvalidPasswordException;
|
||||
import com.goi.erp.common.exception.UserNotFoundException;
|
||||
import com.goi.erp.config.JwtService;
|
||||
import com.goi.erp.config.SecuritySystemClientsProperties;
|
||||
import com.goi.erp.token.Token;
|
||||
import com.goi.erp.token.TokenRepository;
|
||||
import com.goi.erp.token.TokenType;
|
||||
import com.goi.erp.employee.Employee;
|
||||
import com.goi.erp.employee.EmployeeRepository;
|
||||
import com.goi.erp.employee.EmployeeRole;
|
||||
import com.goi.erp.employee.EmployeeRoleRepository;
|
||||
import com.goi.erp.role.RolePermissionRepository;
|
||||
import com.goi.erp.token.Token;
|
||||
import com.goi.erp.token.TokenRepository;
|
||||
import com.goi.erp.token.TokenType;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class AuthenticationService {
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final EmployeeRoleRepository employeeRoleRepository;
|
||||
private final RolePermissionRepository rolePermissionRepository;
|
||||
private final TokenRepository tokenRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final RolePermissionRepository rolePermissionRepository;
|
||||
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final SecuritySystemClientsProperties systemClientsProperties;
|
||||
// private final AuthenticationManager authenticationManager;
|
||||
private final AuthCookieService authCookieService;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
// public AuthenticationResponse register(RegisterRequest request) {
|
||||
// var user = User.builder().firstname(request.getFirstname()).lastname(request.getLastname())
|
||||
// .email(request.getEmail()).password(passwordEncoder.encode(request.getPassword()))
|
||||
// .role(request.getRole()).build();
|
||||
// var savedUser = repository.save(user);
|
||||
// var jwtToken = jwtService.generateToken(user);
|
||||
// var refreshToken = jwtService.generateRefreshToken(user);
|
||||
// saveUserToken(savedUser, jwtToken);
|
||||
// return AuthenticationResponse.builder().accessToken(jwtToken).refreshToken(refreshToken).build();
|
||||
// }
|
||||
// ===================== 로그인 =====================
|
||||
|
||||
// 로그인 처리
|
||||
public AuthenticationResponse authenticate(AuthenticationRequest request) {
|
||||
// 1. Employee 조회
|
||||
public AuthenticationResponse authenticate(
|
||||
AuthenticationRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
Employee employee = employeeRepository.findByEmpLoginId(request.getEmpLoginId())
|
||||
.orElseThrow(() -> new UserNotFoundException("Employee not found"));
|
||||
|
||||
// 2. 비밀번호 검증
|
||||
if (!passwordEncoder.matches(request.getEmpLoginPassword(), employee.getEmpLoginPassword())) {
|
||||
throw new InvalidPasswordException("Invalid password");
|
||||
}
|
||||
|
||||
// 3. EmployeeRole 조회 → Role 이름 리스트 생성
|
||||
List<EmployeeRole> activeRoles = employeeRoleRepository.findActiveRolesByEmployeeId(employee.getEmpId());
|
||||
List<EmployeeRole> activeRoles =
|
||||
employeeRoleRepository.findActiveRolesByEmployeeId(employee.getEmpId());
|
||||
|
||||
List<String> roles = activeRoles.stream()
|
||||
.map(er -> er.getRoleInfo().getRoleName())
|
||||
.collect(Collectors.toList());
|
||||
List<String> roles = extractRoles(activeRoles);
|
||||
List<String> permissions = extractPermissions(activeRoles);
|
||||
|
||||
|
||||
// 4. Role → Permission 조회
|
||||
List<String> permissions = activeRoles.stream()
|
||||
.flatMap(er -> rolePermissionRepository.findByRoleId(er.getRoleInfo().getRoleId()).stream())
|
||||
.map(rp -> rp.getPermissionInfo().getPermModule() + ":" + rp.getPermissionInfo().getPermAction() + ":" + rp.getPermissionInfo().getPermScope())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 5. generate token
|
||||
String jwtToken = jwtService.generateToken(employee, roles, permissions);
|
||||
String accessToken = jwtService.generateToken(employee, roles, permissions);
|
||||
String refreshToken = jwtService.generateRefreshToken(employee, roles, permissions);
|
||||
|
||||
// 기존 토큰 회수 및 새 토큰 저장
|
||||
revokeAllEmployeeTokens(employee);
|
||||
saveEmployeeToken(employee, jwtToken);
|
||||
// refresh 도 저장
|
||||
saveEmployeeToken(employee, accessToken, TokenType.ACCESS);
|
||||
saveEmployeeToken(employee, refreshToken, TokenType.REFRESH);
|
||||
|
||||
|
||||
// Cookie로 내려줌
|
||||
authCookieService.addAuthCookie(response, accessToken);
|
||||
authCookieService.addRefreshCookie(response, refreshToken);
|
||||
|
||||
return AuthenticationResponse.builder()
|
||||
.accessToken(jwtToken)
|
||||
.refreshToken(refreshToken)
|
||||
.authenticated(true)
|
||||
.loginId(employee.getEmpLoginId())
|
||||
.firstName(employee.getEmpFirstName())
|
||||
.lastName(employee.getEmpLastName())
|
||||
.roles(roles)
|
||||
.build();
|
||||
}
|
||||
|
||||
// JWT 토큰 저장
|
||||
private void saveEmployeeToken(Employee employee, String jwtToken) {
|
||||
// ===================== Refresh Token =====================
|
||||
|
||||
public void refreshToken(HttpServletRequest request, HttpServletResponse response) {
|
||||
|
||||
String refreshToken = authCookieService
|
||||
.extractRefreshToken(request)
|
||||
.orElse(null);
|
||||
|
||||
if (refreshToken == null) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
String loginId = jwtService.extractUsername(refreshToken);
|
||||
if (loginId == null) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(loginId);
|
||||
|
||||
if (!jwtService.isTokenValid(refreshToken, userDetails)) {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
Employee employee = employeeRepository.findByEmpLoginId(loginId)
|
||||
.orElseThrow(() -> new UserNotFoundException("Employee not found"));
|
||||
|
||||
List<EmployeeRole> activeRoles =
|
||||
employeeRoleRepository.findActiveRolesByEmployeeId(employee.getEmpId());
|
||||
|
||||
List<String> roles = extractRoles(activeRoles);
|
||||
List<String> permissions = extractPermissions(activeRoles);
|
||||
|
||||
|
||||
String newAccessToken = jwtService.generateToken(employee, roles, permissions);
|
||||
String newRefreshToken = jwtService.generateRefreshToken(employee, roles, permissions);
|
||||
|
||||
// 기존 토큰 전부 revoke
|
||||
revokeAllEmployeeTokens(employee);
|
||||
|
||||
// 새 토큰 저장
|
||||
saveEmployeeToken(employee, newAccessToken, TokenType.ACCESS);
|
||||
saveEmployeeToken(employee, newRefreshToken, TokenType.REFRESH);
|
||||
|
||||
// 쿠키 갱신
|
||||
authCookieService.addAuthCookie(response, newAccessToken);
|
||||
authCookieService.addRefreshCookie(response, newRefreshToken);
|
||||
|
||||
// body 없음
|
||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
}
|
||||
|
||||
|
||||
// ===================== System Token =====================
|
||||
// OPR / CRM 등 서버간 호출용 (Header 기반)
|
||||
|
||||
public SystemAuthenticationResponseDto authenticateSystem(SystemAuthenticationRequestDto request) {
|
||||
|
||||
if (request.getClientId() == null || request.getClientSecret() == null) {
|
||||
throw new InvalidPasswordException("Missing client credentials");
|
||||
}
|
||||
|
||||
var clientConfig = systemClientsProperties.getClients().get(request.getClientId());
|
||||
if (clientConfig == null) {
|
||||
throw new InvalidPasswordException("Invalid system client");
|
||||
}
|
||||
|
||||
if (!clientConfig.getSecret().equals(request.getClientSecret())) {
|
||||
throw new InvalidPasswordException("Invalid system secret");
|
||||
}
|
||||
|
||||
String jwtToken = jwtService.generateSystemToken(
|
||||
request.getClientId(),
|
||||
clientConfig.getPermissions()
|
||||
);
|
||||
|
||||
return SystemAuthenticationResponseDto.builder()
|
||||
.accessToken(jwtToken)
|
||||
.refreshToken(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
// ===================== 내부 헬퍼 =====================
|
||||
|
||||
private List<String> extractRoles(List<EmployeeRole> roles) {
|
||||
return roles.stream()
|
||||
.map(er -> er.getRoleInfo().getRoleName())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> extractPermissions(List<EmployeeRole> roles) {
|
||||
return roles.stream()
|
||||
.flatMap(er -> rolePermissionRepository
|
||||
.findByRoleId(er.getRoleInfo().getRoleId()).stream())
|
||||
.map(rp -> rp.getPermissionInfo().getPermModule()
|
||||
+ ":" + rp.getPermissionInfo().getPermAction()
|
||||
+ ":" + rp.getPermissionInfo().getPermScope())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void saveEmployeeToken(Employee employee, String jwtToken, TokenType tokenType) {
|
||||
Token token = Token.builder()
|
||||
.employee(employee)
|
||||
.token(jwtToken)
|
||||
.tokenType(TokenType.BEARER)
|
||||
.tokenType(tokenType)
|
||||
.expired(false)
|
||||
.revoked(false)
|
||||
.build();
|
||||
tokenRepository.save(token);
|
||||
}
|
||||
|
||||
// 기존 토큰 회수
|
||||
private void revokeAllEmployeeTokens(Employee employee) {
|
||||
List<Token> validTokens = tokenRepository.findAllValidTokenByEmployee(employee.getEmpId());
|
||||
if (validTokens.isEmpty()) return;
|
||||
|
||||
validTokens.forEach(token -> {
|
||||
token.setExpired(true);
|
||||
token.setRevoked(true);
|
||||
});
|
||||
|
||||
tokenRepository.saveAll(validTokens);
|
||||
tokenRepository.revokeAllValidTokensByEmployee(employee.getEmpId());
|
||||
}
|
||||
|
||||
// 리프레시 토큰 처리
|
||||
public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String refreshToken = authHeader.substring(7);
|
||||
String empLoginId = jwtService.extractUsername(refreshToken);
|
||||
|
||||
if (empLoginId != null) {
|
||||
Employee employee = employeeRepository.findByEmpLoginId(empLoginId)
|
||||
.orElseThrow(() -> new RuntimeException("Employee not found"));
|
||||
|
||||
if (jwtService.isTokenValid(refreshToken, employee)) {
|
||||
// 3. EmployeeRole 조회 → Role 이름 리스트 생성
|
||||
List<EmployeeRole> activeRoles = employeeRoleRepository.findActiveRolesByEmployeeId(employee.getEmpId());
|
||||
|
||||
List<String> roles = activeRoles.stream()
|
||||
.map(er -> er.getRoleInfo().getRoleName())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
// 4. Role → Permission 조회
|
||||
List<String> permissions = activeRoles.stream()
|
||||
.flatMap(er -> rolePermissionRepository.findByRoleId(er.getRoleInfo().getRoleId()).stream())
|
||||
.map(rp -> rp.getPermissionInfo().getPermModule() + ":" + rp.getPermissionInfo().getPermAction() + ":" + rp.getPermissionInfo().getPermScope())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 5. generate token
|
||||
String accessToken = jwtService.generateToken(employee, roles, permissions);
|
||||
|
||||
revokeAllEmployeeTokens(employee);
|
||||
saveEmployeeToken(employee, accessToken);
|
||||
|
||||
AuthenticationResponse authResponse = AuthenticationResponse.builder()
|
||||
.accessToken(accessToken)
|
||||
.refreshToken(refreshToken)
|
||||
.build();
|
||||
|
||||
new ObjectMapper().writeValue(response.getOutputStream(), authResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 시스템 토큰 발급 (OPR/CRM 등 서버간 호출용)
|
||||
public AuthenticationResponse authenticateSystem(SystemAuthenticationRequestDto request) {
|
||||
|
||||
if (request.getClientId() == null || request.getClientSecret() == null) {
|
||||
throw new InvalidPasswordException("Missing client credentials");
|
||||
}
|
||||
|
||||
var clientConfig = systemClientsProperties.getClients().get(request.getClientId());
|
||||
if (clientConfig == null) {
|
||||
throw new InvalidPasswordException("Invalid system client");
|
||||
}
|
||||
|
||||
if (!clientConfig.getSecret().equals(request.getClientSecret())) {
|
||||
throw new InvalidPasswordException("Invalid system secret");
|
||||
}
|
||||
|
||||
List<String> permissions = clientConfig.getPermissions(); // 예: ["H:R:A"]
|
||||
|
||||
String jwtToken = jwtService.generateSystemToken(request.getClientId(), permissions);
|
||||
|
||||
// system token은 보통 DB(TokenRepository)에 저장/폐기(revoke) 안 함 (짧게 발급 + 캐싱)
|
||||
return AuthenticationResponse.builder()
|
||||
.accessToken(jwtToken)
|
||||
.refreshToken(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.goi.erp.auth;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SystemAuthenticationResponseDto {
|
||||
|
||||
@JsonProperty("access_token")
|
||||
private String accessToken;
|
||||
@JsonProperty("refresh_token")
|
||||
private String refreshToken;
|
||||
}
|
||||
|
|
@ -1,91 +1,93 @@
|
|||
package com.goi.erp.config;
|
||||
|
||||
import com.goi.erp.employee.Employee;
|
||||
import com.goi.erp.employee.EmployeeDetails;
|
||||
import com.goi.erp.employee.EmployeeRepository;
|
||||
import com.goi.erp.employee.EmployeeRole;
|
||||
import com.goi.erp.employee.EmployeeRoleRepository;
|
||||
import com.goi.erp.token.TokenRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import com.goi.erp.auth.AuthCookieService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
private final EmployeeRoleRepository employeeRoleRepository;
|
||||
private final TokenRepository tokenRepository;
|
||||
private final UserDetailsService userDetailsService;
|
||||
private final AuthCookieService authCookieService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
|
||||
// 인증 API는 필터링 제외
|
||||
if (request.getServletPath().contains("/api/v1/auth")) {
|
||||
if (SecurityContextHolder.getContext().getAuthentication() != null) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
final String jwt;
|
||||
String jwt = resolveToken(request);
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
if (jwt == null) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
jwt = authHeader.substring(7);
|
||||
final String empUuidStr = jwtService.extractUsername(jwt);
|
||||
String username;
|
||||
try {
|
||||
username = jwtService.extractUsername(jwt);
|
||||
} catch (Exception e) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (empUuidStr != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UUID empUuid = UUID.fromString(empUuidStr);
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
|
||||
// Employee 조회
|
||||
Employee employee = employeeRepository.findByEmpUuid(empUuid)
|
||||
.orElseThrow(() -> new RuntimeException("Employee not found"));
|
||||
// Role 조회
|
||||
List<EmployeeRole> roles = employeeRoleRepository.findActiveRolesByEmployeeId(employee.getEmpId());
|
||||
EmployeeDetails employeeDetails = new EmployeeDetails(employee, roles);
|
||||
if (!jwtService.isTokenValid(jwt, userDetails)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// DB 토큰 검증
|
||||
boolean isTokenValid = tokenRepository.findByToken(jwt)
|
||||
.map(t -> !t.isExpired() && !t.isRevoked())
|
||||
.orElse(false);
|
||||
|
||||
// JWT 유효성 검증 + DB 토큰 검증
|
||||
if (jwtService.isTokenValid(jwt, employee) && isTokenValid) {
|
||||
UsernamePasswordAuthenticationToken authToken =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
employee.getEmpLoginId(), // principal → loginId
|
||||
null,
|
||||
employeeDetails.getAuthorities()
|
||||
);
|
||||
authToken.setDetails(
|
||||
new WebAuthenticationDetailsSource().buildDetails(request)
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
userDetails,
|
||||
null,
|
||||
userDetails.getAuthorities()
|
||||
);
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
}
|
||||
|
||||
authentication.setDetails(
|
||||
new WebAuthenticationDetailsSource().buildDetails(request)
|
||||
);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 추출 우선순위:
|
||||
* 1. Authorization Header
|
||||
* 2. HttpOnly Cookie
|
||||
*/
|
||||
private String resolveToken(HttpServletRequest request) {
|
||||
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
return authHeader.substring(7);
|
||||
}
|
||||
|
||||
Optional<String> cookieToken = authCookieService.extractJwt(request);
|
||||
return cookieToken.orElse(null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import io.jsonwebtoken.Jwts;
|
|||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -13,6 +14,7 @@ import java.util.Map;
|
|||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.goi.erp.employee.Employee;
|
||||
|
|
@ -20,7 +22,7 @@ import com.goi.erp.employee.Employee;
|
|||
@Service
|
||||
public class JwtService {
|
||||
|
||||
@Value("${application.security.jwt.secret-key}")
|
||||
@Value("${application.security.jwt.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${application.security.jwt.expiration}")
|
||||
|
|
@ -28,108 +30,85 @@ public class JwtService {
|
|||
|
||||
@Value("${application.security.jwt.refresh-token.expiration}")
|
||||
private long refreshExpiration;
|
||||
|
||||
// =================== 기존 UserDetails용 ===================
|
||||
public String extractUsername(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = extractAllClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
// =================== 공통 ===================
|
||||
|
||||
public String generateToken(Employee employee, List<String> roles, List<String> permissions) {
|
||||
Map<String, Object> extraClaims = new HashMap<>();
|
||||
extraClaims.put("roles", roles);
|
||||
|
||||
// Admin 계정 여부 확인
|
||||
boolean isAdmin = roles.stream().anyMatch(r -> r.equalsIgnoreCase("Admin"));
|
||||
public String extractUsername(String token) {
|
||||
return extractClaim(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
// Admin이면 permissions를 ALL로 단순화
|
||||
extraClaims.put("permissions", List.of("ALL"));
|
||||
} else {
|
||||
// 일반 계정이면 상세 권한 넣기
|
||||
extraClaims.put("permissions", permissions);
|
||||
}
|
||||
|
||||
// 직원 이름 추가
|
||||
extraClaims.put("firstName", employee.getEmpFirstName());
|
||||
extraClaims.put("lastName", employee.getEmpLastName());
|
||||
extraClaims.put("loginId", employee.getEmpLoginId());
|
||||
|
||||
return buildToken(extraClaims, employee.getEmpUuid().toString(), jwtExpiration);
|
||||
}
|
||||
|
||||
public String generateSystemToken(String clientId, java.util.List<String> permissions) {
|
||||
Map<String, Object> extraClaims = new HashMap<>();
|
||||
extraClaims.put("permissions", permissions);
|
||||
extraClaims.put("loginId", clientId);
|
||||
return buildToken(extraClaims, clientId, jwtExpiration);
|
||||
}
|
||||
public <T> T extractClaim(String token, Function<Claims, T> resolver) {
|
||||
return resolver.apply(extractAllClaims(token));
|
||||
}
|
||||
|
||||
// =================== Token 생성 ===================
|
||||
|
||||
public String generateRefreshToken(Employee employee, List<String> roles, List<String> permissions) {
|
||||
Map<String, Object> extraClaims = new HashMap<>();
|
||||
extraClaims.put("roles", roles);
|
||||
// Admin 계정 여부 확인
|
||||
boolean isAdmin = roles.stream().anyMatch(r -> r.equalsIgnoreCase("Admin"));
|
||||
public String generateToken(Employee employee, List<String> roles, List<String> permissions) {
|
||||
return buildEmployeeToken(employee, roles, permissions, jwtExpiration);
|
||||
}
|
||||
|
||||
if (isAdmin) {
|
||||
// Admin이면 permissions를 ALL로 단순화
|
||||
extraClaims.put("permissions", List.of("ALL"));
|
||||
} else {
|
||||
// 일반 계정이면 상세 권한 넣기
|
||||
extraClaims.put("permissions", permissions);
|
||||
}
|
||||
|
||||
// 직원 이름 추가
|
||||
extraClaims.put("firstName", employee.getEmpFirstName());
|
||||
extraClaims.put("lastName", employee.getEmpLastName());
|
||||
extraClaims.put("loginId", employee.getEmpLoginId());
|
||||
|
||||
return buildToken(extraClaims, employee.getEmpUuid().toString(), refreshExpiration);
|
||||
}
|
||||
public String generateRefreshToken(Employee employee, List<String> roles, List<String> permissions) {
|
||||
return buildEmployeeToken(employee, roles, permissions, refreshExpiration);
|
||||
}
|
||||
|
||||
private String buildToken(Map<String, Object> extraClaims, String subject, long expiration) {
|
||||
return Jwts.builder().setClaims(extraClaims).setSubject(subject)
|
||||
.setIssuedAt(new Date(System.currentTimeMillis()))
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSignInKey(), SignatureAlgorithm.HS256).compact();
|
||||
}
|
||||
private String buildEmployeeToken(
|
||||
Employee employee,
|
||||
List<String> roles,
|
||||
List<String> permissions,
|
||||
long expiration
|
||||
) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("roles", roles);
|
||||
|
||||
public boolean isTokenValid(String token, Employee employee) {
|
||||
final String username = extractUsername(token);
|
||||
return (username.equals(employee.getEmpUuid().toString())) && !isTokenExpired(token);
|
||||
}
|
||||
boolean isAdmin = roles.stream().anyMatch(r -> r.equalsIgnoreCase("Admin"));
|
||||
claims.put("permissions", isAdmin ? List.of("ALL") : permissions);
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
claims.put("empUuid", employee.getEmpUuid().toString());
|
||||
claims.put("firstName", employee.getEmpFirstName());
|
||||
claims.put("lastName", employee.getEmpLastName());
|
||||
|
||||
private Date extractExpiration(String token) {
|
||||
return extractClaim(token, Claims::getExpiration);
|
||||
}
|
||||
// 🔥 subject는 loginId
|
||||
return buildToken(claims, employee.getEmpLoginId(), expiration);
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts.parserBuilder().setSigningKey(getSignInKey()).build().parseClaimsJws(token).getBody();
|
||||
}
|
||||
public String generateSystemToken(String clientId, List<String> permissions) {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("permissions", permissions);
|
||||
return buildToken(claims, clientId, jwtExpiration);
|
||||
}
|
||||
|
||||
private Key getSignInKey() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
JwtService jwtService = new JwtService();
|
||||
jwtService.secretKey = "";
|
||||
|
||||
String token = "";
|
||||
// =================== Validation ===================
|
||||
|
||||
// user 정보
|
||||
Claims claims = jwtService.extractAllClaims(token);
|
||||
public boolean isTokenValid(String token, UserDetails userDetails) {
|
||||
final String username = extractUsername(token);
|
||||
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
}
|
||||
|
||||
System.out.println("Claims: " + claims);
|
||||
}
|
||||
// =================== 내부 ===================
|
||||
|
||||
private String buildToken(Map<String, Object> claims, String subject, long expiration) {
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(getSignInKey(), SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
return extractClaim(token, Claims::getExpiration).before(new Date());
|
||||
}
|
||||
|
||||
private Claims extractAllClaims(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(getSignInKey())
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
private Key getSignInKey() {
|
||||
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,34 +7,42 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.goi.erp.token.TokenRepository;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class LogoutService implements LogoutHandler {
|
||||
|
||||
private final TokenRepository tokenRepository;
|
||||
private final TokenRepository tokenRepository;
|
||||
|
||||
@Override
|
||||
public void logout(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication
|
||||
) {
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
final String jwt;
|
||||
if (authHeader == null ||!authHeader.startsWith("Bearer ")) {
|
||||
return;
|
||||
@Override
|
||||
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
|
||||
|
||||
// 로그아웃 한 번에 ACCESS + REFRESH 전부 revoke
|
||||
String tokenValue = null;
|
||||
|
||||
if (request.getCookies() != null) {
|
||||
for (var cookie : request.getCookies()) {
|
||||
if ("AUTH_TOKEN".equals(cookie.getName())
|
||||
|| "REFRESH_TOKEN".equals(cookie.getName())) {
|
||||
tokenValue = cookie.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenValue == null) return;
|
||||
|
||||
var tokenEntity = tokenRepository.findByToken(tokenValue).orElse(null);
|
||||
if (tokenEntity == null) return;
|
||||
|
||||
tokenRepository.revokeAllValidTokensByEmployee(
|
||||
tokenEntity.getEmployee().getEmpId()
|
||||
);
|
||||
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
jwt = authHeader.substring(7);
|
||||
var storedToken = tokenRepository.findByToken(jwt)
|
||||
.orElse(null);
|
||||
if (storedToken != null) {
|
||||
storedToken.setExpired(true);
|
||||
storedToken.setRevoked(true);
|
||||
tokenRepository.save(storedToken);
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.goi.erp.config;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
|
|
@ -12,23 +14,28 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import static com.goi.erp.user.Permission.ADMIN_CREATE;
|
||||
import static com.goi.erp.user.Permission.ADMIN_DELETE;
|
||||
import static com.goi.erp.user.Permission.ADMIN_READ;
|
||||
import static com.goi.erp.user.Permission.ADMIN_UPDATE;
|
||||
import static com.goi.erp.user.Permission.MANAGER_CREATE;
|
||||
import static com.goi.erp.user.Permission.MANAGER_DELETE;
|
||||
import static com.goi.erp.user.Permission.MANAGER_READ;
|
||||
import static com.goi.erp.user.Permission.MANAGER_UPDATE;
|
||||
import static com.goi.erp.user.Role.ADMIN;
|
||||
import static com.goi.erp.user.Role.MANAGER;
|
||||
import static org.springframework.http.HttpMethod.DELETE;
|
||||
import static org.springframework.http.HttpMethod.GET;
|
||||
import static org.springframework.http.HttpMethod.POST;
|
||||
import static org.springframework.http.HttpMethod.PUT;
|
||||
//import static com.goi.erp.user.Permission.ADMIN_CREATE;
|
||||
//import static com.goi.erp.user.Permission.ADMIN_DELETE;
|
||||
//import static com.goi.erp.user.Permission.ADMIN_READ;
|
||||
//import static com.goi.erp.user.Permission.ADMIN_UPDATE;
|
||||
//import static com.goi.erp.user.Permission.MANAGER_CREATE;
|
||||
//import static com.goi.erp.user.Permission.MANAGER_DELETE;
|
||||
//import static com.goi.erp.user.Permission.MANAGER_READ;
|
||||
//import static com.goi.erp.user.Permission.MANAGER_UPDATE;
|
||||
//import static com.goi.erp.user.Role.ADMIN;
|
||||
//import static com.goi.erp.user.Role.MANAGER;
|
||||
//import static org.springframework.http.HttpMethod.DELETE;
|
||||
//import static org.springframework.http.HttpMethod.GET;
|
||||
//import static org.springframework.http.HttpMethod.POST;
|
||||
//import static org.springframework.http.HttpMethod.PUT;
|
||||
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
|
|
@ -50,19 +57,38 @@ public class SecurityConfiguration {
|
|||
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||
private final AuthenticationProvider authenticationProvider;
|
||||
private final LogoutHandler logoutHandler;
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
|
||||
config.setAllowedOrigins(List.of("http://localhost:5173"));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-CSRF-TOKEN"));
|
||||
config.setAllowCredentials(true);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.cors(Customizer.withDefaults())
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.authorizeHttpRequests(req ->
|
||||
req.requestMatchers(WHITE_LIST_URL)
|
||||
req.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.requestMatchers("/auth/login", "/auth/token/refresh", "/auth/login/system").permitAll()
|
||||
.requestMatchers("/auth/logout").authenticated()
|
||||
.requestMatchers(WHITE_LIST_URL)
|
||||
.permitAll()
|
||||
.requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name())
|
||||
.requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name())
|
||||
.requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name())
|
||||
.requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name())
|
||||
.requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name())
|
||||
// .requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name())
|
||||
// .requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name())
|
||||
// .requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name())
|
||||
// .requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name())
|
||||
// .requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name())
|
||||
.anyRequest()
|
||||
.authenticated()
|
||||
)
|
||||
|
|
@ -70,7 +96,7 @@ public class SecurityConfiguration {
|
|||
.authenticationProvider(authenticationProvider)
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.logout(logout ->
|
||||
logout.logoutUrl("/api/v1/auth/logout")
|
||||
logout.logoutUrl("/auth/logout")
|
||||
.addLogoutHandler(logoutHandler)
|
||||
.logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext())
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ import java.util.List;
|
|||
public interface EmployeeRoleRepository extends JpaRepository<EmployeeRole, Integer> {
|
||||
|
||||
@Query("""
|
||||
SELECT er
|
||||
FROM EmployeeRole er
|
||||
WHERE er.employee.id = :empId
|
||||
AND er.emrRevokedAt IS NULL
|
||||
""")
|
||||
List<EmployeeRole> findActiveRolesByEmployeeId(Long empId);
|
||||
SELECT er
|
||||
FROM EmployeeRole er
|
||||
JOIN FETCH er.roleInfo
|
||||
WHERE er.employee.id = :empId
|
||||
AND er.emrRevokedAt IS NULL
|
||||
""")
|
||||
List<EmployeeRole> findActiveRolesByEmployeeId(Long empId);
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.goi.erp.token;
|
|||
|
||||
import com.goi.erp.employee.Employee;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -9,14 +10,26 @@ import java.util.Optional;
|
|||
|
||||
public interface TokenRepository extends JpaRepository<Token, Integer> {
|
||||
|
||||
@Query("""
|
||||
select t from Token t
|
||||
where t.employee.id = :employeeId
|
||||
and (t.expired = false or t.revoked = false)
|
||||
""")
|
||||
List<Token> findAllValidTokenByEmployee(Long employeeId);
|
||||
@Query("""
|
||||
select t from Token t
|
||||
where t.employee.id = :employeeId
|
||||
and t.expired = false
|
||||
and t.revoked = false
|
||||
""")
|
||||
List<Token> findAllValidTokenByEmployee(Long employeeId);
|
||||
|
||||
Optional<Token> findByToken(String token);
|
||||
|
||||
List<Token> findByEmployee(Employee employee);
|
||||
|
||||
@Modifying
|
||||
@Query("""
|
||||
update Token t
|
||||
set t.expired = true, t.revoked = true
|
||||
where t.employee.id = :employeeId
|
||||
and t.tokenType in ('ACCESS','REFRESH')
|
||||
and t.expired = false
|
||||
and t.revoked = false
|
||||
""")
|
||||
int revokeAllValidTokensByEmployee(Long employeeId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.goi.erp.token;
|
||||
|
||||
public enum TokenType {
|
||||
ACCESS,
|
||||
REFRESH,
|
||||
BEARER
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ application:
|
|||
secret: ${OPR_SYSTEM_CLIENT_SECRET}
|
||||
permissions:
|
||||
- "H:R:A"
|
||||
|
||||
auth:
|
||||
cookie:
|
||||
secure: false # prod: true / local: false
|
||||
same-site: Lax # 필요 시 None (→ Secure 필수)
|
||||
path: /
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
|
|
|
|||
Loading…
Reference in New Issue