REST api 的 Spring 安全性在身份验证失败时显示浏览器弹出窗口

Posted

技术标签:

【中文标题】REST api 的 Spring 安全性在身份验证失败时显示浏览器弹出窗口【英文标题】:Spring security for REST api shows the browser popup when auth fails 【发布时间】:2016-01-11 18:41:34 【问题描述】:

我有一个使用 Facebook 的 React 框架开发的单页应用程序,我使用这部分 JSX 来确定用户是否已登录:

render: function() 
    return <div>
      <BrandBar/>
      <div className="container">
        <TopBar/>
        this.state.sessionState == 'LOADING' ? (<h1>Loading</h1>) : (this.state.sessionState == 'LOGGEDIN' ? this.props.children : <Login/>)
      </div>
    </div>

更改此组件状态的登录函数是一个简单的 REST 调用(使用超级代理),它获取附加了基本身份验证凭据的用户信息:

login: function(username, password)
    return 
      then: function(callback)
        request 
          .get(rootUrl + 'me')
          .auth(username, password)
          .end((err, res) => 
            callback(res);
          );
      
   

在后端,我在 Spring Boot 中有一个 REST api:

控制器:

@RequestMapping(value = "/me",
        produces = APPLICATION_JSON,
        method = RequestMethod.GET)
public User getMe() 
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if (principal != null && principal instanceof CurrentUser)
        return ((CurrentUser) principal).getUser();
    

    return null;


@RequestMapping("/stop")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void stop(HttpSession session) 
    session.invalidate();

安全配置:

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception 

        http.csrf().disable()
                .httpBasic().and()
                .authorizeRequests().anyRequest().authenticated()
                .and().anonymous().disable()
                .exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint()
            @Override
            public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException 
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
            
        );

    

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    


只要我提供的信息正确,登录就可以正常工作,但是当我提供错误的凭据时,服务器会绕过返回普通 401(没有 WWW-Authenticate 标头)的异常处理。因此,尽管BasicAuthenticationEntryPoint 被覆盖,浏览器仍会向我显示弹出窗口。

更奇怪的是,当我在浏览器弹出窗口中提供正确的详细信息时,它仍然返回 401 状态。

这似乎是一个基本设置:一个带有 Spring Security 的简单 REST 服务和一个前端的单页应用程序。有没有人遇到过类似的问题?

【问题讨论】:

尝试创建一个单独的登录页面,它更容易实现:它将包含您的 组件并且应该是打开的,即 permitAll()。应用页面应该是“authenticated()”。使用这种方法,您的 this.state.sessionState == 'LOGGEDIN' 逻辑变得多余。 我之前使用过这种方法,但我觉得这样可以使代码更简洁,因为我不需要实现一种机制来跟踪用户试图访问的 url。如果用户想去domain/task/id 并且需要先登录,使用这种方法它会自动工作:首先显示登录屏幕,然后是目标页面。我的问题完全在于身份验证请求。 好吧,也许我不清楚你的目标,但我宁愿专注于保留目标 url 的问题,spring security 允许你这样做。见this。而且,顺便说一句,在客户端保留身份验证逻辑会给您一种“假”授权,因为您可以通过浏览器中的 js 调试器对自己进行身份验证。干杯! 你使用的是什么版本的 Spring Security / Spring Boot? 【参考方案1】:

您是否已经尝试注册拒绝访问处理程序?

http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandlerImpl());

public class AccessDeniedHandlerImpl implements AccessDeniedHandler 

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse     response, AccessDeniedException accessDeniedException) throws IOException, ServletException 
        response.sendError(HttpStatus.UNAUTHORIZED.value());
    

就像一个测试当您在安全上下文(控制器内的代码)中找不到主体时尝试抛出 AuthenticationException 以查看过滤器链中的某个位置是否处理了异常,应该是的。

【讨论】:

这个没试过,今晚试试。目前,我只有 exceptionHandling().authenticationEntryPoint()【参考方案2】:

如果您不想弹出浏览器,最好避免使用401

对于 AJAX,我建议使用 403 作为身份验证入口点。

response.sendError(HttpServletResponse.SC_FORBIDDEN)

如果您需要区分“真实”禁止(当用户通过身份验证但不允许访问)和以上,您可以在响应正文中或作为自定义标头传递信息。

【讨论】:

以上是关于REST api 的 Spring 安全性在身份验证失败时显示浏览器弹出窗口的主要内容,如果未能解决你的问题,请参考以下文章

用于非授权连接的 Spring Security REST Api

如何在 Spring Boot Rest api 中创建安全登录控制器

从移动设备登录 Spring Security、Rest api 和 Facebook

在 Spring 安全性中使用 rest 服务进行身份验证和授权 [关闭]

使用 Django rest 框架在两个 api 之间配置身份验证的最佳方法是啥?

用于重定向到 REST api 中的身份验证的适当 HTTP 状态