복붙노트

[SPRING] 스프링 보안 3.2.1 WebSecurityConfigurerAdapter가 다른 여러 로그인 폼

SPRING

스프링 보안 3.2.1 WebSecurityConfigurerAdapter가 다른 여러 로그인 폼

Spring Security 3.2.1을 사용하고 있습니다. Spring MVC 4.0.4.RELEASE를 사용하여 릴리스하십시오.

두 개의 별개의 로그인 항목 페이지가있는 웹 응용 프로그램에 대해 Spring Security를 ​​설정하려고합니다. 스타일이 지정되고 다르게 액세스 될 수 있도록 페이지를 구분해야합니다.

첫 번째 로그인 페이지는 관리자 용이며 관리자 페이지 / admin / **을 보호합니다.

두 번째 로그인 페이지는 고객 사용자를위한 것이며 고객 페이지 / 고객 / **을 보호합니다.

개별 HttpSecurity 객체를 구성하는 WebSecurityConfigurerAdapter의 두 하위 클래스를 설정하려고했습니다.

CustomerFormLoginWebSecurity는 고객 페이지를 보호하고 승인되지 않은 경우 고객 로그인 페이지로 리디렉션합니다. AdminFormLoginWebSecurity는 권한이없는 경우 관리자 로그인 페이지로 리디렉션되는 관리 페이지를 보호합니다.

불행히도 첫 번째 설정 만 적용되는 것으로 보입니다. 나는이 두 가지 작업을하기 위해 뭔가 더 많은 것을 놓치고 있다고 생각한다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("customer").password("password").roles("CUSTOMER").and()
                .withUser("admin").password("password").roles("ADMIN");
    }

    @Configuration
    @Order(1)
    public static class CustomerFormLoginWebSecurity extends WebSecurityConfigurerAdapter {

        @Override
        public void configure(WebSecurity web) throws Exception {
            web
                    .ignoring()
                    .antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
        }

        protected void configure(HttpSecurity http) throws Exception {
            http
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/customer/**").hasRole("CUSTOMER")
                    .and()
                    .formLogin()
                    .loginPage("/customer_signin")
                    .failureUrl("/customer_signin?error=1")
                    .defaultSuccessUrl("/customer/home")
                    .loginProcessingUrl("/j_spring_security_check")
                    .usernameParameter("j_username").passwordParameter("j_password")
                    .and()
                    .logout()
                    .permitAll();

            http.exceptionHandling().accessDeniedPage("/customer_signin");
        }
    }

    @Configuration
    public static class AdminFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(WebSecurity web) throws Exception {
            web
                    .ignoring()
                    .antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .and()
                    .formLogin()
                    .loginPage("/admin_signin")
                    .failureUrl("/admin_signin?error=1")
                    .defaultSuccessUrl("/admin/home")
                    .loginProcessingUrl("/j_spring_security_check")
                    .usernameParameter("j_username").passwordParameter("j_password")
                    .and()
                    .logout()
                    .permitAll();

            http.exceptionHandling().accessDeniedPage("/admin_signin");
        }
    }

}

해결법

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

    1.로그인 페이지로 리디렉션되는 스프링 로그인 체인의 구성 요소는 인증 필터이며 http.formLogin ()을 사용할 때 연결되는 필터는 DefaultLoginPageGeneratingFilter입니다.

    로그인 페이지로 리디렉션되는 스프링 로그인 체인의 구성 요소는 인증 필터이며 http.formLogin ()을 사용할 때 연결되는 필터는 DefaultLoginPageGeneratingFilter입니다.

    이 필터는 로그인 URL로 리디렉션되거나 로그인 페이지 URL이 제공되지 않는 경우 기본 기본 로그인 페이지를 작성합니다.

    그런 다음 필요한 로그인 페이지를 정의하고 단일 페이지 인증 필터 대신 스프링 보안 체인에 연결하는 로직이있는 사용자 정의 인증 필터가 필요합니다.

    DefaultLoginPageGeneratingFilter를 서브 클래 싱하고 getLoginPageUrl ()을 오버라이드하여 TwoPageLoginAuthenticationFilter를 만드는 것을 고려해보십시오. 충분하지 않은 경우 코드를 복사하고 필요에 맞게 수정하십시오.

    이 필터는 GenericFilterBean이므로 다음과 같이 선언 할 수 있습니다.

    @Bean
    public Filter twoPageLoginAuthenticationFilter() {
        return new TwoPageLoginAuthenticationFilter();
    }
    

    하나의 http 구성 만 빌드하고 formLogin ()을 설정하지 말고 다음을 수행하십시오.

    http.addFilterBefore(twoPageLoginAuthenticationFilter, ConcurrentSessionFilter.class);
    

    그러면 체인의 올바른 위치에 두 개의 폼 인증 필터가 연결됩니다.

  2. ==============================

    2.여러 로그인 페이지에 대한 해결책은 하나의 HTTP 인증과 관련이 있지만

    여러 로그인 페이지에 대한 해결책은 하나의 HTTP 인증과 관련이 있지만

    필요한 것은 이러한 구현이 요청 경로의 토큰에 종속적으로 전환 할 수 있어야했습니다.

    내 웹 사이트에서는 URL에 고객 토큰이있는 페이지가 보호되며 사용자가 customer_signin 페이지에서 CUSTOMER로 인증해야합니다. 따라서 페이지 / 고객 / 집으로 가려면 먼저 customer_signin 페이지로 리다이렉트하여 인증을 받아야합니다. customer_signin에서 인증하지 못한 경우 오류 매개 변수와 함께 customer_signin으로 반환해야합니다. 메시지를 표시 할 수 있습니다. 고객으로 성공적으로 인증 된 후 로그 아웃하려면 LogoutSuccessHandler가 다시 customer_signin 페이지로 이동해야합니다.

    admin_signin 페이지에서 admin 토큰이있는 페이지에 액세스하기 위해 인증해야하는 관리자와 비슷한 요구 사항이 있습니다.

    먼저 토큰 목록 (각 로그인 페이지 유형마다 하나씩)을 가져올 수있는 클래스를 정의했습니다.

    public class PathTokens {
    
        private final List<String> tokens = new ArrayList<>();
    
        public PathTokens(){};
    
        public PathTokens(final List<String> tokens) {
          this.tokens.addAll(tokens);
        }
    
    
        public boolean isTokenInPath(String path) {
          if (path != null) {
            for (String s : tokens) {
                if (path.contains(s)) {
                    return true;
                }
            }
          }
          return false;
        }
    
        public String getTokenFromPath(String path) {
          if (path != null) {
              for (String s : tokens) {
                  if (path.contains(s)) {
                      return s;
                  }
              }
          }
          return null;
      }
    
      public List<String> getTokens() {
          return tokens;
      }
    }
    

    그런 다음이를 PathLoginAuthenticationEntryPoint에서 사용하여 요청 uri의 토큰에 따라 로그인 URL을 변경합니다.

    @Component
    public class PathLoginAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
        private final PathTokens tokens;
    
        @Autowired
        public PathLoginAuthenticationEntryPoint(PathTokens tokens) {
            //  LoginUrlAuthenticationEntryPoint requires a default
            super("/");
            this.tokens = tokens;
        }
    
        /**
         * @param request   the request
         * @param response  the response
         * @param exception the exception
         * @return the URL (cannot be null or empty; defaults to {@link #getLoginFormUrl()})
         */
        @Override
        protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
                                                     AuthenticationException exception) {
           return getLoginUrlFromPath(request);
        }
    
        private String getLoginUrlFromPath(HttpServletRequest request) {
            String requestUrl = request.getRequestURI();
            if (tokens.isTokenInPath(requestUrl)) {
                return "/" + tokens.getTokenFromPath(requestUrl) + "_signin";
            }
            throw new PathTokenNotFoundException("Token not found in request URL " + requestUrl + " when retrieving LoginUrl for login form");
        }
    }
    

    PathTokenNotFoundException은 AuthenticationException을 상속하므로 일반적인 방법으로 처리 할 수 ​​있습니다.

    public class PathTokenNotFoundException extends AuthenticationException {
    
       public PathTokenNotFoundException(String msg) {
           super(msg);
        }
    
        public PathTokenNotFoundException(String msg, Throwable t) {
           super(msg, t);
        }
    }
    

    다음으로 요청 헤더의 referer url을보고 사용자에게 지시 할 로그인 오류 페이지를 결정하는 AuthenticationFailureHandler 구현을 제공합니다.

    @Component
    public class PathUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    
        private final PathTokens tokens;
    
        @Autowired
        public PathUrlAuthenticationFailureHandler(PathTokens tokens) {
            super();
            this.tokens = tokens;
        }
    
        /**
         * Performs the redirect or forward to the {@code defaultFailureUrl associated with this path} if set, otherwise returns a 401 error code.
         * <p/>
         * If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in
         * the target view.
         */
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
            setDefaultFailureUrl(getFailureUrlFromPath(request));
            super.onAuthenticationFailure(request, response, exception);
    
        }
    
        private String getFailureUrlFromPath(HttpServletRequest request) {
            String refererUrl = request.getHeader("Referer");
            if (tokens.isTokenInPath(refererUrl)) {
                return "/" + tokens.getTokenFromPath(refererUrl) + "_signin?error=1";
            }
            throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving failureUrl for login form");
        }
    }
    

    다음으로 요청 헤더의 referer url에있는 토큰에 따라 사용자를 로그 아웃하고 올바른 로그인 페이지로 리디렉션하는 LogoutSuccessHandler 구현을 제공합니다.

    @Component
    public class PathUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
    
        private final PathTokens tokens;
    
        @Autowired
        public PathUrlLogoutSuccessHandler(PathTokens tokens) {
            super();
            this.tokens = tokens;
        }
    
    
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
    
            setDefaultTargetUrl(getTargetUrlFromPath(request));
            setAlwaysUseDefaultTargetUrl(true);
            handle(request, response, authentication);
        }
    
        private String getTargetUrlFromPath(HttpServletRequest request) {
            String refererUrl = request.getHeader("Referer");
            if (tokens.isTokenInPath(refererUrl)) {
                return "/" + tokens.getTokenFromPath(refererUrl) + "_signin";
            }
            throw new PathTokenNotFoundException("Token not found in referer URL " + refererUrl + " when retrieving logoutUrl.");
        } 
    }
    

    마지막 단계는 보안 구성에서 이들을 하나로 연결하는 것입니다.

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        @Autowired PathLoginAuthenticationEntryPoint loginEntryPoint;
    
        @Autowired PathUrlAuthenticationFailureHandler loginFailureHandler;
    
        @Autowired
        PathUrlLogoutSuccessHandler logoutSuccessHandler;
    
    
        @Bean
        public PathTokens pathTokens(){
            return new PathTokens(Arrays.asList("customer", "admin"));
        }
    
        @Autowired
        public void registerGlobalAuthentication(
            AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                .withUser("customer").password("password").roles("CUSTOMER").and()
                .withUser("admin").password("password").roles("ADMIN");
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web
                .ignoring()
                .antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
               http .csrf().disable()
                .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/customer/**").hasRole("CUSTOMER")
                .and()
                .formLogin()
                .loginProcessingUrl("/j_spring_security_check")
                .usernameParameter("j_username").passwordParameter("j_password")
                .failureHandler(loginFailureHandler);
    
            http.logout().logoutSuccessHandler(logoutSuccessHandler);
            http.exceptionHandling().authenticationEntryPoint(loginEntryPoint);
            http.exceptionHandling().accessDeniedPage("/accessDenied");
        }
    }
    

    이 구성이 완료되면 컨트롤러가 있어야 실제 로그인 페이지로 이동할 수 있습니다. 아래의 SigninControiller는 queryString에서 로그인 오류를 나타내는 값을 확인한 다음 오류 메시지를 제어하는 ​​데 사용되는 속성을 설정합니다.

    @Controller
    @SessionAttributes("userRoles")
    public class SigninController {
        @RequestMapping(value = "customer_signin", method = RequestMethod.GET)
        public String customerSignin(Model model, HttpServletRequest request) {
            Set<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
            model.addAttribute("userRole", userRoles);
    
            if(request.getQueryString() != null){
                model.addAttribute("error", "1");
            }
            return "signin/customer_signin";
        }
    
    
        @RequestMapping(value = "admin_signin", method = RequestMethod.GET)
        public String adminSignin(Model model, HttpServletRequest request) {
        Set<String> userRoles = AuthorityUtils.authorityListToSet(SecurityContextHolder.getContext().getAuthentication().getAuthorities());
            model.addAttribute("userRole", userRoles);
            if(request.getQueryString() != null){
                model.addAttribute("error", "1");
            }
            return "signin/admin_signin";
        }
    }
    
  3. ==============================

    3.아마이 게시물은 당신을 도울 수 있습니다 : 다중 로그인 양식

    아마이 게시물은 당신을 도울 수 있습니다 : 다중 로그인 양식

    그것은 스프링 보안의 다른 버전이지만 동일한 문제입니다 : 첫 번째 구성 만이 수행됩니다.

    두 로그인 페이지 중 하나에 대해 login-processing-url을 변경하여 해결 된 것으로 보이지만 사람들은 동일한 URL 처리를 사용하지만 ViewResolver를 사용하여 다른 레이아웃을 사용하도록 제안합니다. 사용자를 인증하는 데 동일한 메커니즘을 사용하는 경우 솔루션입니다 (인증 메커니즘은 브라우저에서 보내는 자격 증명을 처리하는 책임입니다).

    이 게시물은 loginProcessingUrl을 변경하면 성공할 것이라고합니다. 여러 엔트리 포인트를 갖도록 Spring Security 3.x 설정하기

  4. ==============================

    4.또한이 문제가 발생하여 첫 번째 필터링 부분을 놓친 것을 발견했습니다.

    또한이 문제가 발생하여 첫 번째 필터링 부분을 놓친 것을 발견했습니다.

    이 하나:

    http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")
    

    해야한다:

    http.csrf().disable()
        .antMatcher("/admin/**")
        .authorizeRequests()
        .antMatchers("/admin/**").hasRole("ADMIN")
    

    첫 번째 필터링 .antMatcher ( "/ admin / **")를 추가하면 먼저 필터링되어 다른 멤버가 아닌 AdminFormLoginWebSecurity를 ​​사용하게됩니다.

  5. from https://stackoverflow.com/questions/22845474/spring-security-3-2-1-multiple-login-forms-with-distinct-websecurityconfigurerad by cc-by-sa and MIT license