작성
·
482
·
수정됨
0
안녕하세요 구버전에 이어 신버전 강의도 출시해주셔서 감사합니다!
구버전 커뮤니티에 질문올렸었는데요~
이번 강의를 봐도 해결이 되지않아서 질문 올려봅니답..!
직접 spring security 메인테이너나 컨트리뷰터들한테도 물어봤는데, 제가 제대로 질문을 못해서인지 해결을 못했는데요.
시큐리티 + formlogin + Redis를 활용해서 인증방식을 구현했습니다.
아래 코드로 인증 객체를 꺼낼때 문제가 발생합니다.
@ResponseStatus(HttpStatus.OK)
@GetMapping("/test")
public void test() {
SecurityContextHolderStrategy contextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
System.out.println(">> contextHolderStrategy : " + contextHolderStrategy); // org.springframework.security.core.context.ThreadLocalSecurityContextHolderStrategy@7e1fbf12
SecurityContext context = contextHolderStrategy.getContext();
System.out.println(">> context : " + context); // SecurityContextImpl [Authentication=AnonymousAuthenticationToken
Authentication authentication = context.getAuthentication();
System.out.println(">> authentication : " + authentication); // AnonymousAuthenticationToken
MemberContext memberContext = (MemberContext) authentication.getPrincipal();
System.out.println(">> memberContext : " + memberContext); // ClassCastException
String username = memberContext.getUsername();
System.out.println(">> username : " + username);
}
Redis를 사용하지 않고 tomcat에 저장할 경우 session을 통해서 인증 객체를 잘 받아오는데,
>> contextHolderStrategy : org.springframework.security.core.context.ThreadLocalSecurityContextHolderStrategy@54f61d2b
>> context : SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=com.spring.security.config.security.service.MemberContext [Username=sejinpark@email.com, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=A220DB3D8904393F7D9831276564566A], Granted Authorities=[ROLE_ADMIN]]]
>> authentication : UsernamePasswordAuthenticationToken [Principal=com.spring.security.config.security.service.MemberContext [Username=sejinpark@email.com, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=A220DB3D8904393F7D9831276564566A], Granted Authorities=[ROLE_ADMIN]]
>> memberContext : com.spring.security.config.security.service.MemberContext [Username=sejinpark@email.com, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, CredentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN]]
>> username : sejinpark@email.com
Redis만 사용하면 인증 완료 후 인증 후 요청에서 Anonymous로 변경됩니다.
>> contextHolderStrategy : org.springframework.security.core.context.ThreadLocalSecurityContextHolderStrategy@577154a7
>> context : SecurityContextImpl [Authentication=AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=16bec162-3ad3-459d-8b97-bf3d6f1de226], Granted Authorities=[ROLE_ANONYMOUS]]]
>> authentication : AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=16bec162-3ad3-459d-8b97-bf3d6f1de226], Granted Authorities=[ROLE_ANONYMOUS]]
2024-04-17T07:49:13.061+09:00 ERROR 84540 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ClassCastException: class java.lang.String cannot be cast to class com.spring.security.config.security.service.MemberContext (java.lang.String is in module java.base of loader 'bootstrap'; com.spring.security.config.security.service.MemberContext is in unnamed module of loader 'app')] with root cause
java.lang.ClassCastException: class java.lang.String cannot be cast to class com.spring.security.config.security.service.MemberContext (java.lang.String is in module java.base of loader 'bootstrap'; com.spring.security.config.security.service.MemberContext is in unnamed module of loader 'app')
at com.spring.security.controller.MemberController.test(MemberController.java:43) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
Redis 내부에 인증객체가 저장되어 있는것 까지 확인했는데요.
혹시 SecurityFilterChain에 Redis관련 저장소를 별도로 설정을 해줘야 하는지, 어떤 부분을 확인해야하는지 여쭙고 싶습니다.
Redis를 사용하는데 계속 HttpSessionSecurityContextRepository에서 시큐리티 컨텍스트를 찾을 수 없다고 나옵니답.
2024-04-17T17:05:01.251+09:00 WARN 30066 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : SPRING_SECURITY_CONTEXT did not contain a SecurityContext but contained: '{authentication={authorities=[{authority=ROLE_ADMIN}], details={remoteAddress=0:0:0:0:0:0:0:1, sessionId=null}, authenticated=true, principal={password=null, username=sejinpark@email.com, authorities=[{authority=ROLE_ADMIN}], accountNonExpired=true, accountNonLocked=true, credentialsNonExpired=true, enabled=true}, credentials=null, name=sejinpark@email.com}}'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class?
우선 테스트용으로 SecurityFilterChain에서 아래처럼 기존 버전 처럼 사용해서 인증 상태를 무조건 저장할 수 있도록 해놨습니다.
커스텀 인증필터를 사용 안하고, SecurityContextPersistanceFilter를 사용하도록했습니다.securityContext.requireExplicitSave(false);
테스트용으로 만든 레포지토리 링크 첨부합니다!
답변 2
0
네
테스트를 하기 위한 개발환경이나 실행환경등을 자세하게 말씀해 주시면 좋겠습니다.
member 테이블도 없어서 별도로 생성해야 하네요...
일단 포스트 맨으로 테스트 한 후 답변 드리겠습니다.
네 음.
제가 테스트 해 보니 레디스 세션에 SecurityContext 가 저장되어 있지 않고 LinkedHashMap 이 저장되어 있습니다.
HttpSessionSecurityContextRepository.javva
즉 세션으로 부터 SecurityContext 를 가지고 와야 인증 상태를 계속 유지할 수 있는데 Map 객체가 리턴되니 결국 null 을 반환하고 있습니다.
이 부분은 좀 더 확인이 필요할 것 같습니다.
레디스 설정이나 기타 실행 환경등을 확인해 보시기 바랍니다.
0
안녕하세요, 인프런 AI 인턴입니다.
세진님의 질문에 답변드립니다.
Redis를 사용하여 인증 상태를 영속화하는 과정에서의 문제는 상당히 복잡할 수 있으며, 정확한 설정과 구현이 필요합니다. Sézin님이 언급하신 내용에 기반하여 몇 가지 점검해야 할 사항을 안내해드리겠습니다.
Redis에 인증 객체 저장: Redis를 사용하여 인증 객체를 저장할 때, HttpSession
에 직접 저장되는 것이 아닌 RedisSession
을 통해 관리되어야 합니다. 이는 spring-session-data-redis
와 같은 라이브러리를 사용하여 구현할 수 있습니다. Redis와 Spring Security를 함께 사용할 때는, 세션 정보가 Redis에 올바르게 저장되고 관리되어야 합니다.
SecurityContextRepository 설정: HttpSessionSecurityContextRepository
를 사용하는 대신 Redis를 사용하여 Session을 관리
하는 경우에 맞는 SecurityContextRepository
를 구성해야 할 수도 있습니다. spring-session-data-redis
는 Spring Session
을 통해 SecurityContext
를 Redis로 저장하고 관리하는 메커니즘을 제공합니다. 따라서, Spring Session
이 올바르게 구성되었는지 확인하시기 바랍니다.
Security Filter Chain: SecurityFilterChain
에 SessionManagementFilter
와 함께 Spring Session
에서 제공하는 SpringSessionRepositoryFilter
가 올바르게 등록되어 있는지 확인해야 합니다. requireExplicitSave(false)
설정은 세션의 변화를 자동으로 감지하여 저장하는데 도움이 될 수 있으나, 세션 저장 방식과 관련된 근본적인 문제를 해결하지는 못합니다.
세션 ID의 관리: WebAuthenticationDetails
에서의 SessionId
가 null
로 나타나는 문제를 주의 깊게 살펴봐야 합니다. 세션 ID가 올바르게 생성되고 관리되는지, Redis에 저장될 때 유실되지 않는지 확인해야 합니다.
로그와 에러 메시지 분석: 주어진 로그와 에러 메시지를 통해, SPRING_SECURITY_CONTEXT
가 예상대로 작동하지 않는다는 점을 포착할 수 있습니다. 이는 세션 관리나 SecurityContext
관리에 있어서의 설정 문제를 암시할 수 있습니다.
프로젝트의 구성이나 코드의 세부적인 부분까지 검토가 필요할 수 있으므로, https://github.com/codesejin/security-test 링크에 있는 예제 프로젝트에서의 구현 방식을 다시 한번 점검해보시기 바랍니다. 필요한 경우, Spring Session과 관련된 공식 문서나 예제를 참조하여 구성을 검토하고, 필요한 설정이 누락되지 않았는지 확인하여 문제를 해결해 나가시기 바랍니다.
우선 답변 달아주셔서 감사합니다!
회원가입은 REST API로 진행했고, 회원가입 시 PasswordEncoder를 사용해서 비밀번호로 암호화할 수 있도록 진행했습니다. (Postman 테스트했습니다)
로그인의 경우에는 formlogin을 사용했고, 브라우저를 통해 테스트했습니다.
로그인을 Rest API로 만들지 않아도 formlogin의 경우에도 시큐리티 내부적으로 POST http method와 url을 만들어주기 때문에 같이 사용해보려고 했습니다!