Spring Security Authentication 핸들링

0

SpringBoot

목록 보기
3/3

관리자 권한 수정

👉🏻 관리자가 사용자의 권한 수정시 해당 사용자가 접속해 있을 경우 즉시 권한이 적용되도록 구현

  • 이 기능을 구현하기 위해 구글링을 해보았는데 보통 Redis를 이용하여 세션클러스팅 하는 케이스들이 많았음.
  • 현재 서비스에서는 Redis를 추가할 수 없기 때문에 어플리케이션 레벨에서 세션을 핸들링 하는 방법으로 구현 하기로 함.

WebSessionListener

👉🏻 Java servlet에서 제공하는 HttpSessionAttributeListener, HttpSessionListener 인터페이스를 상속받아 구현

@Component
public class WebSessionListener implements HttpSessionAttributeListener, HttpSessionListener {

    public static ConcurrentHashMap<String, HttpSession> sessionMap = new ConcurrentHashMap();


    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {
        attributeSessionMap(se);
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) {
        attributeSessionMap(se);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        SecurityContextImpl contextImpl = SecuritySessionUtils.getContextInSession(session);
        String userId = getNameInContextImpl(contextImpl);
        sessionMap.remove(userId);
    }

    private void attributeSessionMap(HttpSessionBindingEvent se){
        if(!(se.getName()).contains(SecuritySessionUtils.SPRING_SECURITY)){
            return;
        }
        HttpSession session = se.getSession();
        SecurityContextImpl contextImpl = (SecurityContextImpl) se.getValue();
        String userId = getNameInContextImpl(contextImpl);
        sessionMap.put(userId, session);
    }
  • Session을 분석해보니 SPRING_SECURITY_CONTEXT 라는 key값으로 인증을 담아두고 있었다.
  • 원래 HttpSessionListener 의 sessionCreated 메소드를 오버라이딩 했지만 해당 이벤트는 springsecurityContext가 저장되기 전에 호출되는 이벤트였음.
  • 그래서 HttpSessionAttributeListener 의 attributeAdded 메소드를 오버라이딩 하여 구현.
  • Threadsafe 하기 위해 ConcurrentHashMap<String, HttpSession> sessionMap 으로 세션목록 관리.
  • userId를 key, Session을 value값으로 저장

SecuritySessionUtils

sessionMap 핸들링하여 SecurityContextImpl, Authentication 접근.

public class SecuritySessionUtils {

    public static final String SPRING_SECURITY = "SPRING_SECURITY";
    public static final String SPRING_SECURITY_CONTEXT = SPRING_SECURITY + "_CONTEXT";

    public static Authentication getAuthenticationByUserId(String userId){
        HttpSession session = getSessionByUserId(userId);
        SecurityContextImpl contextImpl = getContextInSession(session);
        return contextImpl.getAuthentication();
    }

    public static HttpSession getSessionByUserId(String userId){
        return WebSessionListener.sessionMap.get(userId);
    }

    public static SecurityContextImpl getContextByUserId(String userId){
        HttpSession session = getSessionByUserId(userId);
        return getContextInSession(session);
    }

    public static SecurityContextImpl getContextInSession(HttpSession session){
        return (SecurityContextImpl) session.getAttribute(SPRING_SECURITY_CONTEXT);
    }

}

setAuthorities

UsernamePasswordAuthenticationToken을 새롭게 만들어 context에 주입하는 방법

public static void setAuthorities(String userId, List<String> roleNames) {
        HttpSession session = SecuritySessionUtils.getSessionByUserId(userId);
        if(session == null) return;
        SecurityContextImpl context = SecuritySessionUtils.getContextInSession(session);
        Authentication accessAuth = context.getAuthentication();
        List<GrantedAuthority> updatedAuthorities = new ArrayList<>();
        roleNames.stream()
                .forEach(x -> updatedAuthorities.add(new SimpleGrantedAuthority(concatRolePrefix(x))));
        Authentication newAuth = new UsernamePasswordAuthenticationToken(
                accessAuth.getPrincipal(),
                null,
                updatedAuthorities
        );
        context.setAuthentication(newAuth);
    }

위에 코드를 적용했을 시 이슈가 있었다.
❗️권한은 즉시 적용되었지만 html 에서 해당 유저의 Role 한글이름을 보여주는데 기존의 권한으로 보여지는 문제가 있었다.


디버그 결과 새롭게 생성한 AuthenticationToken 의 authorities는 재 할당이 되었지만, Principal안에있는 authorities와 roleNames은 변경되지 않고 있었다.

(사실 당연함. 기존의 Authorities를 불러와 그 안에 있는 Principal로 만든 token이었기 때문에)
-> 아래 UsernamePasswordAuthenticationToken 클래스의 생성자를 보면 당연한 결과.

setAuthorities 코드 수정

public static void setAuthorities(Member member, List<String> roleNames) {
        HttpSession session = SecuritySessionUtils.getSessionByUserId(member.getUserId());
        if(session == null) return;
        SecurityContextImpl context = SecuritySessionUtils.getContextInSession(session);
        List<GrantedAuthority> updatedAuthorities = new ArrayList<>();
        roleNames.stream()
                .forEach(x -> updatedAuthorities.add(new SimpleGrantedAuthority(concatRolePrefix(x))));
        Authentication newAuth = new UsernamePasswordAuthenticationToken(
                new CustomUserDetailsImpl(member),
                null,
                updatedAuthorities
        );
        context.setAuthentication(newAuth);
    }

기존에 accessAuth.getPrincipal() 대신에 변경된 member entity로 새로운CustomUserDetailsImpl을 만들어 주입해준 결과 정상적으로 적용되었다.

0개의 댓글