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:
Add a Role enumeration to define User's role:
Add the domain(/model/entity) class:
Now add the dao class (with dummy user population):
com.test.ws.dao.UserDao
We need to create a CustomUserDetailsService which implements UserDetailsService
This is how the project-structure should look like (underlined items are newly added over the project in the previous post):
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
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;
}
}
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):
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