We will based our application from the one we developed for Spring MVC 3 - Security - Using Simple User-Service tutorial. This is because everything is exactly the same. It's just a matter of configuration to enable OpenID support.
What is OpenID?
OpenID allows you to use an existing account to sign in to multiple websites, without needing to create new passwords.
You may choose to associate information with your OpenID that can be shared with the websites you visit, such as a name or email address. With OpenID, you control how much of that information is shared with the websites you visit.
With OpenID, your password is only given to your identity provider, and that provider then confirms your identity to the websites you visit. Other than your provider, no website ever sees your password, so you don’t need to worry about an unscrupulous or insecure website compromising your identity.
Source: http://openid.net/get-an-openid/what-is-openid/
Screenshot
Here's a screenshot of the application's OpenID login page:Review
Enabling OpenID authentication is actually simple. Remember our application is based on the Spring MVC 3 - Security - Using Simple User-Service. Because of that all we need to do is modify the existing spring-security.xml config.The Old Config
Here's our existing config file (for a thorough explanation please read the aforementioned guide):spring-security.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:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <!-- This is where we configure Spring-Security --> <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" > <security:intercept-url pattern="/krams/auth/login" access="permitAll"/> <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/> <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/> <security:form-login login-page="/krams/auth/login" authentication-failure-url="/krams/auth/login?error=true" default-target-url="/krams/main/common"/> <security:logout invalidate-session="true" logout-success-url="/krams/auth/login" logout-url="/krams/auth/logout"/> </security:http> <!-- Declare an authentication-manager to use a custom userDetailsService --> <security:authentication-manager> <security:authentication-provider user-service-ref="userDetailsService"> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager> <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database --> <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/> <!-- An in-memory list of users. No need to access an external database layer. See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication --> <!-- john's password is admin, while jane;s password is user --> <security:user-service id="userDetailsService"> <security:user name="john" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_USER, ROLE_ADMIN" /> <security:user name="jane" password="ee11cbb19052e40b07aac0ca060c23ee" authorities="ROLE_USER" /> </security:user-service> </beans>This configuration uses Spring Security's form-based login where you enter a username and password. At the end of the file, we have declared a list of users with corresponding credentials.
To authenticate a user, he must first provide a username and password which the application will compare in the in-memory list as declared in the user-service tag. If a match is found, the user is authenticated and authorized.
The New Config
Here's our new config file which enables OpenID support. Notice almost everything is still exactly the same!spring-security.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:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd"> <!-- This is where we configure Spring-Security --> <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" > <security:intercept-url pattern="/krams/auth/login" access="permitAll"/> <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/> <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/> <!-- Adding the openid-login tag activates Spring Security's support for OpenID --> <security:openid-login login-page="/krams/auth/login" authentication-failure-url="/krams/auth/login?error=true" default-target-url="/krams/main/common"/> <security:logout invalidate-session="true" logout-success-url="/krams/auth/login" logout-url="/krams/auth/logout"/> </security:http> <!-- Declare an authentication-manager to use a custom userDetailsService --> <security:authentication-manager> <security:authentication-provider user-service-ref="userDetailsService"> <security:password-encoder ref="passwordEncoder"/> </security:authentication-provider> </security:authentication-manager> <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database --> <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/> <!-- An in-memory list of users. No need to access an external database layer. See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication --> <security:user-service id="userDetailsService"> <!-- user name is based on the returned OpenID identifier from Google --> <security:user name="https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" password="" authorities="ROLE_USER, ROLE_ADMIN" /> </security:user-service> </beans>
Google: Enabling OpenID
With OpenID the same principle applies. To authenticate a user via Google's OpenID support, he must perform the following steps:1. Click the Sign with Google button.
2. Login with the OpenID provider.
After a successful authentication, the provider will ask the user to continue and return back to the original application
4. Spring Security will then perform two additional steps:
- Check if the returned OpenID identifier is registered in the application's database.
- Check if the returned OpenID identifier has authorities assigned in the application's database.
Notice these are the same steps used in a form-based login. This is the reason why it's beneficial that you understand how to setup a simple Spring Security 3 application using a simple user-details service first.
Authorities/Roles
Take note the job of the OpenID provider is to authenticate the user and returned back a valid OpenID identifier. The appropriate authorities or roles is the responsibility of the application. We must assign the roles in other words!<security:user-service id="userDetailsService"> <!-- user name is based on the returned open id identifier --> <security:user name="https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" password="" authorities="ROLE_USER, ROLE_ADMIN" /> </security:user-service>Notice for this username we assigned a ROLE_USER and ROLE_ADMIN.
Development
Let's enable OpenID authentication by modifying the config file. We just need to perform two steps:1. Replace the form-login tag with openid-login tag
old config
<security:http auto-config="true" > <security:form-login > ...omitted declarations </security:http>
new config
<security:http auto-config="true" > <security:openid-login > ...omitted declarations </security:http>
2. Add a new user name inside the user-service tag
<security:user-service id="userDetailsService"> <security:user name="https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E" password="" authorities="ROLE_USER, ROLE_ADMIN" /> </security:user-service>Notice the password attribute is empty. That's because the actual authentication is performed by the OpenID provider. The user name is based on the OpenID identifier returned by the provider. By default the OpenID login format is:
http://USERNAME.myopenid.com/where USERNAME is a placeholder for the user's chosen username during registration at myOpenID website.
However Google doesn't follow this convention. Instead the returned OpenID identifier has the following format:
https://www.google.com/accounts/o8/id?id=XXXXXXXXXXXXXXXXXXXXXFor example:
https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E
The JSP Login Page
Since we'll be using OpenID, we need to modify our JSP login page to use the OpenID form identifiers.<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <h1>Login</h1> <div id="login-error">${error}</div> <c:url var="logoUrl" value="/resources/openidlogosmall.png" /> <p><img src="${logoUrl}"></img>Login with OpenID:</p> <c:url var="openIDLoginUrl" value="/j_spring_openid_security_check" /> <form action="${openIDLoginUrl}" method="post" > <label for="openid_identifier">OpenID Login</label>: <input id="openid_identifier" name="openid_identifier" type="text"/> <input type="submit" value="Login"/> </form> <hr/> <c:url var="googleLogoUrl" value="/resources/google-logo.png" /> <img src="${googleLogoUrl}"></img> <form action="${openIDLoginUrl}" method="post"> For Google users: <input name="openid_identifier" type="hidden" value="https://www.google.com/accounts/o8/id"/> <input type="submit" value="Sign with Google"/> </form> </body> </html>Notice we have two form tags.
The first form tag is for OpenID providers that support direct id entry. For example, http://krams915.myopenid.com or http://krams915.blogspot.com. For a description of this form tag, see the tutorial Spring Security 3 - OpenID Login with myOpenID Provider
Google doesn't support direct id entry. Instead you need to use the following URL as the entry id:
https://www.google.com/accounts/o8/idThis works. However, the user must remember this exact URL which is bad because the majority of users will not remember that URL ever! Talk about bad user experience.
To resolve this issue, we added a second form tag that only works for Google. What we did here is hide the input text field and embed Google's default entry id (https://www.google.com/accounts/o8/id) so that when the user clicks the Sign with Google button, he doesn't need to type that unfriendly URL.
Determine the OpenID Identifier
The best way to determine the OpenID identifier is to set the application's logger to DEBUG level. Then run the application and check the logs. Of course, you need to have a working internet connection to be able to login with the provider. Our application uses log4j for logging.Here's the log4j properties file:
log4j.properties
log4j.rootLogger=DEBUG,console #Console Appender log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=[%5p] [%t %d{hh:mm:ss}] (%F:%M:%L) %m%n #Custom assignments log4j.logger.controller=DEBUG,console log4j.logger.service=DEBUG,console #Disable additivity log4j.additivity.controller=false log4j.additivity.service=false
Run the application and login with OpenID. After a successful authentication, check the logs. Here's a sample output:
log output
[DEBUG] [http-8080-Processor23 02:33:21] (Association.java:sign:261) Computing signature for input data: op_endpoint:https://www.google.com/accounts/o8/ud claimed_id:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E identity:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E return_to:http://localhost:8080/spring-security-openid-google/j_spring_openid_security_check response_nonce:2011-02-12T15:48:26Zdji30Q3btZj2Cw assoc_handle:AOQobUebACOhT-Hh3ckKxe8Bxc_drPGqIIfzMcYDBfU0167YQZw6J4F6uvH18g2UlNRDPC7B [DEBUG] [http-8080-Processor24 11:48:27] (Association.java:sign:267) Calculated signature: Mq8Iu1XQ87rKQePQxEnIcU7GttlT6E1usz3E+BgGvx4= [DEBUG] [http-8080-Processor24 11:48:27] (ConsumerManager.java:verifySignature:1790) Local signature verification succeeded. [ INFO] [http-8080-Processor24 11:48:27] (ConsumerManager.java:verifySignature:1850) Verification succeeded for: https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E [DEBUG] [http-8080-Processor24 11:48:27] (ProviderManager.java:doAuthentication:127) Authentication attempt using org.springframework.security.openid.OpenIDAuthenticationProviderPay attention to these values:
claimed_id:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E identity:https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0EThe claimed_id is what you need to add in the application's user-details table. In our case, it's an in-memory list.
If you really want to ensure that you're using the correct id, check the next line in the log file:
[ INFO] [http-8080-Processor24 11:48:27] (ConsumerManager.java:verifySignature:1850) Verification succeeded for: https://www.google.com/accounts/o8/id?id=AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0EThe value you see here is the id that must be placed in the user-details table. I'm adding emphasis here because if you're using other OpenID providers, you might get confused which id to used. This one is always correct.
Other OpenID Providers
Below is a sample table that shows the different identifiers returned by various OpenID providers (We'll try to expand this list as we explore OpenID further in future tutorials):MyOpenID | http://krams915.myopenid.com/ |
Yahoo | https://me.yahoo.com/a/ooXDFSsdfsqDbYAGuDSFSIK.PIuBsfdKA--#ade71 |
https://www.google.com/accounts/o8/id?id=BVlajJOIDjsldfjszSfjsM5sdfs0E | |
Blogspot | http://krams915.blogspot.com/ |
Notice some OpenID providers adhere to the standard format, but others do not. To see other availabe OpenID providers, please visit http://openid.net/get-an-openid/
Run the Application
To run the application, use the following URL:http://localhost:8080/spring-security-openid-myopenid/krams/auth/loginOf course, you will need to modify the username in the user-details service to match your provider's returned OpenID identifier. You will need to run the application first, authenticate, after a failed authentication, you should be able to see the claimed_id in the logs.
Conclusion
That's it. We've managed to enable OpenID support on our existing Spring Security 3 application by just modifying two simple configurations and a single JSP file. We've also discussed how to retrieve the OpenID identifier and how Spring Security evaluates this property.The best way to learn further is to try the actual application.
Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-openid/
You can download the project as a Maven build. Look for the spring-security-openid-google.zip in the Download sections.
You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run
If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
Share the joy:
|
Subscribe by reader Subscribe by email Share
Hi krams, I found it really good as I am new to spring security. Thanks for the post.
ReplyDeleteHowever, I have a little difficulty at the last step while testing the application. I have kept the application as it is supplied; with just the change in google openId value.
After gibing the google credentials, it comes back to application login page with the error "You have entered an invalid username or password!"
Can you try enabling the logs and verify the OpenID parameters sent to you by Google? Also check the stack trace from the log. When developing with OpenID, I had to look often on the logs.
DeleteWhere else could anyone get that kind of info written in such a perfect way? I have a presentation that I am presently working on, and I have been on the look out for such information.
ReplyDeleteHi krams,
ReplyDeleteMy openID login process works fine thanks to your excellent article.
However, I cannot manage to get the user to continue to her original destination URL once logged in : she is always redirected to / , despites the fact that the "default-target-url" and "always-use-default-target-url" are disabled in my security config.
Would you have any idea why, and how to correct this behavior ?
Olivier
Hi Krams,
ReplyDeleteThis tutorial was extremely useful! Huge props to you!
One thing though, I realised that every single time the user signs in they are directed to the consent screen. Is this right? This is despite the user selecting "Remember this approval".
Any suggestions would be appreciated.
I've got it all working now.
ReplyDeleteThanks,
CorayThan
Hi ANy idea about this issue http://stackoverflow.com/questions/15254334/openidjsf-redirect-user-to-the-page-he-login
ReplyDeleteHello Krams,
ReplyDeleteThe tutorial is really very helpful. However I am not able to run it using maven. When I execute mvn tomcat:run it creates the war file. It shows following info in terminal.
[INFO] Running war on http://localhost:8080/spring-security-openid-google
[INFO] Creating Tomcat server configuration at /home/sumant/spring-security-openid-google/target/tomcat
Apr 25, 2013 6:12:18 PM org.apache.catalina.startup.Embedded start
INFO: Starting tomcat server
Apr 25, 2013 6:12:19 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.29
Apr 25, 2013 6:12:19 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring root WebApplicationContext
Apr 25, 2013 6:12:22 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'spring'
Apr 25, 2013 6:12:22 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Apr 25, 2013 6:12:22 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
After this if I try to access the application using the url it gives me the following error.
The requested resource (/spring-security-openid-google/) is not available.
Hi Mark,
ReplyDeleteI think this is a great tutorial to understand how to get started with OpenID and use it in a spring mvc app. I noticed, you have configured your app, to authenticate 1 'pre-selected' user only. The open-id username for the user is stored in spring-security.xml.How can we make it dynamic - meaning how can we maintain a dynamic white-list which will contain all the users (their OpenID username) that are allowed. If some user is not on the list, they will be able to register themselves in.
Thanks for the post. It helped a lot. I was wondering how do you get to know the OpenId that you have registered in the user-service section in spring-security.xml?
ReplyDeleteIn my webapp, I want to use the gmail authentication but want to allow only a few users. So I am planning to add all the allowed users to the xml file. The issue is in getting the OpenId. In your case:"AItxxioJSDLFJLjxcksdfjOpAASDFosSSoJ0E".
How do you get your hands on this id?
Thank you for the great tutorial.
ReplyDeleteIs there a way to allow login for all members of some google apps domain, w/o listing explicitly all users?
Or, at least, keep users list, but make ids host-independent (now google generates different ids for each host of deployment)
is it possible to use ACL based authorization??
ReplyDeleteHey!! nice tutorial... i downloaded the zip but its giving me an error in spring-security.xml on line 38...Attribute : class
ReplyDeleteThe fully qualified name of the bean's class, except if it serves only as a parent definition for child bean ..i m new to spring
definitions.
Nice Tutorial..
ReplyDeleteIt is very helpful to me.Thank You
Great post!!! In really had a great time reading it!!! It was really informative, thanks for sharing!!!! visit more info Gmail Support You can reach Acetecsupport at their Call Toll Free No +1-800-231-4635 For US/CA.
ReplyDeletegreat articles for understanding spring technology for beginners like me.
ReplyDeleteUnfortunately non of the projects i downloaded from ur articles work for me. I keep getting the 404 status with no message as soon as I run any jsp file. Ive spent very long hours on it. could somebody help suggesting what am i doing wrong? (totaly new to spring)
Hey, I am still struggling to get the Google OpenID. Could anyone please help ?
ReplyDeleteHi I am getting error "You have entered an invalid username or password!" while clicking on signin with google account. Can someone please help?
ReplyDeleteI have read your blog its very attractive and impressive. I like it your blog.
ReplyDeleteSpring online training Spring online training Spring Hibernate online training Spring Hibernate online training Java online training
spring training in chennai spring hibernate training in chennai
Hi Karams,Can you give me the working codefor the above discussed spring openid
ReplyDeleteThanks for sharing this wonderful information. I hope you will share more helpful information regarding the content.
ReplyDeleteweb portal development company in chennai
sem services in chennai
professional web design company in chennai
This is my blog. Click here.
ReplyDeleteรูปแบบใหม่ไพ่บาคาร่าออนไลน์"