在 Spring Security 中处理基本身份验证的未经授权的错误消息

Posted

技术标签:

【中文标题】在 Spring Security 中处理基本身份验证的未经授权的错误消息【英文标题】:Handle unauthorized error message for Basic Authentication in Spring Security 【发布时间】:2011-05-22 18:37:04 【问题描述】:

我正在尝试在我的 Web 应用程序中使用 Spring Security 3.0.5。基本上,我想要一个通过HTTP GET返回json格式数据的网络服务。

我已经实现了一个 RESTful 服务,它在请求 url http://localhost:8080/webapp/json 时返回数据。这适用于以下 curl 命令

> curl http://localhost:8080/webapp/json
"key":"values"

使用spring security添加基本身份验证后,我可以使用以下命令获取数据

> curl  http://localhost:8080/webapp/json
<html><head><title>Apache Tomcat/6.0.29 - Error report .....
> curl -u username:password http://localhost:8080/webapp/json
"key":"values"

前一个命令返回标准的 tomcat 错误页面,因为现在它需要用户名和密码。 我的问题是是否有可能以打印出我自己的错误消息的方式处理拒绝访问?

> curl  http://localhost:8080/webapp/json
"error":"401", "message":"Username and password required"

这是我的 spring 安全配置和AccessDeniedHandler。如您所见,我正在尝试添加 access-denied-handler,它只是通过 servlet 响应打印出一个字符串,但它仍然没有在命令行上打印我自己的消息。

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
          http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
          http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
    <global-method-security secured-annotations="enabled"/>

    <beans:bean name="access-denied" class="webapp.error.JSONAccessDeniedHandler"></beans:bean>

    <http auto-config="true">
        <access-denied-handler ref="access-denied"/>
        <intercept-url pattern="/json" access="ROLE_USER,ROLE_ADMIN"  />
        <http-basic />
    </http>

    <authentication-manager>
        <authentication-provider>
            <password-encoder hash="md5"/>
            <user-service>
            ...
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>

AccessDeniedHandler.java

package webapp.error;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

public class JSONAccessDeniedHandler implements AccessDeniedHandler  

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException 
        PrintWriter writer = response.getWriter();
        writer.print("\"error\":\"401\", \"message\":\"Username and password required\"");
    


【问题讨论】:

你能检查 curl -S 或 curl -v 的输出吗? 我已经修改了我的问题。它没有打印任何东西,但它没有打印我想要的东西。 curl -S 和 curl -v 都打印标准 tomcat 401 错误页面。 -v 有更多的请求/响应细节 【参考方案1】:

我已经解决了我的问题,所以我想我应该在这里分享它。此配置允许服务器根据请求软件发送不同的错误消息。如果请求来自 Web 浏览器,它将检查 User-Agent 标头并在必要时重定向到表单登录。如果请求来自,例如curl,认证失败时会打印出纯文本错误信息。

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:sec="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <!-- AspectJ pointcut expression that locates our "post" method and applies security that way
    <protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>-->
    <sec:global-method-security secured-annotations="enabled"/>

    <bean id="basicAuthenticationFilter"
          class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"
          p:authenticationManager-ref="authenticationManager"
          p:authenticationEntryPoint-ref="basicAuthenticationEntryPoint" />

    <bean id="basicAuthenticationEntryPoint"
          class="webapp.PlainTextBasicAuthenticationEntryPoint"
          p:realmName="myWebapp"/>

    <bean id="formAuthenticationEntryPoint"
          class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"
          p:loginFormUrl="/login.jsp"/>

    <bean id="daep" class="org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint">
        <constructor-arg>
            <map>
                <entry key="hasHeader('User-Agent','Mozilla') or hasHeader('User-Agent','Opera') or hasHeader('User-Agent','Explorer')" value-ref="formAuthenticationEntryPoint" />
            </map>
        </constructor-arg>
        <property name="defaultEntryPoint" ref="basicAuthenticationEntryPoint"/>
    </bean>

    <sec:http entry-point-ref="daep">
        <sec:intercept-url pattern="/login.jsp*" filters="none"/>
        <sec:intercept-url pattern="/json" access="ROLE_USER,ROLE_ADMIN"  />
        <sec:intercept-url pattern="/json/*" access="ROLE_USER,ROLE_ADMIN"  />
        <sec:logout
            logout-url="/logout"
            logout-success-url="/home.jsp"/>
        <sec:form-login
            login-page="/login.jsp"
            login-processing-url="/login"
            authentication-failure-url="/login.jsp?login_error=1" default-target-url="/home.jsp"/>
        <sec:custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthenticationFilter" />
    </sec:http>

    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider>
        ...
        </sec:authentication-provider>
    </sec:authentication-manager>

</beans>

PlainTextBasicAuthenticationEntryPoint 通过扩展org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

public class PlainTextBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint 

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException 
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + getRealmName() + "\"");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        writer.println("HTTP Status " + HttpServletResponse.SC_UNAUTHORIZED + " - " + authException.getMessage());
    

【讨论】:

【参考方案2】:

我想知道它是否会进入您的 AccessDeniedHandler。错误是因为身份验证还是授权?两种情况都会调用 AccessDeniedHandeler 吗?我现在正在自己解决这个问题。

【讨论】:

你是对的。它对 AccessDeniedHandler 不起作用,因为它仍处于身份验证或授权过程中。 我已经解决了这个问题。 Spring Security 将其放在上面并将其交还给您的应用程序在 web.xml 中定义的 401 页面。好吧,如果你想捕获它,你必须编写自己的 AuthenticationProcessFilter 并在那里处理内容类型和错误响应。我注意到有些人只是让 HTTP 状态出现错误消息,但我不喜欢这样,最终会编写一个 CustomAuthenticationProcessingFilter 来查看原始请求扩展或 Accept Header 并返回适当的响应。跨度> 我的解决方法略有不同,但它相当强大,允许我同时进行表单登录。我已经在下面发布了。如果你分享你的代码,它可能会很有帮助

以上是关于在 Spring Security 中处理基本身份验证的未经授权的错误消息的主要内容,如果未能解决你的问题,请参考以下文章

在corda中使用spring security的基本身份验证

如何在同一应用程序中使用 spring-sample 示例配置 Spring Security 基本身份验证和 SAML 身份验证

Spring Security 中的基本身份验证(身份验证失败消息)

在 Spring Security 中接收令牌的基本身份验证

在 Spring Security(spring-boot 项目)中使用 ldap 凭据进行 Http 基本身份验证以保护休息服务调用

Angular 基本身份验证 Spring Boot