【问题标题】:Spring Security - Differentiate between wrong username/password combination and unauthorised usersSpring Security - 区分错误的用户名/密码组合和未经授权的用户
【发布时间】:2019-06-13 22:15:37
【问题描述】:

在 Spring Boot + Spring Security 应用程序中,我编写了自己的 AuthenticationProvider。它对另一台服务器进行身份验证,该服务器保存有关用户名及其密码的信息。

到目前为止,一切正常 - 通过在安全配置中的 formLogin() 上使用 failureUrl(String),我可以告诉人们他们使用了无效的用户名/密码组合。 Spring 还会自动将未经授权的请求重定向到登录表单。

但是,用户有可能提供了有效的用户名/密码组合,但无论如何都不允许使用该应用程序。当然,这样的用户不应该收到“错误的用户名/密码”的相同反馈,因为这会让他感到困惑。在我的AuthenticationProvider 或安全配置中有什么方法可以让我以某种方式实现这两者之间的区分吗?到目前为止,我看不到任何方法,在 AuthenticationProvider 中返回 null 会使 Spring 认为这是一个“失败”,因此只是重定向到 failureUrl(String) 中指定的路径。

【问题讨论】:

    标签: java spring spring-security


    【解决方案1】:

    我建议您区分身份验证和授权。应始终对具有有效凭据的用户进行身份验证。由于缺少授权,访问可能仍会被拒绝。

    那么,如何为真正允许使用您的应用的所有用户授予角色“USER”,并将您的应用配置为针对特定请求需要此角色,如下所示:

    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()                   
            .antMatchers("/api/**").hasRole("USER")                                                            
            .anyRequest().authenticated()                                                               
            .and()
            // ...
            .httpBasic();   
    }
    

    【讨论】:

    • 通常是这样的吗?我认为它可以很好地工作 - 尽管我必须确保用户也可以再次注销。我想我会将您的答案与 jzheaux 的答案结合起来,这样我就可以使用 AccessDeniedHandler 来显示一些有关用户无法进入网站的原因的信息。
    • 认证和授权的区别?确实。并且确实使用 AccessDeniedHandler 将是要走的路。如果您打算在您的应用程序中拥有不同的访问权限,我会试一试。您会发现 Spring 以一种简洁的方式自动构建您的代码,而无需混合职责。看看 AccessDecisionVoterAccessDecisionManager
    【解决方案2】:

    Spring Security 使用两个不同的接口支持这种场景。

    AuthenticationFailureHandler

    Spring Security 使用此 API 来确定如何处理身份验证失败(例如错误的信用)。想要重定向回登录页面是很常见的,所以这是默认情况下发生的。但是,您可以使用

    对其进行自定义
    http
        .formLogin()
            .failureUrl(myUrl)
    

    http
        .formLogin()
            .failureHandler(myFailureHandler)
    

    满足更复杂的需求。

    AccessDeniedHandler

    假设用户已登录,但他们正在请求他们无权访问的受保护资源。

    默认情况下,Spring Security 返回 403,但也可以配置为拒绝访问页面:

    http
        .exceptionHandling()
            .accessDeniedPage(myAccessDeniedPage)
    

    http
        .exceptionHandling()
            .accessDeniedHandler(myAccessDeniedHandler)
    

    满足更复杂的需求。

    您的AuthenticationProvider 应该只专注于对用户进行身份验证——您可以抛出AuthenticationException 来指示失败。 UsernamePasswordAuthenticationFilter 将负责获取失败的 url,ExceptionTranslationFilter 将负责获取拒绝访问页面。

    【讨论】:

    • 啊,非常有趣 - 我明天将不得不研究 AuthenticationFailureHandler。我将不得不看看如何将发生哪些案例的信息从我的自定义 AuthenticationProdiver 传递到我的自定义 AuthenticationFailureHandler - 我快速浏览了一下,它看起来像是通过 AuthenticationProvider 中抛出的异常来工作的。明天我会更仔细地查看并报告,感谢您迄今为止的意见!
    【解决方案3】:

    您可以为用户未经授权的情况制作自定义类:

    public class UnauthorizedException extends AuthenticationException {
    
        public UnauthorizedException(String msg) {
            super(msg);
        }
    
    }
    

    如果你从 AuthenticationProvider 实现中使用它,重要的是扩展 Spring Security 的 AuthenticationException。只需像这样使用它:

    throw new UnauthorizedException("Unauthorized to use this application.");
    

    您可能想要本地化消息。

    此类异常将放在名为SPRING_SECURITY_LAST_EXCEPTION 的属性中的会话中。例如,如果您使用 Thymeleaf,您可以像这样打印消息:

    <p data-th-if="${param.error != null and session.SPRING_SECURITY_LAST_EXCEPTION != null}"
       data-th-text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}">
    </p>
    

    【讨论】:

      【解决方案4】:

      希望对你有帮助

          @RequestMapping(value = "/login")
      public String getLoginPage(@RequestParam(name = "error", required = false) String error, Model model,
              @RequestParam(name = "logout", required = false) String logout) {
          if (error != null) {
              model.addAttribute("message", "Invalid Username or Password");
          }
          if (logout != null) {
              model.addAttribute("logout", "You have Successfully Logged Out");
          }
      
          return "login";
      }
      
      @RequestMapping(value = "/logout-user")
      public String logout(HttpServletRequest request, HttpServletResponse response) {
          // fetch the authentication object
          Authentication auth = SecurityContextHolder.getContext().getAuthentication();
          if (auth != null) {
              // logout and clear out the session and remove the authentication from the
              // security context
              new SecurityContextLogoutHandler().logout(request, response, auth);
          }
          return "redirect:/login?logout";
      }
      

      Spring-安全配置

      <http>
      
          <intercept-url pattern="/**" access="permitAll" />
          <!-- navigate to login url -->
          <form-login login-page="/login" username-parameter="username"
              password-parameter="password" />
          <!-- access forbidden for unauthorized user -->
          <access-denied-handler error-page="/access-denied" />
          <csrf />
      </http>
      

      【讨论】:

      • 遗憾的是,您的回答并没有真正回答问题 - 您刚刚展示了基本授权在没有我尝试实施的特殊用例的情况下是如何工作的。
      猜你喜欢
      • 1970-01-01
      • 2021-02-02
      • 2015-10-21
      • 2013-05-18
      • 1970-01-01
      • 2022-06-16
      • 2022-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多