I'm new to Spring (and for web development in general), but I managed to create a simple web application, and now I'm trying to add authentication support to it so that users have to sign up to access its functions.
I am using Spring MVC 3.2.2, Spring Security 3.1.4 and Tomcat 7.
For testing purposes, I managed to add authentication support using hard-tuned users, and everything worked correctly. Now I have to use the existing PostgreSQL database to authenticate users. I would like to emphasize that my application does not support user creation; users are already stored in the database.
The following is the problem:
(1) The application must authenticate users in the PostgreSQL database, which I cannot change in any way.
(2) Passwords in the database are hashed using crypt ("plain text password", gen_salt (md5)).
(3) Since I used annotations and xml configuration heavily, basically most of the hard work is done with Spring, which means that many things happen behind the scenes that I donβt know about, As a result, I am now stuck in setting up salt. which a password encoder should use to authenticate users. Such a salt should already be a hashed password stored in the database.
Question:
How to tell Spring Security to use a hashed password stored in the database as a salt of the password? Is there a way to do this from xml or do I need to go ahead and implement certain classes (s)?
I was browsing intensively online, and all the examples I found deviated significantly from what I did, mainly because in the examples most of them were functionally implemented by the developer, and not mainly relying on the built-in features.
This is the security.xml file:
<http use-expressions="true"> <intercept-url pattern="/resources/images/**" access="permitAll" requires-channel="any"/> <intercept-url pattern="/resources/css/**" access="permitAll" requires-channel="any"/> <intercept-url pattern="/login*" access="permitAll" requires-channel="any"/> <intercept-url pattern="/**" access="hasAnyRole('ROLE_Processer', 'ROLE_Verifier', 'ROLE_Approver', 'ROLE_Supervisor', 'ROLE_Admin')" requires-channel="any"/> <session-management> <concurrency-control max-sessions="1" expired-url=/login?expired=true" session-registry-alias="sessionRegistry"/> </session-management> <form-login login-page="/login" default-target-url="/" always-use-default-target="true" authentication-failure-url="/login?error=true"/> <logout logout-success-url="/login" invalidate-session="true" delete-cookies="JSESSIONID"/> </http> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource" users-by-username-query="SELECT id AS username, password, enabled FROM users WHERE id=?;" authorities-by-username-query="SELECT id AS username, role FROM users WHERE id=?;" role-prefix="ROLE_"/> <password-encoder hash="md5"> <salt-source ???/> </password-encoder> </authentication-provider> </authentication-manager>
This is the corresponding excerpt from the web.xml file :
<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> <listener> <listener-class> org.springframework.security.web.session.HttpSessionEventPublisher </listener-class> </listener>
This is the corresponding excerpt from the application-context.xml file:
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> <beans:property name="driverClass" value="org.postgresql.Driver"/> <beans:property name="url" value="jdbc:postgresql://host:port/database"/> <beans:property name="username" value="username"/> <beans:property name="password" value="password"/> </beans:bean>
In terms of implementation, this is the only relevant LoginController.java controller (all others handle the actual functionality provided by the application):
@Controller
public class LoginController {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class); @RequestMapping(value="/login", method=RequestMethod.GET) public String displayLoginForm(@RequestParam(value="error", required=false) String error, @RequestParam(value="expired", required=false) String expired, Model model) { LOGGER.info("Displaying Login form:: displayLoginForm(Model)"); if (error != null) { LOGGER.info("Invalid username or password."); model.addAttribute("error", "Invalid username or password."); } if (expired != null) { LOGGER.info("Session has been expired (possibly due to multiple concurrent logins being attempted as the same user)"); model.addAttribute("expired", "Session has been expired (possibly due to multiple concurrent logins being attempted as the same user)."); } return "login"; }}
Any pointers would be greatly appreciated. I would like to thank all of you for your time and help.
Edit:
I tried with
<password-encoder hash="md5"> <salt-source user-property="password"/> </password-encoder>
as I understand it, after reading the documentation , this is the user password stored in the database. However, this does not work. Every time I try to log in, I get a message with invalid credentials; however, the credentials are valid.
Perhaps any of the events described in the "Hashing and Authentication" section of this page may be the reason that it does not work. I am wondering how to write an appropriate test, as suggested.
Other Edit:
As a test, I commented out the password element and verified authentication with a plain text password (i.e., I created a test user and saved a simple text password in the database). It worked. So the problem is definitely related to the way Spring Security encodes a user-entered password that does not match the hashed password stored in the database.