![]() |
| Authentication Required! |
This post has discussed a way to implement basic authentication to secure RESTful web service calls with some kind of customization. However, after researching Spring Security in depth, I found the customization solution can be even simpler.
So let's say we want to implement a customized basic authentication for a RESTful web service. the authentication only allow a request with a web referrer from certain URL or domain (or from a certain IP address). That means we need to check the HTTP Request header "Referer".
Here's the example solution. First we define spring security filter in the web.xml as shown below. It will load the spring security context definition from /WEB-INF/spring-security.xml.
[web.xml]
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-security.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>spring-restful</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring-restful</servlet-name> <url-pattern>/example/webservice/*</url-pattern> </servlet-mapping> </web-app>
The spring security context definition is shown below. It defines a <security:http> element. This enables us to secure web app URL "/example/webservice/**" with basic authentication.
[spring-security.xml]
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> <security:global-method-security pre-post-annotations="enabled"/> <security:http create-session="stateless" entry-point-ref="authenticationEntryPoint" realm="Example Webservices" pattern="/example/webservice/**"> <security:http-basic authentication-details-source-ref="myAuthDetailsSource" /> <security:intercept-url method="POST" pattern="/example/webservice/**" access="IS_AUTHENTICATED_FULLY"/> </security:http> <bean id="myAuthDetailsSource" class="com.mytechtip.spring.security.example.MyAuthenticationDetailsSource" /> <bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint"> <property name="realmName" value="Example Webservices" /> </bean> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="myAuthenticationProvider" /> </security:authentication-manager> <bean id="myAuthenticationProvider" class="com.mytechtip.spring.security.example.MyAuthenticationProvider" /> </beans>
As you can see above, we customize the basic authentication with our own source of authentication details. This custom source allows us to capture the information (such as Referer) we can authenticate against. Also we wire up a custom authentication provider that can check the custom fields. We list the the sample implementation of the two classes.
[MyAuthenticationDetailsSource.java]
package com.mytechtip.spring.security.example; import javax.servlet.http.HttpServletRequest; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; public class MyAuthenticationDetailsSource extends WebAuthenticationDetailsSource { @Override public WebAuthenticationDetails buildDetails(HttpServletRequest context) { return new MyAuthenticationDetails(context); } @SuppressWarnings("serial") class MyAuthenticationDetails extends WebAuthenticationDetails { private final String referer; public MyAuthenticationDetails(HttpServletRequest request) { super(request); this.referer = request.getHeader("Referer"); } public String getReferer() { return referer; } } }
[MyAuthenticationProvider.java]
package com.mytechtip.spring.security.example; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import com.mytechtip.spring.security.example.MyAuthenticationDetailsSource.MyAuthenticationDetails; public class MyAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // cast as it pass the support method UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; String principal = (String) auth.getPrincipal(); String credential = (String) auth.getCredentials(); Object obj = auth.getDetails(); if (obj instanceof MyAuthenticationDetails) { MyAuthenticationDetails details = (MyAuthenticationDetails) obj; // Doing the extra check such as referer if ("http://example.com/referer".equalsIgnoreCase(details.getReferer())) { // do the further check ... // return the result if passed validation UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, credential); return result; } } throw new BadCredentialsException("Bad Authentication"); } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }

you are showing web.xml as spring-security.xml :D
ReplyDelete