[데브코스] Spring Security - 3
Updated: Categories: TILW16D2 - Session 관련 필터 / 인가처리에 대해 알아보자
SecurityContextPersistenceFilter
- 로그인 후 세션을 통해
SecurityContext
를 관리해주는 필터. SecurityContextRepository
인터페이스 구현체를 통해 사용자의SecurityContext
를 가져오거나 갱신함- 인증 관련 필터 중 최상단에 위치 — 이미 인증된 사용자는 다시 로그인할 필요가 없음
SecurityContext
가 존재하지 않는다면, emptySecurityContext
를 생성함
SecurityContextRepository
의 default 구현체는 세션을 이용하는HttpSessionSecurityContextRepository
클래스
SessionManagementFilter
- 사용자의 세션을 관리하는 필터
Session-fixation attack
- 악성 유저가 어떤 사용자의 로그인 전 세션을 탈취하여 가지고 있다면?
- 해당 사용자가 로그인 했다면 악성 사용자는 해당 세션을 가지고 똑같이 인증이 가능함..!
Session-fixation protection
- 위와 같은 세션 하이재킹을 막기 위해선 로그인 후에 세션을 새로 발급하면 된다.
- 로그인 전 세션은 쓸모가 없어지므로 해당 공격에 효과적으로 대응이 가능하다.
필터 사용법
http.sessionManagement()
.invalidSessionUrl(URL) // 유효하지 않은 세션 감지 시 지정된 URL로 리다이렉트
.sessionFixation().보호전략()
.sessionCreationPolicy(SessionCreationPolicy.생성전략)
.maximumSessions(세션 수) // 동일 사용자의 최대 세션 수
.maxSessionsPreventsLogin(bool값) // 최대 세션 개수 도달시 로그인 가능 여부
.and()
- 세션 보호 전략 4가지 옵션
.none()
: 아무것도 하지 않음 (세션을 그대로 유지함).newSession()
: 새로운 세션을 만들고, 기존 데이터는 복제하지 않음.migrateSession()
: 새로운 세션을 만들고, 데이터를 모두 복제함.changeSessionId()
: 새로운 세션을 만들지 않지만, session-fixation 공격을 방어함 (단, servlet 3.1 이상에서만 지원)
- 세션 생성 전략 4가지 옵션
IF_REQUIRED
: 필요시 생성함 (기본값)NEVER
: Spring Security에서는 세션을 생성하지 않지만, 세션이 존재하면 사용함STATELESS
: 세션을 완전히 사용하지 않음 (JWT 인증이 사용되는 REST API 서비스에 적합)ALWAYS
: 항상 세션을 사용함
Authorization
- 인증된 사용자와 권한을 매핑해야 함
- Spring Security에서는 보통 ‘역할’이라고 함 (예: ROLE_USER, ROLE_ADMIN, ROLE_ANONYMOUS)
- 보호되는 리소스에 대한 권한 확인
- 관리자 권한을 가진 사용자만 관리자 페이지에 접근 가능
FilterSecurityInterceptor
- 필터체인 가장 마지막에 위치하며 사용자의 권한과 요청한 리소스의 권한을 비교, 접근가능 여부를 판단함
- 실질적으로 접근 허용 여부 판단은
AccessDecisionManager
인터페이스 구현체에서 이루어짐
- 실질적으로 접근 허용 여부 판단은
- 해당 필터가 호출된다면 사용자는 이미 인증이 완료되어 있으므로 권한 목록을 가져올 수 있다!
- 익명 사용자도 인증이 완료된 것으로 간주하며, ROLE_ANONYMOUS 권한을 가짐
- 보호되는 리소스에서 요구하는 권한 정보는
SecurityMetadataSource
인터페이스를 통해ConfigAttribute
타입으로 가져옴
AccessDecisionManager
- 사용자가 갖고 있는 권한과 리소스에서 요구하는 권한을 확인하고, 사용자가 적절한 권한을 갖고 있지 않다면 접근 거부 처리함
AccessDecisionVoter
목록을 갖고 있음AccessDecisionVoter
들의 투표(vote)결과를 취합하고, 접근 승인 여부를 결정하는 3가지 구현체를 제공함AffirmativeBased
:AccessDecisionVoter
가 승인하면 이전에 거부된 내용과 관계없이 접근이 승인됨 (기본값)ConsensusBased
: 다수의AccessDecisionVoter
가 승인하면 접근이 승인됨UnanimousBased
: 모든AccessDecisionVoter
가 만장일치로 승인해야 접근이 승인됨
WebExpressionVoter
- SpEL(Spring Expression Language) 표현식을 사용해 접근 승인 여부에 대한 규칙을 지정할 수 있음
- SpEL 표현식 처리를 위해
DefaultWebSecurityExpressionHandler
그리고WebSecurityExpressionRoot
구현에 의존함 WebSecurityExpressionRoot
클래스는 SpEL 표현식에서 사용할수 있는 다양한 메소드를 제공
표현식 | 설명 | 예시 |
---|---|---|
hasIpAddress(ipAddress) | 요청 IP 주소가 특정 IP 주소 또는 특정 대역에 해당하는지 확인 | access=”hasRole(‘ADMIN’) and hasIpAddress(‘192.168.1.0/24’)” |
hasRole(String role) | 사용자가 특정 role을 갖고 있는지 확인 | access=”hasRole(‘USER’)” |
hasAnyRole(String… roles) | 사용자가 주어진 role 목록 중 매칭되는 role을 갖고 있는지 확인 | access=”hasAnyRole(‘USER’, ‘ADMIN’)” |
hasAuthority(String authority) | 사용자가 특정 권한을 갖고 있는지 확인 | access=”hasAuthority(‘ROLE_USER’)” |
hasAnyAuthority(String… authorities) | 사용자가 주어진 권한 목록 중 매칭되는 권한을 갖고 있는지 확인 | access=”hasAnyAuthority(‘ROLE_USER’, ‘ROLE_ADMIN’)” |
permitAll | 모든 사용자에 대해 접근 허용 | access=”permitAll” |
denyAll | 모든 사용자에 대해 접근 거부 | access=”denyAll” |
isAnonymous() | 사용자가 익명 사용자인지 확인 | access=”isAnonymous()” |
isRememberMe() | 사용자가 remember-me 를 통해 인증되었는지 확인 | access=”isRememberMe() or hasRole(‘USER’)” |
isAuthenticated() | 사용자가 인증되었는지 확인 | access=”isAuthenticated()” |
isFullyAuthenticated() | 사용자가 익명 사용자가 아니고, remember-me 인증 사용자도 아닌지 확인 | access=”isFullyAuthenticated() and hasRole(‘USER’)” |
- 기본적으로 제공하는 SpEL 표현식 외에 커스텀 표현식을 생성할 수 있다.
권한 규칙 커스텀
- 1. 표현식 추가 :
WebSecurityExpressionRoot
를 상속받은 클래스를 생성하고 커스텀 규칙 메소드를 추가한다- 해당 메소드는 반환값이 bool값이어야 함.
public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot {
...
커스텀 규칙 메소드
...
}
- 2. 핸들러 추가 :
AbstractSecurityExpressionHandler<FilterInvocation>
를 상속받은 클래스를 생성한다- 표현식에 커스텀한
WebSecurityExpressionRoot
를 지정해주기 위함. DefaultWebSecurityExpressionHandler
와 거의 유사하게 만들어줌
- 표현식에 커스텀한
public class CustomWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler<FilterInvocation> {
private final AuthenticationTrustResolver trustResolver;
private final String defaultRolePrefix;
public CustomWebSecurityExpressionHandler(AuthenticationTrustResolver trustResolver, String defaultRolePrefix) {
this.trustResolver = trustResolver;
this.defaultRolePrefix = defaultRolePrefix;
}
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
CustomWebSecurityExpressionRoot root = new CustomWebSecurityExpressionRoot(authentication, fi);
root.setPermissionEvaluator(this.getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(this.getRoleHierarchy());
root.setDefaultRolePrefix(this.defaultRolePrefix);
return root;
}
}
- 3. 커스텀 핸들러 팩토리 메소드를 만들고 필터 체이닝에서 추가해준다.
public SecurityExpressionHandler<FilterInvocation> securityExpressionHandler() {
return new CustomWebSecurityExpressionHandler(
new AuthenticationTrustResolverImpl(),
"ROLE_"
);
}
http.authorizeRequests()
...
.expressionHandler(securityExpressionHandler())
- 원래는 자동으로
DefaultWebSecurityExpressionHandler
로 지정된다.