Previous: Domain Model Up: Tutorial Next: Authorization

Our application now has all the features it needs. The only thing missing is some kind of user management. Every owner should be able to add and edit his/her pets and add visits, but should not be able to do this with the pets of other owners. So we have to know, which owner is currently using the system. We need some kind of authentication.

In this chapter we will add a simple login to our application

Adding authentication data to the database

First we need to handle some kind of login information. For this we create a database table USERS to store it. This table needs a reference to the corresponding owner or vet. Unfortunately we currently have no common table for owners and vets. Owners and vets already have some data in common, so it seems naturally to create a common base-table. We name it PERSONS and we put the FIRST_NAME and LAST_NAME into it. The USERS table then can reference the table PERSONS.

Again we change the database schema in the file petclinic.script from the root-folder (which is named petclinic/). Change the following lines

CREATE MEMORY TABLE VETS(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,FIRST_NAME VARCHAR(30),LAST_NAME VARCHAR(30))
CREATE INDEX VETS_LAST_NAME ON VETS(LAST_NAME)
[...]
CREATE MEMORY TABLE OWNERS(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,FIRST_NAME VARCHAR(30),LAST_NAME VARCHAR(30),ADDRESS VARCHAR(255),CITY VARCHAR(80),TELEPHONE VARCHAR(20))
CREATE INDEX OWNERS_LAST_NAME ON OWNERS(LAST_NAME)
    

into

CREATE MEMORY TABLE PERSONS(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,FIRST_NAME VARCHAR(30),LAST_NAME VARCHAR(30))
CREATE INDEX PERSONS_LAST_NAME ON PERSONS(LAST_NAME)
CREATE MEMORY TABLE USERS(ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,USERNAME VARCHAR(30),PASSWORD VARCHAR(30),PERSON_ID INTEGER NOT NULL,CONSTRAINT FK_USERS_PERSONS FOREIGN KEY(PERSON_ID) REFERENCES PERSONS(ID))
CREATE INDEX USERS_USERNAME ON USERS(USERNAME)
CREATE MEMORY TABLE VETS(ID INTEGER NOT NULL PRIMARY KEY,CONSTRAINT FK_VETS_PERSONS FOREIGN KEY(ID) REFERENCES PERSONS(ID))
[...]
CREATE MEMORY TABLE OWNERS(ID INTEGER NOT NULL PRIMARY KEY,ADDRESS VARCHAR(255),CITY VARCHAR(80),TELEPHONE VARCHAR(20),CONSTRAINT FK_OWNERS_PERSONS FOREIGN KEY(ID) REFERENCES PERSONS(ID))
    

Now we have a common id for owners and vets. It is the id of the table PERSONS. We have to fix the generated ids. In our database we currently have 16 persons (6 vets and 10 owners). So change the following lines

ALTER TABLE VETS ALTER COLUMN ID RESTART WITH 7
[...]
ALTER TABLE OWNERS ALTER COLUMN ID RESTART WITH 11
    

into

ALTER TABLE PERSONS ALTER COLUMN ID RESTART WITH 17
    

Additionally we have to change the data to insert. First we add the inserts for the table PERSONS.

INSERT INTO PERSONS VALUES(1,'James','Carter')
INSERT INTO PERSONS VALUES(2,'Helen','Leary')
INSERT INTO PERSONS VALUES(3,'Linda','Douglas')
INSERT INTO PERSONS VALUES(4,'Rafael','Ortega')
INSERT INTO PERSONS VALUES(5,'Henry','Stevens')
INSERT INTO PERSONS VALUES(6,'Sharon','Jenkins')
INSERT INTO PERSONS VALUES(7,'George','Franklin')
INSERT INTO PERSONS VALUES(8,'Betty','Davis')
INSERT INTO PERSONS VALUES(9,'Eduardo','Rodriquez')
INSERT INTO PERSONS VALUES(10,'Harold','Davis')
INSERT INTO PERSONS VALUES(11,'Peter','McTavish')
INSERT INTO PERSONS VALUES(12,'Jean','Coleman')
INSERT INTO PERSONS VALUES(13,'Jeff','Black')
INSERT INTO PERSONS VALUES(14,'Maria','Escobito')
INSERT INTO PERSONS VALUES(15,'David','Schroeder')
INSERT INTO PERSONS VALUES(16,'Carlos','Estaban')
    

Second we remove the names from the inserts into the table VETS.

Change

INSERT INTO VETS VALUES(1,'James','Carter')
INSERT INTO VETS VALUES(2,'Helen','Leary')
INSERT INTO VETS VALUES(3,'Linda','Douglas')
INSERT INTO VETS VALUES(4,'Rafael','Ortega')
INSERT INTO VETS VALUES(5,'Henry','Stevens')
INSERT INTO VETS VALUES(6,'Sharon','Jenkins')
    

into

INSERT INTO VETS VALUES(1)
INSERT INTO VETS VALUES(2)
INSERT INTO VETS VALUES(3)
INSERT INTO VETS VALUES(4)
INSERT INTO VETS VALUES(5)
INSERT INTO VETS VALUES(6)
    

Third we remove the names from the inserts into the table OWNERS and fix the indices.

Change

INSERT INTO OWNERS VALUES(1,'George','Franklin','110 W. Liberty St.','Madison','6085551023')
INSERT INTO OWNERS VALUES(2,'Betty','Davis','638 Cardinal Ave.','Sun Prairie','6085551749')
INSERT INTO OWNERS VALUES(3,'Eduardo','Rodriquez','2693 Commerce St.','McFarland','6085558763')
INSERT INTO OWNERS VALUES(4,'Harold','Davis','563 Friendly St.','Windsor','6085553198')
INSERT INTO OWNERS VALUES(5,'Peter','McTavish','2387 S. Fair Way','Madison','6085552765')
INSERT INTO OWNERS VALUES(6,'Jean','Coleman','105 N. Lake St.','Monona','6085552654')
INSERT INTO OWNERS VALUES(7,'Jeff','Black','1450 Oak Blvd.','Monona','6085555387')
INSERT INTO OWNERS VALUES(8,'Maria','Escobito','345 Maple St.','Madison','6085557683')
INSERT INTO OWNERS VALUES(9,'David','Schroeder','2749 Blackhawk Trail','Madison','6085559435')
INSERT INTO OWNERS VALUES(10,'Carlos','Estaban','2335 Independence La.','Waunakee','6085555487')
    

into

INSERT INTO OWNERS VALUES(7,'110 W. Liberty St.','Madison','6085551023')
INSERT INTO OWNERS VALUES(8,'638 Cardinal Ave.','Sun Prairie','6085551749')
INSERT INTO OWNERS VALUES(9,'2693 Commerce St.','McFarland','6085558763')
INSERT INTO OWNERS VALUES(10,'563 Friendly St.','Windsor','6085553198')
INSERT INTO OWNERS VALUES(11,'2387 S. Fair Way','Madison','6085552765')
INSERT INTO OWNERS VALUES(12,'105 N. Lake St.','Monona','6085552654')
INSERT INTO OWNERS VALUES(13,'1450 Oak Blvd.','Monona','6085555387')
INSERT INTO OWNERS VALUES(14,'345 Maple St.','Madison','6085557683')
INSERT INTO OWNERS VALUES(15,'2749 Blackhawk Trail','Madison','6085559435')
INSERT INTO OWNERS VALUES(16,'2335 Independence La.','Waunakee','6085555487')
    

Now we fix the owner-indices of the pets.

Change

INSERT INTO PETS VALUES(1,'Leo','2000-09-07',1,1)
INSERT INTO PETS VALUES(2,'Basil','2002-08-06',6,2)
INSERT INTO PETS VALUES(3,'Rosy','2001-04-17',2,3)
INSERT INTO PETS VALUES(4,'Jewel','2000-03-07',2,3)
INSERT INTO PETS VALUES(5,'Iggy','2000-11-30',3,4)
INSERT INTO PETS VALUES(6,'George','2000-01-20',4,5)
INSERT INTO PETS VALUES(7,'Samantha','1995-09-04',1,6)
INSERT INTO PETS VALUES(8,'Max','1995-09-04',1,6)
INSERT INTO PETS VALUES(9,'Lucky','1999-08-06',5,7)
INSERT INTO PETS VALUES(10,'Mulligan','1997-02-24',2,8)
INSERT INTO PETS VALUES(11,'Freddy','2000-03-09',5,9)
INSERT INTO PETS VALUES(12,'Lucky','2000-06-24',2,10)
INSERT INTO PETS VALUES(13,'Sly','2002-06-08',1,10)
    

into

INSERT INTO PETS VALUES(1,'Leo','2000-09-07',1,7)
INSERT INTO PETS VALUES(2,'Basil','2002-08-06',6,8)
INSERT INTO PETS VALUES(3,'Rosy','2001-04-17',2,9)
INSERT INTO PETS VALUES(4,'Jewel','2000-03-07',2,9)
INSERT INTO PETS VALUES(5,'Iggy','2000-11-30',3,10)
INSERT INTO PETS VALUES(6,'George','2000-01-20',4,11)
INSERT INTO PETS VALUES(7,'Samantha','1995-09-04',1,12)
INSERT INTO PETS VALUES(8,'Max','1995-09-04',1,12)
INSERT INTO PETS VALUES(9,'Lucky','1999-08-06',5,13)
INSERT INTO PETS VALUES(10,'Mulligan','1997-02-24',2,14)
INSERT INTO PETS VALUES(11,'Freddy','2000-03-09',5,15)
INSERT INTO PETS VALUES(12,'Lucky','2000-06-24',2,16)
INSERT INTO PETS VALUES(13,'Sly','2002-06-08',1,16)
    

Last we have to insert the credentials to log in the users.

Add

INSERT INTO USERS VALUES(1,'james','b4cc344d25a2efe540adbf2678e2304c',1)
INSERT INTO USERS VALUES(2,'helen','7a2eb41a38a8f4e39c1586649da21e5f',2)
INSERT INTO USERS VALUES(3,'linda','eaf450085c15c3b880c66d0b78f2c041',3)
INSERT INTO USERS VALUES(4,'rafael','9135d8523ad3da99d8a4eb83afac13d1',4)
INSERT INTO USERS VALUES(5,'henry','027e4180beedb29744413a7ea6b84a42',5)
INSERT INTO USERS VALUES(6,'sharon','215a6517848319b70f3f450da480d888',6)
INSERT INTO USERS VALUES(7,'george','9b306ab04ef5e25f9fb89c998a6aedab',7)
INSERT INTO USERS VALUES(8,'betty','82b054bd83ffad9b6cf8bdb98ce3cc2f',8)
INSERT INTO USERS VALUES(9,'rod','52c59993d8e149a1d70b65cb08abf692',9)
INSERT INTO USERS VALUES(10,'harold','c57f431343f100b441e268cc12babc34',10)
INSERT INTO USERS VALUES(11,'peter','51dc30ddc473d43a6011e9ebba6ca770',11)
INSERT INTO USERS VALUES(12,'jean','b71985397688d6f1820685dde534981b',12)
INSERT INTO USERS VALUES(13,'jeff','166ee015c0e0934a8781e0c86a197c6e',13)
INSERT INTO USERS VALUES(14,'maria','263bce650e68ab4e23f28263760b9fa5',14)
INSERT INTO USERS VALUES(15,'david','172522ec1028ab781d9dfd17eaca4427',15)
INSERT INTO USERS VALUES(16,'carlos','dc599a9972fde3045dab59dbd1ae170b',16)
    

The encoded passwords are the same as the appropriate user name. They may be changed later.

Adding the Authentication Model

Now we add Credential.java in src/main/java/org/springframework/samples/petclinic/ with the following content. We implement the org.springframework.security.userdetails.UserDetails interface as later we will use Spring-Security for login.

      
package org.springframework.samples.petclinic;

import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.providers.encoding.Md5PasswordEncoder;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.util.StringUtils;

public class Credential extends BaseEntity implements UserDetails {

    private static final GrantedAuthority[] USER_AUTHORITIES = {new GrantedAuthorityImpl("ROLE_USER")};
    
    private String username;
    private String password;
    private Person user;
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }
    
    protected void setPassword(String password) {
        this.password = password;
    }
    
    public String getNewPassword() {
        return "new password";
    }
    
    public void setNewPassword(String password) {
        if (StringUtils.hasText(password)) {
            setPassword(new Md5PasswordEncoder().encodePassword(password, null));
        }
    }
    
    public Person getUser() {
        return user;
    }
    
    public void setUser(Person user) {
        this.user = user;
    }

    public GrantedAuthority[] getAuthorities() {
        return USER_AUTHORITIES;
    }

    public boolean isEnabled() {
        return true;
    }

    public boolean isAccountNonExpired() {
        return true;
    }

    public boolean isAccountNonLocked() {
        return true;
    }

    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    public boolean equals(Object object) {
        return object instanceof Credential? super.equals(object): false;
    }
}
      
    

We change Person.java in src/main/java/org/springframework/samples/petclinic/ to link to the credentials.

Add

    private Credential credential;

    public Credential getCredential() {
        return credential;
    }
    
    public void setCredential(Credential credential) {
        this.credential = credential;
    }
    

We have added the table PERSONS and USERS and added the class Credential. Now we must reflect this in our O/R-Mapping.

Change orm.xml (in src/main/resources/META-INF/)

      
	<mapped-superclass class="Person">
		<attributes>
			<basic name="firstName">
				<column name="FIRST_NAME"/>
			</basic>
			<basic name="lastName">
				<column name="LAST_NAME"/>
			</basic>
		</attributes>
	</mapped-superclass>
      
    

into

      
	<entity class="Person">
		<table name="PERSONS"/>
		<inheritance strategy="JOINED"/>
		<attributes>
			<basic name="firstName">
				<column name="FIRST_NAME"/>
			</basic>
			<basic name="lastName">
				<column name="LAST_NAME"/>
			</basic>
			<one-to-one name="credential" target-entity="Credential" mapped-by="user" fetch="EAGER">
				<cascade>
					<cascade-all/>
				</cascade>
			</one-to-one>
		</attributes>
	</entity>

	<entity class="Credential">
		<table name="USERS"/>
		<attributes>
			<basic name="username">
				<column name="USERNAME"/>
			</basic>
			<basic name="password">
				<column name="PASSWORD"/>
			</basic>
			<one-to-one name="user" target-entity="Person" fetch="EAGER">
				<join-column name="PERSON_ID"/>
				<cascade>
					<cascade-all/>
				</cascade>
			</one-to-one>
			<transient name="newPassword"/>
			<transient name="authorities"/>
			<transient name="enabled"/>
			<transient name="accountNonExpired"/>
			<transient name="accountNonLocked"/>
			<transient name="credentialsNonExpired"/>
		</attributes>
	</entity>
      
    

Change the password

Our application is now able to store usernames and passwords, but there is no possibility to input or change it.

We modify the ownerForm.jsp (in src/main/webapp/WEB-INF/jsp/) to achive both. We add the fields for username and password between the phone number and the buttons.

Change

      
    <tr>
      <th>
        Telephone: <form:errors path="telephone" cssClass="errors"/>
        <br/>
        <form:input path="telephone" size="20" maxlength="20"/>
      </th>
    </tr>
    <tr>
      <td>
        <c:choose>
          <c:when test="${owner.new}">
            <p class="submit"><input type="submit" value="Add Owner"/></p>
          </c:when>
          <c:otherwise>
            <p class="submit"><input type="submit" value="Update Owner"/></p>
          </c:otherwise>
        </c:choose>
      </td>
    </tr>
      
    

into

      
    <tr>
      <th>
        Telephone: <form:errors path="telephone" cssClass="errors"/>
        <br/>
        <form:input path="telephone" size="20" maxlength="20"/>
      </th>
    </tr>
    <tr>
      <th>
        <c:choose>
          <c:when test="${owner.new}">
            Username: <form:errors path="credential.username" cssClass="errors"/>
            <br/>
            <form:input path="credential.username" size="20" maxlength="20"/>
          </c:when>
          <c:otherwise>
            Username: ${owner.credential.username}
          </c:otherwise>
        </c:choose>
      </th>
    </tr>
    <tr>
      <th>
        Password: <form:errors path="credential.newPassword" cssClass="errors"/>
        <br/>
        <form:password path="credential.newPassword" size="20" maxlength="20"/>
      </th>
    </tr>
    <tr>
      <td>
        <c:choose>
          <c:when test="${owner.new}">
            <p class="submit"><input type="submit" value="Register User"/></p>
          </c:when>
          <c:otherwise>
            <p class="submit"><input type="submit" value="Update Owner"/></p>
          </c:otherwise>
        </c:choose>
      </td>
    </tr>
      
    

Now we modify AddOwnerForm.java and EditOwnerForm.java (in src/main/java/org/springframework/samples/petclinic/web/) to actually store username and password.

In AddOwnerForm.java change

      
    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(Model model) {
        Owner owner = new Owner();
        model.addAttribute(owner);
        return "ownerForm";
    }
      
    

into

      
    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(Model model) {
        Owner owner = new Owner();
        Credential credential = new Credential();
        owner.setCredential(credential);
        credential.setUser(owner);
        model.addAttribute(owner);
        model.addAttribute(credential); //This is for the pre-authentication filter later
        return "ownerForm";
    }
      
    

In EditOwnerForm.java change

      
@Controller
@RequestMapping("/editOwner.do")
@SessionAttributes(types = Owner.class)
public class EditOwnerForm {
    ...
}
      
    

into

      
@Controller
@RequestMapping("/editOwner.do")
@SessionAttributes("owner")
public class EditOwnerForm {
    ...
}
      
    

and

      
    @InitBinder
    public void setAllowedFields(WebDataBinder dataBinder) {
        dataBinder.setDisallowedFields(new String[] {"id"});
    }
      
    

into

      
    @InitBinder
    public void setAllowedFields(WebDataBinder dataBinder) {
        dataBinder.setDisallowedFields(new String[] {"id", "username"});
    }
      
    

Additionally we want to check that the user did not input an empty password (especially when the user is created, otherwise the current password can be taken), so we have to change the OwnerValidator.java (in src/main/java/org/springframework/samples/petclinic/validation).

Add the following checks to the end of the validate method.

      
        if (!StringUtils.hasLength(owner.getCredential().getUsername())) {
            errors.rejectValue("credential.username", "required", "required");
        }
        if (owner.getCredential().isNew() && !StringUtils.hasLength(owner.getCredential().getPassword())) {
            errors.rejectValue("credential.newPassword", "required", "required");
        }
      
    

Integrating Spring Security

We now need a simple login mechanism. Since we want to use Spring-Security, we create a credential-service that implements the org.springframework.security.userdetails.UserDetailsService interface.

We create a package security (by creating the folder src/main/java/org/springframework/samples/petclinic/security/) and put CredentialService.java into it.

The content of CredentialService.java follows:

      
package org.springframework.samples.petclinic.security;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.dao.DataAccessException;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public class CredentialService implements UserDetailsService {

    @PersistenceContext
    private EntityManager em;
    
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        try {
            Query query = this.em.createQuery("SELECT credential FROM Credential credential "
                                            + "INNER JOIN FETCH credential.user "
                                            + "WHERE credential.username = :username");
            query.setParameter("username", username);
            return (UserDetails)query.getSingleResult();
        } catch (NoResultException e) {
            throw new UsernameNotFoundException(username, e);
        }
    }
}
      
    

We add the security configuration to the Spring application context (src/main/webapp/WEB-INF/applicationContext-jpa.xml).

First change

      
<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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
			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-3.0.xsd
			http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
      
    

into

      
<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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="
			http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
			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-3.0.xsd
			http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
			http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
			http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
      
    

Now add the following configuration at the end of applicationContext-jpa.xml.

      
    <sec:http auto-config="true">
        <sec:intercept-url pattern="/addOwner.do" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <sec:intercept-url pattern="/*.do" access="ROLE_USER" />
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <sec:form-login login-page="/login.jsp"/>
        <sec:logout />
    </sec:http>

	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider user-service-ref="userService" >
        	<sec:password-encoder hash="md5"/>
    	</sec:authentication-provider>
    </sec:authentication-manager>
	
    <bean id="userService" class="org.springframework.samples.petclinic.security.CredentialService"/>
      
    

With this configuration access to our pages is allowed only for users with the role ROLE_USER (which are all owners and vets). Only access to our login page and to our page to add owners is allowed for everyone. The latter is needed to enable new users to register.

Now we need a login page. We create the file login.jsp in src/main/webapp/ with the following content.

      
<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter" %>
<%@ page import="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" %>
<%@ page import="org.springframework.security.core.AuthenticationException" %>
<%@ include file="/WEB-INF/jsp/includes.jsp" %>
<%@ include file="/WEB-INF/jsp/header.jsp" %>

    <form name="f" action="<c:url value='j_spring_security_check'/>" method="POST">
      <table>
        <tr><td>User:</td><td><input type='text' name='j_username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>'/></td></tr>
        <tr><td>Password:</td><td><input type='password' name='j_password'></td></tr>
        <tr><td><input type="checkbox" name="_spring_security_remember_me"></td><td>Don't ask for my password for two weeks</td></tr>

        <tr><td colspan='2'><p class="submit"><input name="submit" type="submit"></p></td></tr>
        <tr><td colspan='2'><p class="submit"><input name="reset" type="reset"></p></td></tr>
      </table>

    </form>

  <table class="footer">
    <tr>
      <td><a href="<c:url value="/addOwner.do"/>">Register</a></td>
    </tr>
  </table>

  </div>
</body>

</html>
      
    

With this changes we now are able to log in any user. We now also can improve the creation of users: We are now able to check whether a username does already exist and we can automatically log in a newly created user. We have to inject the UserDetailsService we just created into the AddOwnerForm (in src/main/java/org/springframework/samples/petclinic/web/AddOwnerForm.java)

First we add an attribute UserDetailsService and inject it via the constructor. Change

      
    @Autowired
    public AddOwnerForm(Clinic clinic) {
        this.clinic = clinic;
    }
      
    

into

      
    private final UserDetailsService userDetailsService;

    @Autowired
    public AddOwnerForm(Clinic clinic,
                        UserDetailsService userDetailsService) {
        this.clinic = clinic;
        this.userDetailsService = userDetailsService;
    }
      
    

Now we can check, whether the username does already exist. In processSubmit after

      
        new OwnerValidator().validate(owner, result);
      
    

add

      
        try {
            userDetailsService.loadUserByUsername(owner.getCredential().getUsername());
            result.rejectValue("credential.username", "alreadyExists", "already exists");
        } catch (UsernameNotFoundException e) {
            //all right, the user does not already exist
        }
      
    

To automatically log in the new user, modify the else block in processSubmit.

Change

      
        else {
            this.clinic.storeOwner(owner);
            status.setComplete();
            return "redirect:owner.do?ownerId=" + owner.getId();
        }
      
    

into

      
        else {
            this.clinic.storeOwner(owner);
            Credential credential = owner.getCredential();
            Authentication authentication
                = new UsernamePasswordAuthenticationToken(credential, credential, credential.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            status.setComplete();
            return "redirect:welcome.do";
        }
      
    

Don't forget to add the required imports.

When we are logged in we would expect a personal greeting and a direct link to our pets.

For this we add the required data to the model in ClinicController.java in (src/main/java/org/springframework/samples/petclinic/web/).

Change

      
	@RequestMapping("/welcome.do")
	public void welcomeHandler() {
	}
      
    

into

      
    @RequestMapping("/welcome.do")
    public ModelMap welcomeHandler() {
        Credential credential = (Credential)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        ModelMap model = new ModelMap("person", credential.getUser());
        model.addAttribute("vet", credential.getUser() instanceof Vet);
        model.addAttribute("owner", credential.getUser() instanceof Owner);
        return model;
    }
      
    

And we add the data to welcome.jsp (from src/main/webapp/WEB-INF/jsp/).

Change

      
<h2><fmt:message key="welcome"/></h2>

<ul>
  <li><a href="<c:url value="/findOwners.do"/>">Find owner</a></li>
  <li><a href="<c:url value="/vets.do"/>">Display all veterinarians</a></li>
  <li><a href="<c:url value="/html/petclinic.html"/>">Tutorial</a></li>
  <li><a href="<c:url value="/docs/index.html"/>">Documentation</a></li>
</ul>
      
    

into

      
<h2><fmt:message key="welcome"/> ${person.firstName} ${person.lastName}</h2>

<ul>
  <c:choose>
    <c:when test="${vet}">
      <li><a href="<c:url value="/vet.do?vetId=${person.id}"/>">Personal information</a></li>
      <li><a href="<c:url value="/findOwners.do"/>">Find owner</a></li>
    </c:when>
    <c:when test="${owner}">
      <p>&nbsp;</p>
      <li><a href="<c:url value="/owner.do?ownerId=${person.id}"/>">Personal information</a></li>
    </c:when>
  </c:choose>
  <li><a href="<c:url value="/vets.do"/>">All veterinarians</a></li>
</ul>
      
    

You also may add a logout-link to every page by modifying footer.jsp (from src/main/webapp/WEB-INF/jsp/).

Change

      
      <td align="right"><img src="<c:url value="/images/springsource-logo.png"/>"/></td>
      
    

into

      
      <td align="right"><a href='<c:url value="j_spring_security_logout"/>'>Logout</a></td>
      
    

The last thing we have to do is to add the Spring-Security filter to our web.xml (in src/main/webapp/WEB-INF/).

Add

      
    <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>
      
    

When you now start the application you have to login to access the pages. Remember the login data we have put into the database (the password was the same as the username) or use the Register link from the login-page to register a new owner.


Previous: Domain Model Up: Tutorial Next: Authorization