복붙노트

[SPRING] Spring 보안의 모든 요청에서 데이터베이스의 UserDetails 객체 다시로드

SPRING

Spring 보안의 모든 요청에서 데이터베이스의 UserDetails 객체 다시로드

나는 요청마다 Spring Security UserDetails 객체를 다시로드하는 방법을 찾고 있었고 어디에서나 예제를 찾을 수 없었다.

누구든지 그런 일을하는 법을 압니까?

기본적으로 사용자 권한은 웹 요청에서 웹 요청으로 변경 될 수 있으므로 요청마다 사용자 권한을 다시로드하려고합니다.

예를 들어, 로그인되어 있고 새로운 권한이 부여 된 사용자 (전자 메일을 통해 새 권한이 있음을 알리는 사용자)는 실제로 그 새로운 권한을 얻은 사용자를 알 수있는 유일한 방법은 로그 아웃 한 다음 다시 로그인하십시오. 나는 가능한 한 피하고 싶습니다.

모든 친절한 조언을 부탁드립니다.

해결법

  1. ==============================

    1.마지막으로, 2 년 후, 위의 질문과이 질문 이후 6 년 동안 여기 Spring의 요청에 따라 사용자의 UserDetails를 다시로드하는 방법에 대한 대답이 있습니다.

    마지막으로, 2 년 후, 위의 질문과이 질문 이후 6 년 동안 여기 Spring의 요청에 따라 사용자의 UserDetails를 다시로드하는 방법에 대한 대답이 있습니다.

    요청 당 사용자 / 보안 컨텍스트를 다시로드하려면 SecurityContextRepository 인터페이스를 구현하는 Spring Security의 HttpSessionSecurityContextRepository의 기본 동작을 무시하는 것이 중요합니다.

    HttpSessionSecurityContextRepository는 Spring Security가 HttpSession으로부터 사용자의 보안 컨텍스트를 얻기 위해 사용하는 클래스입니다. 이 클래스를 호출하는 코드는 SecurityContext를 threadlocal에 배치합니다. 따라서 loadContext (HttpRequestResponseHolder requestResponseHolder) 메소드가 호출 될 때 우리는 DAO 나 Repository에 요청을하고 사용자 / 주체를 다시로드 할 수 있습니다.

    걱정할 필요가없는 것들도 있습니다.

    이 코드는 스레드로부터 안전합니까?

    웹 서버에 스레드 / 요청마다 새 SecurityContext가 생성되는지 여부에 따라 달라집니다. 새 SecurityContext가 만들어진 경우 수명이 좋지만 그렇지 않은 경우 부실 개체 예외와 같은 일부 예기치 않은 예기치 않은 동작, 사용자 / 주체의 잘못된 상태가 데이터 저장소에 저장되는 등의 문제가 발생할 수 있습니다.

    우리의 코드는 잠재적 인 멀티 스레드 문제를 테스트하지 않았으므로 충분히 낮은 위험도입니다.

    요청마다 데이터베이스를 호출 할 때 성능이 저하됩니까?

    웹 서버 응답 시간에 눈에 띄는 변화는 없었습니다.

    이 주제에 대한 몇 가지 간단한 메모 ...

    이번 변경으로 얻게 된 혜택 :

    코드

    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.openid.OpenIDAuthenticationToken;
    import org.springframework.security.web.context.HttpRequestResponseHolder;
    import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
    import xxx.repository.security.UserRepository;
    import xxx.model.security.User;
    import xxx.service.security.impl.acegi.AcegiUserDetails;
    
    public class ReloadUserPerRequestHttpSessionSecurityContextRepository extends HttpSessionSecurityContextRepository {
    
        // Your particular data store object would be used here...
        private UserRepository userRepository;
    
        public ReloadUserPerRequestHttpSessionSecurityContextRepository(UserRepository userRepository) {
    
            this.userRepository = userRepository;
        }
    
        public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
    
            // Let the parent class actually get the SecurityContext from the HTTPSession first.
            SecurityContext context = super.loadContext(requestResponseHolder);
    
            Authentication authentication = context.getAuthentication();
    
            // We have two types of logins for our system, username/password
            // and Openid, you will have to specialize this code for your particular application.
            if (authentication instanceof UsernamePasswordAuthenticationToken) {
    
                UserDetails userDetails = this.createNewUserDetailsFromPrincipal(authentication.getPrincipal());
    
                // Create a new Authentication object, Authentications are immutable.
                UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());
    
                context.setAuthentication(newAuthentication);
    
            } else if (authentication instanceof OpenIDAuthenticationToken) {
    
                UserDetails userDetails = this.createNewUserDetailsFromPrincipal(authentication.getPrincipal());
    
                OpenIDAuthenticationToken openidAuthenticationToken = (OpenIDAuthenticationToken) authentication;
    
                // Create a new Authentication object, Authentications are immutable.
                OpenIDAuthenticationToken newAuthentication = new OpenIDAuthenticationToken(userDetails, userDetails.getAuthorities(), openidAuthenticationToken.getIdentityUrl(), openidAuthenticationToken.getAttributes());
    
                context.setAuthentication(newAuthentication);
            }
    
            return context;
        }
    
        private UserDetails createNewUserDetailsFromPrincipal(Object principal) {
    
            // This is the class we use to implement the Spring Security UserDetails interface.
            AcegiUserDetails userDetails = (AcegiUserDetails) principal;
    
            User user = this.userRepository.getUserFromSecondaryCache(userDetails.getUserIdentifier());
    
            // NOTE:  We create a new UserDetails by passing in our non-serializable object 'User', but that object in the AcegiUserDetails is transient.
            // We use a UUID (which is serializable) to reload the user.  See the userDetails.getUserIdentifier() method above.
            userDetails = new AcegiUserDetails(user);
    
            return userDetails;
        }
    }
    

    새로운 SecurityContextRepository를 xml 구성으로 연결하려면 security : http 컨텍스트에서 security-context-repository-ref 속성을 설정하기 만하면됩니다.

    예제 xml :

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:security="http://www.springframework.org/schema/security"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                               http://www.springframework.org/schema/security
                               http://www.springframework.org/schema/security/spring-security-4.0.xsd">
        <security:http context-repository-ref="securityContextRepository" >
             <!-- intercept-url and other security configuration here... -->
        </security:http>
    
        <bean id="securityContextRepository" class="xxx.security.impl.spring.ReloadUserPerRequestHttpSessionSecurityContextRepository" >
            <constructor-arg index="0" ref="userRepository"/>
        </bean>
    </beans>
    
  2. ==============================

    2.안녕하십니까,이 경우 Oownh2의 경우 토큰 기반 인증과 관련된 문제를 공유하고 싶습니다. 처음에는 위의 hooknc 접근 방식을 시도했지만, 제 경우에는 토큰 기반 인증을 사용했기 때문에 인증 개체는 instanceOf Oauth2Authentication이었습니다. 표준 인증 주체와 달리 Oauth2Authentication 개체는 권한 부여 요청 및 인증 개체로 구성됩니다. 또한 주체는 토큰 자체를 사용하여 생성됩니다. 따라서 다른 호출에서 토큰을 재사용하려고 시도하면 교착 상태에있는 오래된 사용자 데이터로 끝납니다. 따라서이 방법은 토큰 기반 인증에서는 작동하지 않습니다.

    안녕하십니까,이 경우 Oownh2의 경우 토큰 기반 인증과 관련된 문제를 공유하고 싶습니다. 처음에는 위의 hooknc 접근 방식을 시도했지만, 제 경우에는 토큰 기반 인증을 사용했기 때문에 인증 개체는 instanceOf Oauth2Authentication이었습니다. 표준 인증 주체와 달리 Oauth2Authentication 개체는 권한 부여 요청 및 인증 개체로 구성됩니다. 또한 주체는 토큰 자체를 사용하여 생성됩니다. 따라서 다른 호출에서 토큰을 재사용하려고 시도하면 교착 상태에있는 오래된 사용자 데이터로 끝납니다. 따라서이 방법은 토큰 기반 인증에서는 작동하지 않습니다.

    내 원래의 문제는 사용자가 사용자 설정을 업데이트 한 후에였습니다. 사용자가 다른 API 호출을하면 이전 사용자 정보가 생길 것입니다. 교장을 업데이트하는 대신 업데이트 후 새 토큰을 발급하는 것이 더 나은 접근 방법이었습니다.

    또한 인증 Oauth2 체계가 완전히 무 상태이고 모든 것이 DB에 저장된다는 것을 추가해야합니다.

  3. ==============================

    3.FilterSecurityInterceptor 재 인증 트릭을 시도하고 있습니다.

    FilterSecurityInterceptor 재 인증 트릭을 시도하고 있습니다.

    JDBC userDetailsService를 사용하여 양식 로그인

    AuthenticationProvider

    false로 인증되었습니다.

    package studying.spring;
    
    import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.UserDetails;
    
    public class MyDaoAuthenticationProvider extends DaoAuthenticationProvider {
    
      @Override
      protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
    
        Authentication result = super.createSuccessAuthentication(principal, authentication, user);
        result.setAuthenticated(false);
    
        return result;
      }
    }
    

    ExceptionTranslationFilter에 대한 AuthenticationEntryPoint

    로그 아웃하고 로그인 페이지로 리디렉션하십시오.

    package studying.spring;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
    import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    
    public class MyLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
    
      public MyLoginUrlAuthenticationEntryPoint(String loginFormUrl) {
        super(loginFormUrl);
      }
    
      @Override
      protected String determineUrlToUseForThisRequest(
          HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
    
        if (exception != null) {
          Authentication auth = SecurityContextHolder.getContext().getAuthentication();
          new SecurityContextLogoutHandler().logout(request, response, auth);
          SecurityContextHolder.getContext().setAuthentication(null);
        }
    
        return super.determineUrlToUseForThisRequest(request, response, exception);
      }
    }
    

    root-context.xml

    rase-credentials attr. 거짓으로.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:security="http://www.springframework.org/schema/security"
        xsi:schemaLocation="
          http://www.springframework.org/schema/security
          http://www.springframework.org/schema/security/spring-security-4.1.xsd
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
    
      <security:http entry-point-ref="myLoginUrlAuthenticationEntryPoint">
        <security:intercept-url pattern="/**" access="hasRole('USER')"/>
        <security:form-login/>
        <security:logout/>
      </security:http>
    
      <security:authentication-manager erase-credentials="false">
        <security:authentication-provider ref="myDaoAuthenticationProvider"/>
      </security:authentication-manager>
    
      <bean id="myLoginUrlAuthenticationEntryPoint" class="studying.spring.MyLoginUrlAuthenticationEntryPoint">
        <constructor-arg name="loginFormUrl" value="/login" />
      </bean>
    
      <bean id="myDaoAuthenticationProvider" class="studying.spring.MyDaoAuthenticationProvider">
        <property name="userDetailsService" ref="jdbcDaoImpl" />
      </bean>
    
      <bean id="jdbcDaoImpl" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource" ref="securityDataSource" />
      </bean>
    
      <bean id="securityDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/studying_spring_security" />
        <property name="username" value="root" />
        <property name="password" value="" />
      </bean>
    </beans>
    
  4. ==============================

    4.관리 사용자가 다른 사용자의 권한을 변경하는 경우 : 영향을받는 사용자의 세션을 검색하고 일부 속성을 설정하여 다시로드가 필요함을 나타낼 수 있습니다.

    관리 사용자가 다른 사용자의 권한을 변경하는 경우 : 영향을받는 사용자의 세션을 검색하고 일부 속성을 설정하여 다시로드가 필요함을 나타낼 수 있습니다.

    만약 당신이 (예를 들어, 여러 컨테이너 인스턴스를 지원하기 위해) 데이터베이스에 지속 된 세션 속성을 가진 스프링 세션을 사용한다면, admin 사용자가 권한을 변경할 때 다시로드 할 활성 세션에 태그를 붙일 수 있습니다 :

    @Autowired
    private FindByIndexNameSessionRepository<Session> sessionRepo;
    
    public void tag(String username) {
        Map<String, Session> sessions = sessionRepo.findByIndexNameAndIndexValue
                (FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username);
        for (Session s : sessions.values()) {
            s.setAttribute("reloadAuth", true);
            sessionRepo.save(s);
        }
    }
    

    로그인 한 사용자 쪽 : 현재 세션의 인증을 다시로드할지 여부를 확인할 세션 속성을 확인할 스프링 보안 필터를 작성할 수 있습니다. 관리자가 다시로드 태그를 지정한 경우 DB에서 Principal을 다시 검색하고 인증을 다시 설정합니다.

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpSession session = ((HttpServletRequest) request).getSession();
        if (session != null) {
            Boolean reload = (Boolean) session.getAttribute("reloadAuth");
            if (Boolean.TRUE.equals(shouldReloadRoles)) {
                session.removeAttribute("reloadAuth");
                /* Do some locking based on session ID if you want just to avoid multiple reloads for a session */
                Authentication newAuth = ... // Load new authentication from DB
                SecurityContextHolder.getContext().setAuthentication(newAuth);
            }
        }
        chain.doFilter(request, response);
    }
    
  5. from https://stackoverflow.com/questions/23072235/reload-userdetails-object-from-database-every-request-in-spring-security by cc-by-sa and MIT license