本文介绍了Spring Security 3.2.1 具有不同 WebSecurityConfigurerAdapters 的多个登录表单的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 Spring Security 3.2.1.RELEASE 和 Spring MVC 4.0.4.RELEASE

I'm using Spring Security 3.2.1.RELEASE with Spring MVC 4.0.4.RELEASE

我正在尝试为具有两个不同登录入口页面的 Web 应用程序设置 Spring Security.我需要不同的页面,因为它们的样式和访问方式不同.

I'm trying to setup Spring Security for a web application that will have two distinct login entry pages. I need the pages to be distinct as they will be styled and accessed differently.

第一个登录页面是为管理员用户和保护管理页面/admin/**

First login page is for Admin users and protects admin pages /admin/**

第二个登录页面供客户用户使用,并保护客户页面/customer/**.

Second login page is for Customer users and protects customer pages /customer/**.

我尝试设置 WebSecurityConfigurerAdapter 的两个子类来配置单个 HttpSecurity 对象.

I've attempted to setup two subclasses of WebSecurityConfigurerAdapter configuring individual HttpSecurity objects.

CustomerFormLoginWebSecurity 正在保护客户页面并在未经授权的情况下重定向到客户登录页面.AdminFormLoginWebSecurity 保护管理页面在未经授权的情况下重定向到管理登录页面.

CustomerFormLoginWebSecurity is protecting customer pages and redirecting to customer login page if not authorised. The AdminFormLoginWebSecurity is protecting admin pages redirecting to admin login page if not authorised.

不幸的是,似乎只有第一个配置被强制执行.我认为我缺少一些额外的东西来使这两者都起作用.

Unfortunately it seems that only the first of the configurations is enforced. I think that I am missing something extra to make these both work.

@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");
        }
    }

}

推荐答案

我为多个登录页面提出的解决方案涉及单个 http 身份验证,但我提供了自己的

The solution that I have come to for multiple login pages involves a single http authentication but I provide my own implementations of

  • AuthenticationEntryPoint
  • AuthenticationFailureHandler
  • LogoutSuccessHandler

我需要的是这些实现能够根据请求路径中的令牌进行切换.

What I needed was for these implementations to be able to switch dependent on a token in the request path.

在我的网站中,url 中带有客户令牌的页面受到保护,并要求用户在 customer_signin 页面上以 CUSTOMER 身份进行身份验证.因此,如果想转到/customer/home 页面,那么我需要首先重定向到 customer_signin 页面进行身份验证.如果我无法在 customer_signin 上进行身份验证,那么我应该返回到 customer_signin 并带有错误参数.以便可以显示消息.
当我成功通过 CUSTOMER 身份验证并希望退出时,LogoutSuccessHandler 应该会将我带回 customer_signin 页面.

In my website the pages with a customer token in the url are protected and require a user to authenticate as CUSTOMER at the customer_signin page.So if wanted to goto a page /customer/home then I need to be redirected to the customer_signin page to authenticate first.If I fail to authenticate on customer_signin then I should be returned to the customer_signin with an error paramater. So that a message can be displayed.
When I am successfully authenticated as a CUSTOMER and then wish to logout then the LogoutSuccessHandler should take me back to the customer_signin page.

我对管理员有类似的要求,需要在 admin_signin 页面进行身份验证才能访问 url 中带有管理员令牌的页面.

I have a similar requirement for admins needing to authenticate at the admin_signin page to access a page with an admin token in the url.

首先,我定义了一个类,它允许我获取令牌列表(每种类型的登录页面一个)

First I defined a class that would allow me to take a list of tokens (one for each type of login page)

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.

I then use this in PathLoginAuthenticationEntryPoint to change the login url depending on the token in the request uri.

@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 以便您可以用通常的方式处理它.

PathTokenNotFoundException extends AuthenticationException so that you can handle it in the usual way.

public class PathTokenNotFoundException extends AuthenticationException {

   public PathTokenNotFoundException(String msg) {
       super(msg);
    }

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

接下来我提供了一个 AuthenticationFailureHandler 的实现,它查看请求标头中的引用 URL,以确定将用户定向到哪个登录错误页面.

Next I provide an implementation of AuthenticationFailureHandler that looks at the referer url in the request header to determine which login error page to direct the user to.

@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");
    }
}

接下来,我提供了一个 LogoutSuccessHandler 的实现,它将根据请求标头中的引用 URL 中的令牌注销用户并将他们重定向到正确的登录页面.

Next I provide an implementation of LogoutSuccessHandler that will logout the user and redirect them to the correct signin page depending on the token in ther referer url in the request header.

@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.");
    } 
}

最后一步是在安全配置中将它们连接在一起.

The final step is to wire them all together in the security configuration.

@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 中是否存在指示登录错误的值,然后设置用于控制错误消息的属性.

Once you have this configured you need a controller to to direct to the actual signin page. The SigninControiller below checks the queryString for a value that would indicate a signin error and then sets an attribute used to control an error message.

@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";
    }
}

这篇关于Spring Security 3.2.1 具有不同 WebSecurityConfigurerAdapters 的多个登录表单的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-22 11:37