Skip to content

Latest commit

ย 

History

History
473 lines (377 loc) ยท 13.5 KB

auth-basics.md

File metadata and controls

473 lines (377 loc) ยท 13.5 KB

์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ€(Authorization) ๊ธฐ๋ณธ ๊ฐœ๋…

์ฃผ์š” ๊ฐœ๋…

1. ์ธ์ฆ (Authentication)

  • "๋„ˆ๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •"
  • ์‹ ์›์„ ์ฆ๋ช…ํ•˜๋Š” ๊ณผ์ •
  • ex) ๋กœ๊ทธ์ธ

2. ์ธ๊ฐ€ (Authorization)

  • "๋„ˆ๊ฐ€ ๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •"
  • ๊ถŒํ•œ์„ ํ™•์ธํ•˜๋Š” ๊ณผ์ •
  • 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) {
        // ์‚ฌ์šฉ์ž ์ƒ์„ฑ ๋กœ์ง
    }
}

์ด๋Ÿฌํ•œ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ํ†ตํ•ด:

  1. ์ธ์ฆ(Authentication)

    • ์‚ฌ์šฉ์ž ์‹ ์› ํ™•์ธ
    • ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฒ€์ฆ
    • ์ธ์ฆ ์ƒํƒœ ๊ด€๋ฆฌ
  2. ์ธ๊ฐ€(Authorization)

    • ๊ถŒํ•œ ๊ฒ€์ฆ
    • ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ์ œ์–ด
    • ์—ญํ•  ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ๊ด€๋ฆฌ
  3. ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ

    • ์ธ์ฆ ์ •๋ณด ์œ ์ง€
    • ์Šค๋ ˆ๋“œ๋ณ„ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ
    • ๋ณด์•ˆ ์ •๋ณด ๊ฒฉ๋ฆฌ
  4. ์ ‘๊ทผ ์ œ์–ด

    • AOP ๊ธฐ๋ฐ˜ ๋ณด์•ˆ ์ฒ˜๋ฆฌ
    • ์„ ์–ธ์  ๋ณด์•ˆ ์„ค์ •
    • ์„ธ๋ฐ€ํ•œ ๊ถŒํ•œ ์ œ์–ด

๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉด์ ‘๊ด€: ์‹ค์ œ ์„œ๋น„์Šค์—์„œ ์ธ์ฆ/์ธ๊ฐ€ ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ๋•Œ์˜ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?

์‹ค์ œ ์„œ๋น„์Šค์˜ ์ธ์ฆ/์ธ๊ฐ€ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

1. ํŒจ์Šค์›Œ๋“œ ๋ณด์•ˆ

@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);
    }
}

2. ์„ธ์…˜ ๋ณด์•ˆ

@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);
            }
        });
    }
}

3. ์ ‘๊ทผ ์ œ์–ด ๊ฐ•ํ™”

@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);
            }
        }
    }
}

4. ๊ณต๊ฒฉ ๋ฐฉ์ง€

@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);
        }
    }
}

์ด๋Ÿฌํ•œ ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ์„ ํ†ตํ•ด:

  1. ํŒจ์Šค์›Œ๋“œ ๋ณด์•ˆ

    • ์•ˆ์ „ํ•œ ํ•ด์‹ฑ
    • ๊ฐ•๋ ฅํ•œ ์ •์ฑ…
    • ์ด๋ ฅ ๊ด€๋ฆฌ
  2. ์„ธ์…˜ ๋ณด์•ˆ

    • ํ•˜์ด์žฌํ‚น ๋ฐฉ์ง€
    • ๋™์‹œ ์ ‘์† ์ œ์–ด
    • ์ด์ƒ ์ง•ํ›„ ๊ฐ์ง€
  3. ์ ‘๊ทผ ์ œ์–ด

    • ์„ธ๋ฐ€ํ•œ ๊ถŒํ•œ ๊ด€๋ฆฌ
    • ๋™์  ๊ถŒํ•œ ๋ถ€์—ฌ
    • ๊ฐ์‚ฌ ๋กœ๊น…
  4. ๊ณต๊ฒฉ ๋ฐฉ์ง€

    • ๋ธŒ๋ฃจํŠธํฌ์Šค ๋ฐฉ์–ด
    • ์ž…๋ ฅ ๊ฒ€์ฆ
    • ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง

์„ ๊ตฌํ˜„ํ•˜์—ฌ ๋ณด์•ˆ์„ฑ์„ ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.