👉🏻 관리자가 사용자의 권한 수정시 해당 사용자가 접속해 있을 경우 즉시 권한이 적용되도록 구현
👉🏻 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);
}
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);
}
}
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 클래스의 생성자를 보면 당연한 결과.
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을 만들어 주입해준 결과 정상적으로 적용되었다.