Handle unauthorized error message for Basic Authentication in Spring Security
I have solved my problem so I think I should share it here. This configuration allows server to send out error message differently depending on the requesting software. If the request comes from a web browser, it will check the User-Agent
header and redirect to a form login if necessary. If the request comes from, for example, curl
, it will print out plain text error message when the authentication fails.
<?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
by extending 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());
}
}
gigadot
This is blank.
Updated on July 12, 2022Comments
-
gigadot almost 2 years
I am trying to use Spring Security 3.0.5 in my web application. Basically, I want to have a web service which return data in json format via
HTTP GET
.I have implemented a RESTful service which returns data when the url
http://localhost:8080/webapp/json
is requested. This works fine with the following curl command> curl http://localhost:8080/webapp/json {"key":"values"}
After I added basic authentication using spring security, I can use following commands to get the data
> 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"}
The former command returns standard tomcat error page since now it requires username and password. My question is whether it is possible to handle access denied in such a way that it prints out my own error message? i.e.
> curl http://localhost:8080/webapp/json {"error":"401", "message":"Username and password required"}
Here is my spring security configuration and
AccessDeniedHandler
. As you can see, I am trying to addaccess-denied-handler
which simply prints out a string through servlet response but it still does not print my own message on command line.<?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\"}"); } }