In my previous blog, I had some sample code to explain the implementation of a RESTfull Authorization Service, using Spring Security and OAuth2. In this blog I will explain how to use your LDAP service to authenticate user login. For easier understanding, I will use the same code, but will high-light in RED, the portions need to be touched/modified for LDAP Integration.
Please note that, the LDAP properties configuration may be different, specific to your organization.
Here are my Gradle build dependency entries. If you are using Maven you can choose the jar artifact versions accordingly.
project.ext {
springversion = '4.0.3.RELEASE'
jerseyversion = '1.8' }
dependencies {
providedCompile "javax.servlet:servlet-api:2.5"
compile "com.google.code.gson:gson:1.7.2"
//Jersey
compile group: 'com.sun.jersey', name: 'jersey-server', version: jerseyversion
compile group: 'com.sun.jersey', name: 'jersey-json', version: jerseyversion compile("com.sun.jersey.contribs:jersey-spring:1.8") {
exclude module: 'spring-beans'
exclude module: 'spring-core'
exclude module: 'spring-web'
exclude module: 'spring-context' }
//Spring
compile group: 'org.springframework', name: 'spring-web', version: springversion
compile group: 'org.springframework', name: 'spring-context', version: springversion
compile group: 'org.springframework', name: 'spring-core', version: springversion
compile group: 'org.springframework', name: 'spring-webmvc', version: springversion
compile group: 'org.springframework', name: 'spring-beans', version: springversion
//OAuth2 and Spring Security
compile group: 'org.springframework.security.oauth', name: 'spring-security-oauth2', version:'2.0.3.RELEASE'
compile group: 'org.springframework.security', name: 'spring-security-core', version:'3.2.4.RELEASE'
compile group: 'org.springframework.security', name: 'spring-security-ldap', version:'3.2.4.RELEASE'
}
Web.xml
<web-app id="WebApp_ID" version="2.4"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">
<display-name>OAUTH2 Authorization Services</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.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>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring-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:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:property-placeholder location="classpath:spring-context.properties"/>
<context:component-scan base-package="com.test” />
<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> <property name="url" value="${ldap.url}"/>
<property name="base" value="${ldap.base}"/>
<property name="userDn" value="${ldap.userdn}"/>
<property name="password" value="${ldap.password}"/>
<property name="pooled" value="${ldap.pooled}"/>
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="contextSource"/>
</bean>
<bean id="authManager" class="com.test.security.AuthenticationManager">
<property name="ldapTemplate" ref="ldapTemplate"/>
</bean>
<!-- For requesting and accessing token -->
<http pattern="/oauth/token" create-session="stateless"
authentication-manager-ref="authenticationManager"
xmlns="http://www.springframework.org/schema/security" >
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
<anonymous enabled="false" />
<http-basic entry-point-ref="clientAuthenticationEntryPoint" />
<custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" /> <access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<!-- For accessing protected resources after receiving token -->
<http pattern="/api/**" create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/api/**" method="GET" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<!-- For logout -->
<http pattern="/logout" create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/logout" method="GET" />
<sec:logout invalidate-session="true" logout-url="/logout" success-handler-ref="logoutSuccessHandler" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<bean id="logoutSuccessHandler" class="com.lb.security.service.LogoutImpl" >
<property name="tokenstore" ref="tokenStore">
</property>
</bean>
<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> </bean>
<bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> <property name="realmName" value="springsec/client" />
<property name="typeName" value="Basic" />
</bean>
<bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"> </bean>
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"> <property name="authenticationManager" ref="authenticationManager" />
</bean>
<authentication-manager alias="authenticationManager" xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="clientDetailsUserService" />
</authentication-manager>
<bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"> <constructor-arg ref="clientDetails" />
</bean>
<bean id="clientDetails" class="com.test.security.service.ClientDetailsServiceImpl"/>
<authentication-manager id="userAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
<authentication-provider ref="customUserAuthenticationProvider">
</authentication-provider>
</authentication-manager>
<bean id="customUserAuthenticationProvider" class="com.test.security.service.CustomUserAuthenticationProvider">
</bean>
<oauth:authorization-server
client-details-service-ref="clientDetails"
token-services-ref="tokenServices">
<oauth:authorization-code />
<oauth:implicit/>
<oauth:refresh-token/>
<oauth:client-credentials />
<oauth:password authentication-manager-ref="userAuthenticationManager"/> </oauth:authorization-server>
<oauth:resource-server id="resourceServerFilter"
resource-id="springsec" token-services-ref="tokenServices" />
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="supportRefreshToken" value="true" />
<property name="accessTokenValiditySeconds" value="1500"></property>
<property name="clientDetailsService" ref="clientDetails" />
</bean>
<mvc:annotation-driven />
<mvc:default-servlet-handler />
</beans>
spring-context.properties
# LDAP context source properties
ldap.url=ldap://<your-ldap-uri-here>:389/
#The ldap.base may be specific to your organization. Please confirm to your Organization Settings
#please check for additional entries required for you (should be used as a comma separated string)
ldap.base=DC=com
ldap.userdn=<your-ldap—domain>\\username
ldap.password=password ldap.pooled=true
ClientDetailsServiceImpl.java
package com.test.security.service;import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.NoSuchClientException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service; import java.util.ArrayList;
import java.util.List;
@Service
public class ClientDetailsServiceImpl implements ClientDetailsService {
public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception {
if (clientId.equals(“myClient”)) {
List<String> authorizedGrantTypes=new ArrayList(); authorizedGrantTypes.add("password");
authorizedGrantTypes.add("refresh_token"); authorizedGrantTypes.add("client_credentials");
BaseClientDetails clientDetails = new BaseClientDetails(); clientDetails.setClientId("myClient");
clientDetails.setClientSecret("secret1”); clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);
List<String> scopes = new ArrayList<>();
scopes.add("read");
scopes.add("write");
clientDetails.setScope(scopes);
return clientDetails;
} else{
throw new NoSuchClientException("No client FOUND ... with requested id: " + clientId); }
}
}
CustomUserAuthenticationProvider.java
package com.test.security.service;
import com.test.models.UserDetails;
import com.test.security.AuthenticationManager;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.security.core.GrantedAuthority;
import javax.naming.NamingException;
import java.util.ArrayList; import java.util.List;
public class CustomUserAuthenticationProvider implements AuthenticationProvider {
@Autowired
private AuthenticationManager authManager;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(isValidLADPUser(authentication.getPrincipal(), authentication.getCredentials())) {
List<GrantedAuthority> grantedAuthorities = new ArrayList();
UsernamePasswordAuthenticationToken auth=new
UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(),grantedAuthorities);
return auth;
}else{
throw new BadCredentialsException("Bad User Credentials PROVIDED....");
}
}
public boolean supports(Class<?> arg0) {
// TODO Auto-generated method stub
return true;
}
//LDAP Authentication goes here
public boolean isValidLADPUser(Object username, Object password){
UserDetails userDetails = null;
if(null != username && null != password){
try {
String fullusername = username.toString();
userDetails = authManager.login(!fullusername.contains("\\") ?
"<your-ldap-domain>\\".concat(fullusername):
fullusername, password.toString());
} catch (NamingException e) {
return false;
}
}
return (null != userDetails);
}
}
AuthenticationManager.java
package com.test.security;import com.test.models.UserDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.support.LdapUtils;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import java.util.ArrayList; import java.util.List;
public class AuthenticationManager {
private String authenticationErrorMessage;
@Autowired
private LdapTemplate ldapTemplate;
@Autowired
private UserDetails userDetails;
public UserDetails login(String userName, String password) throws NamingException {
// If User is not a part of user groups this message is displayed.
authenticationErrorMessage = “Invalid User Group”;
List<?> lstUser = getUsers(userName, password);
if (lstUser != null && lstUser.size() == 1) {
userDetails = (UserDetails) lstUser.get(0);
return userDetails;
}
return null;
}
public List<UserDetails> getUsers(String userName, String password) throws NamingException {
DirContext ctx;
try {
ctx = ldapTemplate.getContextSource().getContext(userName, password);
} catch (org.springframework.ldap.NamingException ne) {
authenticationErrorMessage = “Invalid User”;
return null;
}
List<UserDetails> list = new ArrayList<UserDetails>();
NamingEnumeration<SearchResult> results = null;
try {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
results = ctx.search("", buildFilter(userName), controls);
while (results.hasMoreElements()) {
SearchResult search = results.next();
list.add(BeanUtility.convertToContact(search.getAttributes(),
search.getNameInNamespace()));
}
} finally {
LdapUtils.closeContext(ctx);
if (results != null) {
results.close();
}
}
return list;
}
private String buildFilter(String userid) {
AndFilter _andFilter = new AndFilter();
_andFilter.and(new EqualsFilter("objectclass", "user"));
_andFilter.and(new EqualsFilter("sAMAccountName", userid .substring(userid.indexOf("\\") + 1)));
return _andFilter.encode();
}
public UserDetails getUserDetails() {
return userDetails;
}
public void setUserDetails(UserDetails userDetails) {
this.userDetails = userDetails;
}
public LdapTemplate getLdapTemplate() {
return ldapTemplate;
}
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
public String getAuthenticationErrorMessage() {
return authenticationErrorMessage;
}
}
UserDetails.java
package com.test.models;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
public class UserDetails implements Serializable {
private String fName;
private String lName;
public String getfName() {
return fName;
}
public void setfName(String fName) {
this.fName = fName;
}
public String getlName() {
return lName;
}
public void setlName(String lName) {
this.lName = lName;
}
@Override
public String toString() {
return "UserDetails{" + "fName='" + fName + '\'' + ", lName='" + lName + '\'' + '}';
}
}
BeanUtility.java
package com.test.utils;
import com.test.models.UserDetails;
import javax.naming.NamingException;
import javax.naming.directory.Attributes; public class BeanUtility {
public static UserDetails convertToContact(Attributes attributes, String fullRDN) throws NamingException {
UserDetails userDetails = new UserDetails();
try {
userDetails.setfName((String) attributes.get("givenname").get()); userDetails.setlName((String) attributes.get("sn").get());
} catch (Exception e) {
}
return userDetails;
}
}
LogoutImpl.java
package com.test.security.service;import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogoutImpl implements LogoutSuccessHandler {
InMemoryTokenStore tokenstore;
public InMemoryTokenStore getTokenstore() {
return tokenstore;
}
public void setTokenstore(InMemoryTokenStore tokenstore) {
this.tokenstore = tokenstore;
}
@Override
public void onLogoutSuccess(HttpServletRequest paramHttpServletRequest, HttpServletResponse paramHttpServletResponse, Authentication paramAuthentication) throws IOException, ServletException {
removeaccess(paramHttpServletRequest); paramHttpServletResponse.getOutputStream().write("\n\tLogged Out successfully.".getBytes());
}
public void removeaccess(HttpServletRequest req){
String tokens=req.getHeader("Authorization");
System.out.println(tokens);
String value=tokens.substring(tokens.indexOf(" ")).trim();
DefaultOAuth2AccessToken token= new DefaultOAuth2AccessToken(value); System.out.println("This token: "+token);
tokenstore.removeAccessToken(value);
System.out.println("\n\tAccess Token got Removed Successfully!!");
}
}
MyResources.java
package com.test.resources;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/api")
public class MyResources {
@RequestMapping(value = “/myInfo", method = RequestMethod.GET)
@ResponseBody
public String createInfo(){
return "\n\n\tProtected Resource “myInfo” Accessed !\n";
}
@RequestMapping(value = "/auth/signedin", method = RequestMethod.GET)
@ResponseBody
public String signedin(){
return "{\"Login\": \"valid\" }";
}
}
Request/Response
Assuming my project (root-context) name is oauth2poc, from a Mac Terminal try following . . .Resource Owner Password Credentials Grant
The resource owner password credentials authorization grant method works by giving the client application access to the resource owners credentials.Request (for token):
curl -X POST localhost:9004/oauth2poc/oauth/token -d "grant_type=password" -d "client_id=myClient" -d "client_secret=secret1" -d "username=your_username" -d "password=your_password"
Response:
{
"access_token": "9uiodea3-ce51-4bab-a9cf-c6f66900opp00",
"expires_in": 1499,
"scope": "read,write",
"token_type": "bearer"
}
Request (for protected resources):
curl localhost:9004/oauth2poc/api/auth/signedin -H "Authorization: Bearer 9uiodea3-ce51-4bab-a9cf-c6f66900opp00"
Response:
{ "Login" : "valid"}
Request (for protected resources):
curl localhost:9004/oauth2poc/api/myInfo -H "Authorization: Bearer 7eeedea3-ce51-4bab-a9cf-c6f66900d22b"
Response:
Protected Resource “myInfo” Accessed !
Client Credentials Grant
Client credential authorization is for the situations where the client application needs to access resources or call functions in the resource server, which are not related to a specific resource owner.Request (for token):
curl -X POST localhost:9004/oauth2poc/oauth/token -d "grant_type=client_credentials" -d "client_id=myClient" -d "client_secret=secret1"
Response:
{
"access_token": "Zxaedea3-ce51-4bab-a9cf-c6f66900d23d",
"expires_in": 1499,
"scope": "read,write",
"token_type": "bearer"
}
Please note: if you are trying from a REST Client tool, such as Postman in Chrome browser, while requesting/accessing token, set in header, Content-type as x-www-form-urlencoded. And while accessing protected resources after receiving token bearer, set the Authorization: Bearer <access-token-here> in the header, while sending the request.
No comments:
Post a Comment