Tuesday, July 8, 2014

Web Service using Spring-WS with HTTP Digest Authentication

We have seen 'A Simple Web Service Using Spring-WS' in the previous post.
Now we need to secure the service from unauthorized access. We are going to apply HTTP Digest Authentication[1][2] for this.

Note that we are not implementing Message Signing or Encryption here. We will only ensure 'Authentication' using Spring XwsSecurityInterceptor[3], so that only authenticated users can access the add service we defined in the previous post.

First modify the ws-servlet.xml file and add the following bean definitions:

  • XwsSecurityInterceptor bean (inside the <sws:interceptors/> tag),
  • callBackHandlerDigest bean definition,
  • userDetailsService


ws-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:sws="http://www.springframework.org/schema/web-services"
 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.xsd
       http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd">

 <sws:annotation-driven />

 <!-- To detect @Endpoint, @Service, @Component etc -->
 <context:component-scan base-package="com.test.ws" />

 <!-- To generate dynamic wsdl -->
 <sws:dynamic-wsdl id="wstest" portTypeName="add"
  locationUri="/services/add" targetNamespace="http://develop-for-fun.blogspot.com/spring-ws">
  <sws:xsd location="/WEB-INF/spring/service-definitions.xsd" />
 </sws:dynamic-wsdl>

 <!-- For validating your request and response -->
 <!-- So that you don't send a string instead of an integer -->

 <sws:interceptors>
  <bean id="validatingInterceptor"
   class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
   <property name="schema" value="/WEB-INF/spring/service-definitions.xsd" />
   <property name="validateRequest" value="true" />
   <property name="validateResponse" value="true" />
  </bean>
  <bean id="wsSecurityInterceptor"
              class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
            <property name="policyConfiguration" value="/WEB-INF/spring/securityPolicy.xml"/>
            <property name="callbackHandlers">
                <list>
                    <ref bean="callbackHandlerDigest"/>
                </list>
            </property>
        </bean>
 </sws:interceptors>

    <bean id="callbackHandlerDigest" class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>
    <bean id="userDetailsService" class="com.test.ws.services.CustomUserDetailsService" />

</beans>

security-policy.xml

<xwss:securityconfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:requireusernametoken noncerequired="true" passworddigestrequired="true">
</xwss:requireusernametoken></xwss:securityconfiguration>

Add a Role enumeration to define User's role:

com.test.ws.enumeration.Role

package com.test.ws.enumeration;



public enum  Role {

    ROLE_ADMIN(1),

    ROLE_MEMBER(2);



    private final int value;



    Role(int p) {

        this.value = p;

    }



    public int getValue() {

        return this.value;

    }

}

Add the domain(/model/entity) class:

com.test.ws.domain.User

package com.test.ws.domain;

import com.test.ws.enumeration.Role;

/**
 * The User entity
 */

public class User {
    private int id;
    private String username;
    private String password;
    private String email;
    private String name;
    private Role role;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Now add the dao class (with dummy user population):

com.test.ws.dao.UserDao

package com.test.ws.dao;

import com.test.ws.domain.User;
import com.test.ws.enumeration.Role;

public class UserDao {

    private static final String DEFAULT_ADMIN_PASS = "adminpass";
    private static final String DEFAULT_MEMBER_PASS = "memberpass";
    private static final String DUMMY_ADMIN_EMAIL = "admin@domain.com";
    private static final String DUMMY_MEMBER_EMAIL = "member@domain.com";

    public User getUser(String email) {
        /** We are only allowing now two dummy users here
         * Actually here we have to retrieve the users by the email address from Database **/

        User user = new User();
        if(email.equals(DUMMY_ADMIN_EMAIL)) {
            user.setId(1);
            user.setName("Administrator");
            user.setPassword(DEFAULT_ADMIN_PASS);
            user.setRole(Role.ROLE_ADMIN);
        } else if(email.equals(DUMMY_MEMBER_EMAIL)) {
            user.setId(2);
            user.setName("Member");
            user.setPassword(DEFAULT_MEMBER_PASS);
            user.setRole(Role.ROLE_ADMIN);
        }
        user.setEmail(email);
        return user;
    }
}



We need to create a CustomUserDetailsService which implements UserDetailsService

com.test.ws.services.CustomUserDetailsService

package com.test.ws.services;

import com.test.ws.dao.UserDao;
import com.test.ws.domain.User;
import com.test.ws.enumeration.Role;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    UserDao userDao = new UserDao();

    @Override
    public UserDetails loadUserByUsername(String email) {
        try {
            // Retrieve the user by email
            User user = userDao.getUser(email);

            int roleId = user.getRole().getValue();
            return new org.springframework.security.core.userdetails.User (
                    user.getEmail(), user.getPassword(), true, true, true, true, getAuthorities(roleId)
            );
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    public Collection<? extends GrantedAuthority> getAuthorities(Integer role) {
        List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
        return authList;
    }

    public List<String> getRoles(Integer role) {

        List<String> roles = new ArrayList<String>();

        if (role.intValue() == Role.ROLE_ADMIN.getValue()) {
            roles.add(String.valueOf(Role.ROLE_ADMIN));
        } else if (role.intValue() == Role.ROLE_MEMBER.getValue()) {
            roles.add(String.valueOf(Role.ROLE_MEMBER));
        }
        return roles;
    }

    public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }
}

This is how the project-structure should look like (underlined items are newly added over the project in the previous post):

Project Structure



The project can be downloaded using git from spring-ws-test project (secured_user_auth_digest branch):
git clone https://github.com/tariqmnasim/spring-ws-test.git -b secured_user_auth_digest

References:
[1] HTTP Authentication: Basic and Digest Access Authentication
[2] Wikipedia: Digest access Authentication
[3] Securing your web services using Spring-WS

Friday, June 13, 2014

A Simple Web Service Using Spring-WS

In this post I will describe the way to write a very simple SOAP-based web service that will take as input two numbers and return the sum of those numbers.

Create the Project Structure:
We can start our development based on a Java-Web project using maven. [ See maven web project directory layout  and creating maven webapp project ].

Project Structure



Define Data Contract (XSD):
Spring-WS focuses on Contract-First development style [See Why]. So, we first define the data exchange (reuest-response) format of the web service, i.e. the Data Contract.

webapp/WEB-INF/spring/service-definition.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://develop-for-fun.blogspot.com/spring-ws" xmlns:wst="http://develop-for-fun.blogspot.com/spring-ws">
  
  <xs:element name="AddServiceRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="firstNumber" type="xs:integer"/>
        <xs:element name="secondNumber" type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="AddServiceResponse">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="sum" type="xs:integer"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
</xs:schema>


Generate Request/Response Models:
You can generate (un-marshal) Java classes using JAXB from the above XSD. You can use XJC for this or you can use features of your IDE to convert XML-to Java. In IntelliJ IDEA, right click on the .xsd file in the project explorer, click "Web Services" --> "Generate Java classes from XML Schema using JAXB". (for eclipse see this).
AddServiceRequest.java, AddServiceResponse.java and ObjectFactory.java will be generated in this case. Put the generated classes in a separate package (e.g. com.test.ws.wsmodels).

Then write the service interfaces and an implementation of the service interface.

AddService.java

package com.test.ws.services;

import java.math.BigInteger;

public interface AddService {
 public BigInteger addNumbers(BigInteger number1, BigInteger number2);
}

AddServiceImpl.java

package com.test.ws.services;

import java.math.BigInteger;

import org.springframework.stereotype.Service;

@Service
public class AddServiceImpl implements AddService {

 /**
  * Returns the sum of two input numbers.
  */
 public BigInteger addNumbers(BigInteger number1, BigInteger number2) {
  return number1.add(number2);
 }

}
Don't forget the @service annotation.

Now write the endpoint class.

AdditionEndpoint.java

package com.test.ws.endpoints;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

import com.test.ws.wsmodels.*;
import com.test.ws.services.AddService;

@Endpoint
public class AdditionEndpoint {
   //To calculate sum of two input.
   @Autowired
   private AddService addService;
   
   //This is similar to @RequestMapping of Spring MVC
   @PayloadRoot(localPart="AddServiceRequest", namespace="http://develop-for-fun.blogspot.com/spring-ws")
   @ResponsePayload
   public AddServiceResponse generateRandomNumber(@RequestPayload AddServiceRequest request) {
       
    AddServiceResponse response = new ObjectFactory().createAddServiceResponse();
       
    response.setSum(addService.addNumbers(request.getFirstNumber(), request.getSecondNumber()) );
       
    return response;
    
   }
}


All the java classes are ready now. We need to configure the spring servlet definition and the web.xml file.

webapp/WEB-INF/spring/ws-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
 xmlns:sws="http://www.springframework.org/schema/web-services"
 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.xsd
       http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd">

 <sws:annotation-driven />

 <!-- To detect @Service, @Endpoint etc -->
 <context:component-scan base-package="com.test.ws" />

 <!-- To generate dynamic wsdl -->
 <sws:dynamic-wsdl id="wstest" portTypeName="add"
  locationUri="/services/add" targetNamespace="http://develop-for-fun.blogspot.com/spring-ws">
  <sws:xsd location="/WEB-INF/spring/service-definitions.xsd" />
 </sws:dynamic-wsdl>

 <!-- For validating your request and response -->
 <!-- So that you don't send a string instead of an integer -->

 <sws:interceptors>
  <bean id="validatingInterceptor"
   class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
   <property name="schema" value="/WEB-INF/spring/service-definitions.xsd" />
   <property name="validateRequest" value="true" />
   <property name="validateResponse" value="true" />
  </bean>
 </sws:interceptors>


</beans>

webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>Addtion Service</display-name>

    <servlet>
        <servlet-name>ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <init-param>
            <param-name>transformWsdlLocations</param-name>
            <param-value>true</param-value>
        </init-param>
  <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                /WEB-INF/spring/ws-servlet.xml
            </param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>ws</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>

</web-app>



Now the maven configuration.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.test.ws</groupId>
 <artifactId>spring-ws-test</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>Spring-WS Application</name>
 <url>http://www.springframework.org/spring-ws</url>
 <build>
  <finalName>spring-ws-test</finalName>
  <plugins>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.5</source>
     <target>1.5</target>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>tomcat-maven-plugin</artifactId>
    <version>1.1</version>
   </plugin>
   <plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <executions>
     <execution>
      <goals>
       <goal>xjc</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <!-- This should the location of the schema files (*.xsd) for services definition -->
     <schemaDirectory>src/main/webapp/WEB-INF/spring</schemaDirectory>
    </configuration>
   </plugin>
  </plugins>
 </build>
 <dependencies>
  <dependency>
   <groupId>org.springframework.ws</groupId>
   <artifactId>spring-ws-core</artifactId>
   <version>2.0.1.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>javax.xml.bind</groupId>
   <artifactId>jaxb-api</artifactId>
   <version>2.0</version>
  </dependency>
  <dependency>
   <groupId>com.sun.xml.bind</groupId>
   <artifactId>jaxb-impl</artifactId>
   <version>2.0.3</version>
  </dependency>
  <dependency>
   <groupId>org.apache.xmlbeans</groupId>
   <artifactId>xmlbeans</artifactId>
   <version>2.4.0</version>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.8.1</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <scope>compile</scope>
   <version>1.2.16</version>
  </dependency>
 </dependencies>
</project>


Now build the file using the maven command:
mvn clean package

Deploy the spring-ws-test.war file in your server (tomcat / glassfish / jboss). If you deploy the application in your localhost, then you can see the wsdl on your browser in the following URL:
http://localhost:8080/spring-ws-test/services/wstest.wsdl

The service can be tested using Soap-UI or other SOAP client.

You can get the whole project from github using git:
git clone https://github.com/tariqmnasim/spring-ws-test.git


References:
JAXB:

Saturday, May 31, 2014

Automatically deploy applications (WAR files) during JBoss AS starting time.

Add a <deployment/> part under <deployments/> tag in the standalone.xml file (<JBoss Installation Directory>/standalone/standalone.xml) to automatically deploy applications (WAR files) during JBoss AS starting time:
<server>
 <!-- ... -->
 <!-- ... -->
 <deployments>
        <deployment name="my.war" runtime-name="my.war">
            <fs-archive path="Path/to/my.war">
        </fs-archive></deployment>
  <deployment name="other.war" runtime-name="other.war">
            <fs-archive path="Path/to/some/other.war">
        </fs-archive></deployment>
    </deployments>
 <!-- ... -->
 <!-- ... -->
</server>

Now, each time you start JBoss AS, application server will try to deploy the wars specified in the standalone.xml file.

Sunday, May 25, 2014

Get first/last date of current Week/Month

import java.util.Calendar;
import java.util.Date;
/**
 * Returns the date of previous Monday
 */
public static Date getFirstDateOfCurrentWeek() {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);
    cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
    return cal.getTime();
}
/**
 * Returns the date of next Sunday.
 */
public static Date getLastDateOfCurrentWeek() {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);
    cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);

    Calendar last = (Calendar) cal.clone();
    last.add(Calendar.DAY_OF_YEAR, 7);
    last.add(Calendar.MILLISECOND, -1);
    return last.getTime();
}
/**
 * Get the first Date ( first millisecond ) of current month
 */
public static Date getFirstDateOfCurrentMonth() {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    return cal.getTime();
}
/**
 * Get the last time ( last millisecond ) of current month
 */
public static Date getLastDateOfCurrentMonth() {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.HOUR_OF_DAY, 0);
    cal.clear(Calendar.MINUTE);
    cal.clear(Calendar.SECOND);
    cal.clear(Calendar.MILLISECOND);
    cal.add(Calendar.MONTH, 1); // NEXT MONTH
    cal.set(Calendar.DAY_OF_MONTH, 1); // FIRST DAY OF NEXT MONTH
    cal.add(Calendar.MILLISECOND, -1); // Go one Millisecond back, which is the last moment of the last day of current month
    return cal.getTime();
}

Wednesday, May 21, 2014

JBoss Logging Configuration: Write Application Specific Logs in a Different File

In order to write the logs generated from your application into a separate file (not in the default <j-boss-installation-directory>\...\server.log file), follow these steps:

Start JBoss Administrative Console: In your browser hit http://localhost:9990/console/App.html

Configure A File Handler:

  • Go to Profile --> Core --> Logging --> 'Handler' tab --> 'File' sub-tab and Click 'Add' button.
  • In the 'Add File Handlers' pop-up box, enter name, log-level, file-name with extension etc. and 'Save'
  • You may also need to further edit the created handler and set 'Auto Flash' and 'Append' to true.

Add A Log Category:

  • Now go to the 'Log Categories' tab and click 'Add'. Enter the root package of your project in the 'Name' field (e.g. com.mycompany.myproject).
  • Now select (click on) the newly added log-category and click on 'handlers' (under 'Details' section at the bottom).
  • Click 'Add' and add the handler that you created previously.

[You can also configure the handler and Log Category by editing the .../standalone/configuration/standalone.xml file of your server directory.]

Happy Logging:
Now you can log using slf4j from your application like:
private static Logger logger = LoggerFactory.getLogger(MyClass.class);

and all logs of your application will be written to the file-name that you specified for the handler configured above.

I have used EAP 6.2.0 GA (AS 7.3) application server and slf4j (for logging).

Related Links:
JBoss: Configure Logging
slf4j Manual.