Spring security with site minder integration
SSO : Single sign on system which means “sign in once for multiple secured applications”
Site Minder : single sign on system which will authenticate the user for one of the application where the site minder is integrated and all other applications just allows the user inside without any authentication.
Now if the user tries to access any other application within that group of application designed for SSO, it will just pass the request header with user id and the application will just do the authorization of roles and provide appropriate access inside the application.
Example:
If the organization has 5 applications to use for SSO and one of those is integrated with third party SSO.
Then rest 4 of the applications will be just reading the request header from the SSO system and skip the authentication but continues with the authorization.
Let’s see how our application can use request header from site minder and do the SSO process using spring security.
Tools and Technologies used
1)Eclipse IDE Mars Release (4.5.0)
2)Java 8
3)Spring framework 3
4)Spring security 3.2
5)Tomcat 8
6)Mod Header plugin
Create maven project using this link
Modify the pom.xml as below
- <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>SpringSecurityUsingSiteMinder</groupId>
- <artifactId>SpringSecurityUsingSiteMinder</artifactId>
- <packaging>war</packaging>
- <version>0.0.1-SNAPSHOT</version>
- <name>SpringSecurityUsingSiteMinder Maven Webapp</name>
- <url>http://maven.apache.org</url>
- <properties>
- <org.springframework.version>4.2.0.RELEASE</org.springframework.version>
- <spring-security.version>3.2.7.RELEASE</spring-security.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- <!-- Spring MVC depends on these modules spring-core, spring-beans, spring-context,
- spring-web -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-web</artifactId>
- <version>${org.springframework.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</artifactId>
- <version>${org.springframework.version}</version>
- </dependency>
- <!-- Spring Security Dependencies -->
- <dependency>
- <groupId>org.springframework.security</groupId>
- <artifactId>spring-security-core</artifactId>
- <version>${spring-security.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.security</groupId>
- <artifactId>spring-security-web</artifactId>
- <version>${spring-security.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.security</groupId>
- <artifactId>spring-security-config</artifactId>
- <version>${spring-security.version}</version>
- </dependency>
- </dependencies>
- <build>
- <finalName>SpringSecurityUsingSiteMinder</finalName>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.5.1</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </project>
<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>SpringSecurityUsingSiteMinder</groupId> <artifactId>SpringSecurityUsingSiteMinder</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>SpringSecurityUsingSiteMinder Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <org.springframework.version>4.2.0.RELEASE</org.springframework.version> <spring-security.version>3.2.7.RELEASE</spring-security.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- Spring MVC depends on these modules spring-core, spring-beans, spring-context, spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework.version}</version> </dependency> <!-- Spring Security Dependencies --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring-security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring-security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security.version}</version> </dependency> </dependencies> <build> <finalName>SpringSecurityUsingSiteMinder</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.5.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
Modify the web.xml as below
- <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
- version="3.1">
- <display-name>Spring security site minder Application</display-name>
- <!-- Spring MVC dispatcher servlet -->
- <servlet>
- <servlet-name>mvc-dispatcher</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- /WEB-INF/spring-mvc.xml,
- /WEB-INF/spring-security.xml
- </param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>mvc-dispatcher</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <!-- Loads Spring Security configuration file -->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>
- /WEB-INF/spring-mvc.xml,
- /WEB-INF/spring-security.xml
- </param-value>
- </context-param>
- <!-- Spring Security filter -->
- <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>
- </web-app>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>Spring security site minder Application</display-name> <!-- Spring MVC dispatcher servlet --> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-mvc.xml, /WEB-INF/spring-security.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Loads Spring Security configuration file --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring-mvc.xml, /WEB-INF/spring-security.xml </param-value> </context-param> <!-- Spring Security filter --> <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> </web-app>
Modify the spring-mvc.xml as below
- <?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:p="http://www.springframework.org/schema/p"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.2.xsd
- http://www.springframework.org/schema/mvc
- http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
- <context:component-scan base-package="com.kb.*" />
- <mvc:annotation-driven />
- <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="prefix" value="/WEB-INF/pages/" />
- <property name="suffix" value=".jsp" />
- </bean>
- </beans>
<?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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> <context:component-scan base-package="com.kb.*" /> <mvc:annotation-driven /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/" /> <property name="suffix" value=".jsp" /> </bean> </beans>
Create the spring-security.xml as below
- <beans:beans xmlns="http://www.springframework.org/schema/security"
- xmlns:beans="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
- http://www.springframework.org/schema/security
- http://www.springframework.org/schema/security/spring-security-3.2.xsd">
- <http auto-config='true'>
- <intercept-url pattern="/**" access="ROLE_USER" />
- <custom-filter ref="siteMinderFilter" position="PRE_AUTH_FILTER"/>
- </http>
- <beans:bean id="siteMinderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
- <beans:property name="principalRequestHeader" value="SM_USER"/>
- <beans:property name="authenticationManager" ref="authenticationManager" />
- </beans:bean>
- <authentication-manager alias="authenticationManager">
- <authentication-provider ref="preAuthenticationProvider"/>
- </authentication-manager>
- <beans:bean id="preAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
- <beans:property name="preAuthenticatedUserDetailsService">
- <beans:bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
- <beans:property name="userDetailsService" ref="customUserService"/>
- </beans:bean>
- </beans:property>
- </beans:bean>
- <beans:bean id="customUserService" class="com.kb.service.CustomUserService"/>
- </beans:beans>
<beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> <http auto-config='true'> <intercept-url pattern="/**" access="ROLE_USER" /> <custom-filter ref="siteMinderFilter" position="PRE_AUTH_FILTER"/> </http> <beans:bean id="siteMinderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter"> <beans:property name="principalRequestHeader" value="SM_USER"/> <beans:property name="authenticationManager" ref="authenticationManager" /> </beans:bean> <authentication-manager alias="authenticationManager"> <authentication-provider ref="preAuthenticationProvider"/> </authentication-manager> <beans:bean id="preAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <beans:property name="preAuthenticatedUserDetailsService"> <beans:bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <beans:property name="userDetailsService" ref="customUserService"/> </beans:bean> </beans:property> </beans:bean> <beans:bean id="customUserService" class="com.kb.service.CustomUserService"/> </beans:beans>
Create a custom user service class
- package com.kb.service;
- import java.util.ArrayList;
- import java.util.List;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import com.kb.model.CustomUser;
- import com.kb.model.Role;
- public class CustomUserService implements UserDetailsService {
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- // Write your DB call code to get the user details(user and roles) from
- // DB ,But I am just hard coding it.
- CustomUser user = new CustomUser();
- user.setFirstName("kb");
- user.setLastName("gc");
- user.setUsername("kb");
- user.setPassword("1234");
- Role r = new Role();
- r.setName("ROLE_USER");
- List<Role> roles = new ArrayList<Role>();
- roles.add(r);
- user.setAuthorities(roles);
- return user;
- }
- }
package com.kb.service; import java.util.ArrayList; import java.util.List; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.kb.model.CustomUser; import com.kb.model.Role; public class CustomUserService implements UserDetailsService { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // Write your DB call code to get the user details(user and roles) from // DB ,But I am just hard coding it. CustomUser user = new CustomUser(); user.setFirstName("kb"); user.setLastName("gc"); user.setUsername("kb"); user.setPassword("1234"); Role r = new Role(); r.setName("ROLE_USER"); List<Role> roles = new ArrayList<Role>(); roles.add(r); user.setAuthorities(roles); return user; } }
Create a home controller class as below
- package com.kb.controller;
- import org.springframework.security.core.context.SecurityContextHolder;
- import org.springframework.security.core.userdetails.User;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.ModelMap;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import com.kb.model.CustomUser;
- @Controller
- public class HomeController {
- @RequestMapping(value="/secured/home", method = RequestMethod.GET)
- public String securedHome(ModelMap model) {
- Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
- CustomUser user=null;
- if (principal instanceof CustomUser) {
- user = ((CustomUser)principal);
- String name = user.getUsername();
- model.addAttribute("username", name);
- }
- model.addAttribute("message", "Welcome to the secured page through site minder");
- return "home";
- }
- }
package com.kb.controller; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.kb.model.CustomUser; @Controller public class HomeController { @RequestMapping(value="/secured/home", method = RequestMethod.GET) public String securedHome(ModelMap model) { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); CustomUser user=null; if (principal instanceof CustomUser) { user = ((CustomUser)principal); String name = user.getUsername(); model.addAttribute("username", name); } model.addAttribute("message", "Welcome to the secured page through site minder"); return "home"; } }
Create home.jsp as below
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
- </head>
- <body>
- <h3>Hello World!</h3>
- <h4>Hi ${username}, ${message}</h4>
- </body>
- </html>
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> </head> <body> <h3>Hello World!</h3> <h4>Hi ${username}, ${message}</h4> </body> </html>
Now right click on the project and do run as -> maven-install
Copy the war file from project target folder to server’s webapps folder
Restart the server
Now lets access the secured url without passing the user name in the header
http://localhost:8080/SpringSecurityUsingSiteMinder/secured/home
and see the below exception
Now let’s set the header with username as ‘kb’ using ‘Mod Header’ plugin as below
Now lets access the secured url by passing the user name in the header
http://localhost:8080/SpringSecurityUsingSiteMinder/secured/home
Now we are able to access the secured url without any login prompt.
Hi KB, First of all thank a lot for this tutorial. I had a same use case in my current project. I have a query
1. For the first request CustomUserService (implements UserDetailsService) getting called successfully but if i close the browser and login with different user with less authorities is not getting authorized. So my CustomUserService is not getting called for second user. How to address this issue ?
Hi,
This article was highly informative.
But for development (and debugging), we are using the Mod-Header plugin to force-insert the SM_USER into the request header but this introduces a restriction on development & testing – it needs to be done only with Chrome browser (as the Mod Header extension) works only with Chrome.
So anyway to overcome this restriction ?
In my project (we use Angular + Spring-java REST services),
thanks
Jeevan
You can use Modify Headers for other browsers or Burp Suite(preferred one) as well.