- "๋๊ฐ ๋๊ตฌ์ธ์ง ํ์ธํ๋ ๊ณผ์ "
- ์ ์์ ์ฆ๋ช ํ๋ ๊ณผ์
- ex) ๋ก๊ทธ์ธ
- "๋๊ฐ ๋ฌด์์ ํ ์ ์๋์ง ํ์ธํ๋ ๊ณผ์ "
- ๊ถํ์ ํ์ธํ๋ ๊ณผ์
- ex) ๊ด๋ฆฌ์ ํ์ด์ง ์ ๊ทผ ๊ถํ
@Service
public class AuthenticationService {
// 1. ๊ธฐ๋ณธ์ ์ธ ์ธ์ฆ ์ฒ๋ฆฌ
public class AuthenticationManager {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public AuthenticationResult authenticate(
String username, String password) {
// 1. ์ฌ์ฉ์ ์กฐํ
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new AuthenticationException(
"User not found"));
// 2. ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new AuthenticationException("Invalid password");
}
// 3. ์ธ์ฆ ์ฑ๊ณต ์ฒ๋ฆฌ
return createAuthenticationResult(user);
}
private AuthenticationResult createAuthenticationResult(User user) {
return AuthenticationResult.builder()
.userId(user.getId())
.username(user.getUsername())
.roles(user.getRoles())
.authorities(user.getAuthorities())
.authenticated(true)
.build();
}
}
// 2. ๊ถํ ๊ด๋ฆฌ
@Component
public class AuthorizationManager {
public boolean hasPermission(
Authentication authentication,
String resource,
String action) {
// 1. ์ฌ์ฉ์ ๊ถํ ํ์ธ
Set<String> authorities = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
// 2. ๋ฆฌ์์ค์ ๋ํ ๊ถํ ๊ฒ์ฆ
Permission requiredPermission =
Permission.of(resource, action);
return authorities.contains(requiredPermission.toString()) ||
hasRole(authentication, "ADMIN");
}
private boolean hasRole(
Authentication authentication,
String role) {
return authentication.getAuthorities()
.stream()
.anyMatch(auth ->
auth.getAuthority().equals("ROLE_" + role));
}
}
}
// 3. Security Context ๊ด๋ฆฌ
@Component
public class SecurityContextHolder {
private static final ThreadLocal<SecurityContext> contextHolder =
new ThreadLocal<>();
public static void setContext(SecurityContext context) {
contextHolder.set(context);
}
public static SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
public static void clearContext() {
contextHolder.remove();
}
}
// 4. ์ ๊ทผ ์ ์ด ๊ตฌํ
@Aspect
@Component
public class SecurityAspect {
private final AuthorizationManager authorizationManager;
@Around("@annotation(secured)")
public Object checkSecurity(
ProceedingJoinPoint joinPoint,
Secured secured) throws Throwable {
// 1. ํ์ฌ ์ธ์ฆ ์ ๋ณด ํ์ธ
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
if (authentication == null ||
!authentication.isAuthenticated()) {
throw new AccessDeniedException("Authentication required");
}
// 2. ๊ถํ ํ์ธ
if (!authorizationManager.hasPermission(
authentication,
secured.resource(),
secured.action())) {
throw new AccessDeniedException("Insufficient privileges");
}
return joinPoint.proceed();
}
}
// 5. ์ฌ์ฉ ์์
@RestController
@RequestMapping("/api")
public class UserController {
@Secured(resource = "user", action = "read")
@GetMapping("/users/{id}")
public UserResponse getUser(@PathVariable Long id) {
// ์ฌ์ฉ์ ์กฐํ ๋ก์ง
}
@Secured(resource = "user", action = "write")
@PostMapping("/users")
public UserResponse createUser(@RequestBody UserRequest request) {
// ์ฌ์ฉ์ ์์ฑ ๋ก์ง
}
}
์ด๋ฌํ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ํตํด:
-
์ธ์ฆ(Authentication)
- ์ฌ์ฉ์ ์ ์ ํ์ธ
- ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
- ์ธ์ฆ ์ํ ๊ด๋ฆฌ
-
์ธ๊ฐ(Authorization)
- ๊ถํ ๊ฒ์ฆ
- ๋ฆฌ์์ค ์ ๊ทผ ์ ์ด
- ์ญํ ๊ธฐ๋ฐ ๊ถํ ๊ด๋ฆฌ
-
๋ณด์ ์ปจํ ์คํธ
- ์ธ์ฆ ์ ๋ณด ์ ์ง
- ์ค๋ ๋๋ณ ์ปจํ ์คํธ ๊ด๋ฆฌ
- ๋ณด์ ์ ๋ณด ๊ฒฉ๋ฆฌ
-
์ ๊ทผ ์ ์ด
- AOP ๊ธฐ๋ฐ ๋ณด์ ์ฒ๋ฆฌ
- ์ ์ธ์ ๋ณด์ ์ค์
- ์ธ๋ฐํ ๊ถํ ์ ์ด
๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
๋ฉด์ ๊ด: ์ค์ ์๋น์ค์์ ์ธ์ฆ/์ธ๊ฐ ์์คํ ์ ๊ตฌํํ ๋์ ๋ณด์ ๊ณ ๋ ค์ฌํญ์ ๋ฌด์์ธ๊ฐ์?
@Service
public class PasswordSecurityService {
private final PasswordEncoder passwordEncoder;
private final PasswordValidator passwordValidator;
// 1. ์์ ํ ํจ์ค์๋ ํด์ฑ
public String hashPassword(String rawPassword) {
// BCrypt ์ฌ์ฉ - ์๋ ์ํธ ์์ฑ ๋ฐ ์ ์ฉ
return passwordEncoder.encode(rawPassword);
}
// 2. ํจ์ค์๋ ์ ์ฑ
์ ์ฉ
public void validatePassword(String password) {
PasswordPolicy policy = PasswordPolicy.builder()
.minLength(12)
.requireUppercase(true)
.requireLowercase(true)
.requireNumbers(true)
.requireSpecialChars(true)
.preventCommonPasswords(true)
.preventUserInfoInPassword(true)
.build();
List<PasswordViolation> violations =
passwordValidator.validate(password, policy);
if (!violations.isEmpty()) {
throw new PasswordPolicyViolationException(violations);
}
}
// 3. ์ด์ ํจ์ค์๋ ์ฌ์ฌ์ฉ ๋ฐฉ์ง
@Transactional
public void changePassword(User user, String newPassword) {
// ์ด์ ํจ์ค์๋ ํ์คํ ๋ฆฌ ํ์ธ
if (isPasswordPreviouslyUsed(user, newPassword)) {
throw new PasswordRecentlyUsedException(
"Cannot reuse recent passwords");
}
// ํจ์ค์๋ ๋ณ๊ฒฝ ๋ฐ ํ์คํ ๋ฆฌ ์ ์ฅ
user.setPassword(hashPassword(newPassword));
savePasswordHistory(user, newPassword);
}
}
@Component
public class SessionSecurityManager {
private final RedisTemplate<String, Object> redisTemplate;
// 1. ์ธ์
ํ์ด์ฌํน ๋ฐฉ์ง
public HttpSession secureSession(HttpSession session) {
// ์ธ์
ID ์ฌ์์ฑ
session.invalidate();
HttpSession newSession = request.getSession(true);
// ๋ณด์ ์์ฑ ์ค์
newSession.setAttribute("_csrf", generateCsrfToken());
newSession.setAttribute("client_ip", request.getRemoteAddr());
newSession.setAttribute("user_agent", request.getHeader("User-Agent"));
return newSession;
}
// 2. ๋์ ์ธ์
์ ์ด
public void enforceSessionControl(String userId) {
String sessionKey = "user_sessions:" + userId;
// ์ต๋ ํ์ฉ ์ธ์
์ ํ์ธ
Long sessionCount = redisTemplate.opsForSet()
.size(sessionKey);
if (sessionCount >= MAX_SESSIONS_PER_USER) {
// ๊ฐ์ฅ ์ค๋๋ ์ธ์
์ข
๋ฃ
String oldestSession = redisTemplate.opsForSet()
.pop(sessionKey);
invalidateSession(oldestSession);
}
}
// 3. ์ธ์
ํ์ทจ ๊ฐ์ง
@Scheduled(fixedRate = 5000)
public void detectSessionAnomaly() {
activeSessions.forEach((sessionId, sessionInfo) -> {
if (isAnomalousActivity(sessionInfo)) {
terminateSession(sessionId);
notifySecurityTeam(sessionInfo);
}
});
}
}
@Service
public class EnhancedAuthorizationService {
// 1. ์ธ๋ถํ๋ ๊ถํ ์ฒดํฌ
public boolean checkAccess(
Authentication auth,
SecurityResource resource) {
return Stream.of(
checkResourceOwnership(auth, resource),
checkRoleBasedAccess(auth, resource),
checkTimeBasedAccess(auth, resource),
checkLocationBasedAccess(auth, resource)
).allMatch(result -> result);
}
// 2. ๋์ ๊ถํ ๊ด๋ฆฌ
public class DynamicPermissionManager {
private final Cache<String, Set<Permission>> permissionCache;
public Set<Permission> getEffectivePermissions(
Authentication auth) {
String cacheKey = auth.getName();
return permissionCache.get(cacheKey, key -> {
Set<Permission> permissions = new HashSet<>();
// ๊ธฐ๋ณธ ๊ถํ
permissions.addAll(getBasePermissions(auth));
// ์ํฉ๋ณ ๊ถํ
permissions.addAll(
getContextualPermissions(auth));
// ์์ ๊ถํ
permissions.addAll(
getTemporaryPermissions(auth));
return permissions;
});
}
}
// 3. ๊ฐ์ฌ ๋ก๊น
@Aspect
@Component
public class SecurityAuditAspect {
@Around("@annotation(secured)")
public Object auditSecurityAccess(
ProceedingJoinPoint joinPoint,
Secured secured) throws Throwable {
AuditEvent auditEvent = AuditEvent.builder()
.principal(getCurrentUser())
.action(secured.action())
.resource(secured.resource())
.timestamp(Instant.now())
.status("ATTEMPTED")
.build();
try {
Object result = joinPoint.proceed();
auditEvent.setStatus("SUCCESS");
return result;
} catch (SecurityException e) {
auditEvent.setStatus("DENIED");
auditEvent.setFailureReason(e.getMessage());
throw e;
} finally {
auditLogger.log(auditEvent);
}
}
}
}
@Component
public class SecurityDefenseSystem {
// 1. ๋ธ๋ฃจํธํฌ์ค ๊ณต๊ฒฉ ๋ฐฉ์ง
@Service
public class BruteForceProtection {
private final RateLimiter rateLimiter;
public void checkLoginAttempt(String username) {
String key = "login_attempt:" + username;
if (!rateLimiter.tryAcquire(key)) {
throw new AccountLockedException(
"Too many login attempts");
}
}
public void handleFailedLogin(String username) {
String key = "failed_login:" + username;
int attempts = incrementFailedAttempts(key);
if (attempts >= MAX_FAILED_ATTEMPTS) {
lockAccount(username);
}
}
}
// 2. ์
๋ ฅ ๊ฒ์ฆ
public class InputValidator {
public void validateInput(String input, InputType type) {
Pattern pattern = getValidationPattern(type);
if (!pattern.matcher(input).matches()) {
throw new InvalidInputException(
"Invalid input format");
}
// XSS ๋ฐฉ์ง
input = sanitizeInput(input);
// SQL Injection ๋ฐฉ์ง
input = escapeSqlCharacters(input);
}
}
// 3. ๋ณด์ ๋ชจ๋ํฐ๋ง
@Service
public class SecurityMonitor {
@Scheduled(fixedRate = 1000)
public void monitorSecurityEvents() {
// ๋น์ ์ ํจํด ๊ฐ์ง
detectAnomalousPatterns();
// ๋์ ๋ก๊ทธ์ธ ๊ฐ์ง
detectConcurrentLogins();
// ๊ถํ ๋ณ๊ฒฝ ๊ฐ์ง
detectPrivilegeChanges();
}
private void handleSecurityIncident(
SecurityIncident incident) {
// ์ฆ์ ๋์
immediateResponse(incident);
// ๋ณด์ํ ์๋ฆผ
notifySecurityTeam(incident);
// ์ฆ๊ฑฐ ์์ง
collectEvidence(incident);
}
}
}
์ด๋ฌํ ๋ณด์ ๊ณ ๋ ค์ฌํญ์ ํตํด:
-
ํจ์ค์๋ ๋ณด์
- ์์ ํ ํด์ฑ
- ๊ฐ๋ ฅํ ์ ์ฑ
- ์ด๋ ฅ ๊ด๋ฆฌ
-
์ธ์ ๋ณด์
- ํ์ด์ฌํน ๋ฐฉ์ง
- ๋์ ์ ์ ์ ์ด
- ์ด์ ์งํ ๊ฐ์ง
-
์ ๊ทผ ์ ์ด
- ์ธ๋ฐํ ๊ถํ ๊ด๋ฆฌ
- ๋์ ๊ถํ ๋ถ์ฌ
- ๊ฐ์ฌ ๋ก๊น
-
๊ณต๊ฒฉ ๋ฐฉ์ง
- ๋ธ๋ฃจํธํฌ์ค ๋ฐฉ์ด
- ์ ๋ ฅ ๊ฒ์ฆ
- ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
์ ๊ตฌํํ์ฌ ๋ณด์์ฑ์ ๊ฐํํ ์ ์์ต๋๋ค.