Spring Boot、Spring Secuirity、Spring Session (redis) を使用して、Spring REST Web アプリケーションを構築しています。spring cloud と zuul プロキシを使用して、ゲートウェイ パターンに従ってクラウド アプリケーションを構築しています。このパターンでは、Spring セッションを使用して HttpSesssion を redis で管理し、それを使用してリソース サーバーでリクエストを承認しています。セッションの権限を変更する操作が実行されたときに、そのオブジェクトを更新して、更新を反映するためにユーザーがログアウトする必要がないようにしたいと考えています。誰かがこれに対する解決策を持っていますか?
1 に答える
権限を更新するには、2 つの場所で認証オブジェクトを変更する必要があります。1 つはセキュリティ コンテキストにあり、もう 1 つはリクエスト コンテキストにあります。プリンシパル オブジェクトは org.springframework.security.core.userdetails.User になるか、そのクラスを拡張します (UserDetailsService をオーバーライドした場合)。これは、現在のユーザーを変更するために機能します。
Authentication newAuth = new UsernamePasswordAuthenticationToken({YourPrincipalObject},null,List<? extends GrantedAuthority>)
SecurityContextHolder.getContext().setAuthentication(newAuth);
RequestContextHolder.currentRequestAttributes().setAttribute("SPRING_SECURITY_CONTEXT", newAuth, RequestAttributes.SCOPE_GLOBAL_SESSION);
ログインしているユーザーのスプリング セッションを使用してセッションを更新するには、カスタム フィルターが必要です。フィルタには、何らかのプロセスによって変更された一連のセッションが保存されます。新しいセッションを変更する必要がある場合、メッセージング システムはその値を更新します。要求に一致するセッション キーがある場合、フィルターはデータベース内のユーザーを検索して更新をフェッチします。次に、セッションの「SPRING_SECURITY_CONTEXT」プロパティを更新し、SecurityContextHolder の認証を更新します。ユーザーはログアウトする必要はありません。フィルターの順序を指定するときは、SpringSessionRepositoryFilter の後に来ることが重要です。そのオブジェクトの @Order は -2147483598 であるため、フィルターを 1 つ変更して、次に実行されるものであることを確認しました。
ワークフローは次のようになります。
- ユーザー A 権限の変更
- メッセージをフィルターに送信
- 設定するユーザー A セッション キーを追加します (フィルター内)。
次にユーザー A がフィルターを通過したときに、セッションを更新します
@Component @Order(UpdateAuthFilter.ORDER_AFTER_SPRING_SESSION) public class UpdateAuthFilter extends OncePerRequestFilter { public static final int ORDER_AFTER_SPRING_SESSION = -2147483597; private Logger log = LoggerFactory.getLogger(this.getClass()); private Set<String> permissionsToUpdate = new HashSet<>(); @Autowired private UserJPARepository userJPARepository; private void modifySessionSet(String sessionKey, boolean add) { if (add) { permissionsToUpdate.add(sessionKey); } else { permissionsToUpdate.remove(sessionKey); } } public void addUserSessionsToSet(UpdateUserSessionMessage updateUserSessionMessage) { log.info("UPDATE_USER_SESSION - {} - received", updateUserSessionMessage.getUuid().toString()); updateUserSessionMessage.getSessionKeys().forEach(sessionKey -> modifySessionSet(sessionKey, true)); //clear keys for sessions not in redis log.info("UPDATE_USER_SESSION - {} - success", updateUserSessionMessage.getUuid().toString()); } @Override public void destroy() { } @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { HttpSession session = httpServletRequest.getSession(); if (session != null) { String sessionId = session.getId(); if (permissionsToUpdate.contains(sessionId)) { try { SecurityContextImpl securityContextImpl = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT"); if (securityContextImpl != null) { Authentication auth = securityContextImpl.getAuthentication(); Optional<User> user = auth != null ? userJPARepository.findByUsername(auth.getName()) : Optional.empty(); if (user.isPresent()) { user.get().getAccessControls().forEach(ac -> ac.setUsers(null)); MyCustomUser myCustomUser = new MyCustomUser (user.get().getUsername(), user.get().getPassword(), user.get().getAccessControls(), user.get().getOrganization().getId()); final Authentication newAuth = new UsernamePasswordAuthenticationToken(myCustomUser , null, user.get().getAccessControls()); SecurityContextHolder.getContext().setAuthentication(newAuth); session.setAttribute("SPRING_SECURITY_CONTEXT", newAuth); } else { //invalidate the session if the user could not be found session.invalidate(); } } else { //invalidate the session if the user could not be found session.invalidate(); } } finally { modifySessionSet(sessionId, false); } } } filterChain.doFilter(httpServletRequest, httpServletResponse); }