Page 1 of 1

HTTP Basic authentication to REST API

PostPosted: Sun Mar 07, 2010 1:28 pm
by hovenko
Supplying username and password on the URL is considered a security risk, since URLs are logged on webservers and possible proxy servers across the Internet (for example your company proxy/firewall).

I made a patch to Subsonic 3.9 that will allow HTTP Basic Authentication to the REST web services, so you don't have to use the "p" and "u" parameters.

The patch also makes it possible to run the application in jetty, when developing and testing. To run jetty, execute this from the subsonic-main directory:
Code: Select all
mvn jetty:run -Dsubsonic.home=/tmp/subsonic-test


Code: Select all
diff --git a/subsonic-main/pom.xml b/subsonic-main/pom.xml                                                                   
index 5e2eccf..e7e97e7 100644                                                                                               
--- a/subsonic-main/pom.xml                                                                                                 
+++ b/subsonic-main/pom.xml                                                                                                 
@@ -275,6 +275,26 @@                                                                                                         
             <scope>test</scope>                                                                                             
         </dependency>                                                                                                       
                                                                                                                             
+        <dependency>                                                                                                       
+            <groupId>org.springframework</groupId>                                                                         
+            <artifactId>spring-test</artifactId>                                                                           
+            <version>2.5.6</version>                                                                                       
+            <scope>test</scope>                                                                                             
+        </dependency>                                                                                                       
+                                                                                                                           
+        <dependency>                                                                                                       
+            <groupId>org.mortbay.jetty</groupId>                                                                           
+            <artifactId>jetty</artifactId>                                                                                 
+            <version>6.1.5</version>                                                                                       
+            <scope>test</scope>                                                                                             
+            <exclusions>                                                                                                   
+                <exclusion>                                                                                                 
+                    <groupId>org.mortbay.jetty</groupId>                                                                   
+                    <artifactId>servlet-api</artifactId>                                                                   
+                </exclusion>                                                                                               
+            </exclusions>                                                                                                   
+        </dependency>                                                                                                       
+                                                                                                                           
     </dependencies>                                                                                                         
                                                                                                                             
     <profiles>                                                                                                             
@@ -329,6 +349,20 @@                                                                                                         
                 </executions>                                                                                               
             </plugin>                                                                                                       
                                                                                                                             
+            <plugin>                                                                                                       
+                <groupId>org.mortbay.jetty</groupId>                                                                       
+                <artifactId>maven-jetty-plugin</artifactId>                                                                 
+                <version>6.1.16</version>                                                                                   
+                <configuration>                                                                                             
+                    <connectors>                                                                                           
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">                           
+                            <port>8181</port>                                                                               
+                            <maxIdleTime>6000</maxIdleTime>                                                                 
+                        </connector>                                                                                       
+                    </connectors>                                                                                           
+                </configuration>                                                                                           
+            </plugin>                                                                                                       
+                                                                                                                           
         </plugins>                                                                                                         
     </build>                                                                                                               
</project>                                                                                                                 
diff --git a/subsonic-main/src/main/java/net/sourceforge/subsonic/security/RESTRequestParameterProcessingFilter.java b/subsonic-main/src/main/java/net/sourceforge/subsonic/security/RESTRequestParameterProcessingFilter.java                                                                                                                                         
index 659d121..9f23090 100644                                                                                                                                                       
--- a/subsonic-main/src/main/java/net/sourceforge/subsonic/security/RESTRequestParameterProcessingFilter.java                                                                       
+++ b/subsonic-main/src/main/java/net/sourceforge/subsonic/security/RESTRequestParameterProcessingFilter.java                                                                       
@@ -75,6 +75,8 @@ public class RESTRequestParameterProcessingFilter implements Filter {                                                                                             
         HttpServletRequest httpRequest = (HttpServletRequest) request;                                                                                                             
         HttpServletResponse httpResponse = (HttpServletResponse) response;                                                                                                         
                                                                                                                                                                                   
+        Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication();                                                                                     
+                                                                                                                                                                                   
         String username = StringUtils.trimToNull(httpRequest.getParameter("u"));                                                                                                   
         String password = decrypt(StringUtils.trimToNull(httpRequest.getParameter("p")));                                                                                         
         String version = StringUtils.trimToNull(httpRequest.getParameter("v"));                                                                                                   
@@ -82,7 +84,25 @@ public class RESTRequestParameterProcessingFilter implements Filter {                                                                                           
                                                                                                                                                                                   
         RESTController.ErrorCode errorCode = null;                                                                                                                                 
                                                                                                                                                                                   
-        if (username == null || password == null || version == null || client == null) {                                                                                           
+        if (previousAuth == null) {                                                                                                                                               
+            /*                                                                                                                                                                     
+             * The username and password parameters are not required if the user                                                                                                   
+             * was previously authenticated, for example using Basic Auth                                                                                                         
+             */                                                                                                                                                                   
+            if (username == null || password == null) {                                                                                                                           
+                errorCode = RESTController.ErrorCode.MISSING_PARAMETER;                                                                                                           
+            }                                                                                                                                                                     
+        } else {                                                                                                                                                                   
+            if (username != null || password != null) {                                                                                                                           
+                LOG.warn("Username and password provided in URL params, "                                                                                                         
+                    + "but discarded. User already authenticated as "                                                                                                             
+                    + previousAuth.getName());                                                                                                                                     
+            }                                                                                                                                                                     
+                                                                                                                                                                                   
+            username = previousAuth.getName();                                                                                                                                     
+        }                                                                                                                                                                         
+
+        if (version == null || client == null) {
             errorCode = RESTController.ErrorCode.MISSING_PARAMETER;
         }

@@ -90,7 +110,9 @@ public class RESTRequestParameterProcessingFilter implements Filter {
             errorCode = checkAPIVersion(version);
         }

-        if (errorCode == null) {
+        if (previousAuth != null) {
+            // Already authenticated
+        } else if (errorCode == null) {
             errorCode = authenticate(username, password);
         }

@@ -202,4 +224,4 @@ public class RESTRequestParameterProcessingFilter implements Filter {
     public void setSettingsService(SettingsService settingsService) {
         this.settingsService = settingsService;
     }
-}
\ No newline at end of file
+}
diff --git a/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml
index 460fa3a..560be60 100644
--- a/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml
+++ b/subsonic-main/src/main/webapp/WEB-INF/applicationContext-security.xml
@@ -11,7 +11,7 @@
                 /wap**=httpSessionContextIntegrationFilter,logoutFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
                 /podcastReceiver**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
                 /podcast**=httpSessionContextIntegrationFilter,logoutFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
-                /rest/**=httpSessionContextIntegrationFilter,logoutFilter,restRequestParameterProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
+                /rest/**=httpSessionContextIntegrationFilter,logoutFilter,basicProcessingFilter,restRequestParameterProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,basicExceptionTranslationFilter,filterInvocationInterceptor
                 /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
             </value>
         </property>

PostPosted: Mon Mar 08, 2010 8:36 am
by sindre_mehus
Very nice!

I'll review your changes and include them in the 4.0 release.

Have you by any chance tested if this works also when redirecting from a xxx.subsonic.org address?

Thanks a lot for your contribution :-)
Sindre

PostPosted: Mon Mar 08, 2010 2:58 pm
by hovenko
sindre_mehus wrote:Have you by any chance tested if this works also when redirecting from a xxx.subsonic.org address?


No, I havn't, sorry.

How is the xxx.subsonic.org address working? Is it just a HTTP redirect or a proxy?

In case it's a redirect, I guess it's mostly up to the HTTP/REST client to pass the correct authentication header on to the site after redirection.

PostPosted: Mon Mar 08, 2010 5:51 pm
by sindre_mehus
It's a http redirect, so I guess it should work. I'll test it with the Android client once I get round to it.