Skip to content

Getting Started With Spring Lemon

Sanjay Patel edited this page Jul 1, 2022 · 84 revisions

Official Documentation now available!

What is Spring Lemon

Spring Lemon is a library containing the essential configurations and code needed for developing REST APIs and microservices (both reactive and non-reactive) using Spring Framework and Spring Boot. It also contains a production-grade, extensible user module having features like sign up, sign in, social signup/in, stateless JWT token authentication, verify email, update profile, forgot password, change password, change email, captcha validation etc..

You can know more about Spring Lemon here.

About this guide

Click here to get the video version FREE for now.

In this guide, we are going to walk through developing an example RESTful JSON Web Service using Spring Lemon. The finished application would be similar to the Lemon Demo JPA application. For seeing it in action, you can use the Demo AngularJS front-end application.

Watch this video to see how the finished application is going to look like.

Getting started with Spring Lemon Reactive: In this guide we'll focus on getting started with the non-reactive JPA module. But the reactive demo is quite similar. After going through this demo, just looking at the Lemon Demo Reactive application would give a complete idea of getting started with the reactive module. However, please note that OAuth2 Login and filter level CORS are experimental features for these, although they are quite stable and you're encouraged to use those and report any errors. Similarly, for getting started with reactive microservices, refer to this project and this configuration repository.

Prerequisites

To follow this guide, you must have some prior knowledge of Spring Framework’s Dependency Injection, Java configuration, Boot, MVC, Security and Spring Data JPA. If you want to learn these first, we'd highly recommend modules I, II and III of our Spring Framework Rapid Tutorial For Real World Development course.

Getting updates

Spring Lemon is a highly active project, and so is this guide. Do Sign Up to receive updates!

Help and Support

Refer here.

Creating A New Project

Follow these steps to create a new project and add Spring Lemon to it.

1. Create a new Spring Boot project

Create a new Spring Boot project using your favorite method. If you are using Spring Tool Suite (STS), you can use the Spring Starter Project Wizard, which is available at File -> New -> Spring Starter Project.

Fill in the following data there:

  • Name: The name of your application, e.g. lemon-demo
  • Type: Maven Project (Or gradle)
  • Packaging: Jar
  • Java Version: 1.8
  • Group: The group name, e.g. com.naturalprogrammer.spring
  • Artifact: The artifact name, e.g. lemon-demo
  • Description: A one line description about your application
  • Package Name: Could be something like com.naturalprogrammer.spring.lemondemo
  • Boot Version: Choose a version later than or equal to 2.4.1. We have tested it with 2.4.1.
  • Dependencies:
    • SQL: Say HSQLDB in-memory database for this demo (but you can very well choose MySQL, PostgreSQL or whatever you want)
    • Core: Optionally DevTools
    • No need to include other dependencies, e.g. Security, JPA, Mail or Web; those would be transitively included by Spring Lemon.

Leave other fields to defaults.

2. Add Spring Lemon Dependency

Add to your pom.xml (or build.gradle) the following dependency:

<dependencies>
    ...
    <dependency>
        <groupId>com.naturalprogrammer.spring-lemon</groupId>
        <artifactId>spring-lemon-jpa</artifactId>
        <version>1.0.2</version> <!-- See https://github.com/naturalprogrammer/spring-lemon/releases for latest release -->
    </dependency>

<dependencies>

Configuring Spring Lemon

Now that we have added Spring Lemon to our project, time to configure it.

Setting up application.yml

Rename application.properties inside your src/main/resources folder to application.yml, and paste into that the following:

# Spring related properties
spring:

  # database settings
  jpa:
    database: HSQL
    hibernate.ddl-auto: update

  security:
    oauth2:
      client:
        provider:
          facebook:
            user-info-uri: https://graph.facebook.com/me?fields=email,name 
        registration:
          google:
            client-id: 1011974249454-6gq0hr01gqh3cndoqnss5r69tkk2nd84.apps.googleusercontent.com
            client-secret: saDA6Cj60wipncFM-hzBD-C6
          facebook:
            client-id: 548349525905412
            client-secret: 15a20c560c4c780dabdc0e637c02087a

  # JSON serialization settings
  jackson:
    default-property-inclusion: NON_NULL
    
    serialization:
      write-null-map-values: false 
      
    deserialization:
      accept-single-value-as-array: true
  
  devtools:
    # Comment this if you want the app to restart
    # when source code changes
    restart.enabled: false
    livereload.enabled: false

  main.allow-bean-definition-overriding: true

server.servlet.session.persistent: false

logging:
  level:
    root: INFO
    com.naturalprogrammer: DEBUG
  #file: D:\\tmp\\dev-log.txt

lemon:

  # application-url: http://localhost:9000
  # oauth2-authentication-success-url: http://localhost:9000/social-login-success?token=

  # First ADMIN user
  admin:
    username: [email protected]
    password: admin!
    
   # Spring Lemon flags
   # enabled:
      # json-prefix: false
      
  cors:
    # Comma separated values of CORS allowedOrigins
    # If this property is not given, CORS is not configured
    allowed-origins: http://localhost:9000
    
  recaptcha:
    sitekey: 6LdwxRcUAAAAABkhOGWQXhl9FsR27D5YUJRuGzx0
    secretkey: 6LdwxRcUAAAAADaG0Eo1qkYCco15cnngiBoBt2IO
  
  jwt:
    # An aes-128-cbc key generated at https://asecuritysite.com/encryption/keygen (take the "key" field)
    secret: 841D8A6C80CBA4FCAD32D5367C18C53B
    # expiration-millis: 864000000 # 10 days
    # short-lived-millis: 120000   # two minutes

  # Properties to be passed to client
  shared:
    fooBar: 123...

Notice above the following:

  1. You may like to use a persistent database instead of HSQL. In such case, you'll need to create a database and replace the datasource details with yours. The following are examples for MySQL and Postgres configurations:
    # MySQL
    jpa:
      hibernate:
        ddl-auto: update
        use-new-id-generator-mappings: false
      properties:
        hibernate:
          dialect: org.hibernate.dialect.MySQL5InnoDBDialect
    datasource:
      url: jdbc:mysql://localhost:3306/lemon?useSSL=false
      username: lemon
      password: lemon
    
    # Postgres
    jpa:
      hibernate:
        use-new-id-generator-mappings: false
      properties:
        hibernate.jdbc.lob.non_contextual_creation: true
    datasource:
      url: jdbc:postgresql://localhost:5432/lemondb
      username: lemon
      password: lemon
    
  2. The CORS allowed-origins property is needed if you plan to have web clients (e.g. an AngularJS client) hosted cross origin.
  3. By providing recaptcha sitekey and secretkey, we tell Spring Lemon to support Google reCAPTCHA validation. If you don't provide these properties, captcha validation won't be supported. The sitekey and secretkey given above would work only at localhost. You should replace those with your keys in production.
  4. See how facebook and google OAuth2 clients are configured. That tells Spring Lemon to support google and facebook signup/in. The client-id and client-secret values given above would work only at localhost. You should replace those with your keys in production.
  5. The application-url property provides the base url of your web front-end (e.g. an AngularJS application). The default value is http://localhost:9000.
  6. The oauth2-authentication-success-url property is used for building the URL to redirect the user to after successful facebook/google sign in. The default value is http://localhost:9000/social-login-success?token=.
  7. When an application is installed, it's helpful to have its database initialized with an ADMIN user. The admin.username and admin.password become the credentials of that first administrator. At application startup, Spring Lemon will check if that user exists. If not, the user will be created, with ADMIN rights.
  8. jwt.secret is used for signing and encrypting JWT tokens. Spring Lemon uses Nimbus JWE tokens with shared key.
  9. jwt.expiration-millis tells how many milliseconds the JWT tokens would be valid. Default is 864000000 (10 days)
  10. jwt.short-lived-millis tells how many milliseconds shortlived tokens should be valid. Default is 120000 (2 minutes)

More details are covered in our Spring Framework Recipes For Real World Application Development book.

Optional - Sending Emails

Spring Lemon comes with a MailSender service for sending emails. To configure that to use an SMTP mail sending platform like GMail, add the following to application.yml:

spring:
  mail:
    host: smtp.gmail.com
    username: [email protected]
    password: xxxxxx

    properties:
      mail:
        smtp:
          auth: true
          ssl.enable: true
          socketFactory:
            port: 465
            class: javax.net.ssl.SSLSocketFactory
            fallback: false

The above configuration works with GMail, provided you have enabled Google 2-step Verification, and the password is an application password. The properties might differ for other SMTP services.

If you skip the above configuration, Spring Lemon will just write the email verification and forgot password mails onto the log, which may be fine for a demo.

Spring Lemon also allows you to configure a MailSender based on non-SMTP services, e.g. AWS SES. Details are covered in our Spring Framework Recipes For Real World Application Development book.

Optional - Spring Lemon flags

Spring Lemon provides some flags to enable/disable certain of its features. Defaults would be fine for most of the applications. But, just to give you an example, if you want to enable JSON vulnerablity protection, add the following property to application.yml:

lemon.enabled.json-prefix: true

Providing I18n messages

Spring Lemon uses I18n error messages for bean validation errors. Hence, create a file named ValidationMessages.properties inside src\main\resources, and paste the following into that:

com.naturalprogrammer.spring.blank.email: Email needed
com.naturalprogrammer.spring.invalid.email: Not a well formed email address
com.naturalprogrammer.spring.invalid.email.size: Email must be between {min} and {max} characters
com.naturalprogrammer.spring.duplicate.email: Email Id already used
com.naturalprogrammer.spring.wrong.captcha: Looks like you are a robot! Please try again.

com.naturalprogrammer.spring.invalid.password.size: Password must be between {min} and {max} characters

com.naturalprogrammer.spring.different.passwords: Passwords do not match
com.naturalprogrammer.spring.blank.password: Password needed

Similarly, for other custom messages, create messages.properties inside src\main\resources, and paste the following into that:

com.naturalprogrammer.spring.validationError: Validation Error

com.naturalprogrammer.spring.verifySubject: Please verify your email id
com.naturalprogrammer.spring.verifyEmail: Hi,<br/><br/>Your email id at XYZ is unverified. Please click the link below to get verified:<br/><br/>{0}<br/><br/>

com.naturalprogrammer.spring.alreadyVerified: Already verified
com.naturalprogrammer.spring.wrong.verificationCode: Wrong verification code

com.naturalprogrammer.spring.wrong.login: Wrong login
com.naturalprogrammer.spring.notFound: Not found
com.naturalprogrammer.spring.userNotFound: User {0} not found

com.naturalprogrammer.spring.forgotPasswordSubject: Reset Password
com.naturalprogrammer.spring.forgotPasswordEmail: Please click <a href="{0}">here</a> to reset your password.

com.naturalprogrammer.spring.versionException: {0} {1} is already modified by somebody else. Please refresh and try again.

com.naturalprogrammer.spring.wrong.password: Wrong password

com.naturalprogrammer.spring.changeEmailSubject: Want to change your email?
com.naturalprogrammer.spring.changeEmailEmail: Please click <a href="{0}">here</a> to change your email.

com.naturalprogrammer.spring.wrong.changeEmailCode: Could not change email. Already changed?
com.naturalprogrammer.spring.duplicate.email: Email Id already used
com.naturalprogrammer.spring.blank.newEmail: No new email found. Looks like you have already changed.

com.naturalprogrammer.spring.oauth2EmailNeeded: {0} didn't provide any email id. OAuth2 scopes configured properly?
com.naturalprogrammer.spring.oauth2EmailNotVerified: Can't proceed because your email isn't yet verified at {0}

com.naturalprogrammer.spring.wrong.audience: Wrong token audience
com.naturalprogrammer.spring.obsoleteToken: Token has become obsolete
com.naturalprogrammer.spring.expiredToken: Expired token

com.naturalprogrammer.spring.notGoodAdminOrSameUser: Only a good Admin or same user is permitted for this operation

com.naturalprogrammer.spring.blank: Please provide {0}

com.naturalprogrammer.spring.userClaimAbsent: User claim absent in the authorization token
com.naturalprogrammer.spring.fullTokenNotAllowed: Full authorization tokens aren't allowed here

Extending Spring Lemon

Spring Lemon comes with a few base classes, which you need to extend and configure.

AbstractUser

Spring Lemon comes with an AbstractUser entity, which you need to extend as below:

import javax.persistence.Entity;
import javax.persistence.Table;

import com.naturalprogrammer.spring.lemon.domain.AbstractUser;

@Entity
@Table(name="usr")
public class User extends AbstractUser<Long> {

    private static final long serialVersionUID = 2716710947175132319L;   
}

The Long generic parameter above is the type of the primary key.

In the next section, we are going see how to add new a field to this class. For more details, refer our Spring Framework Recipes For Real World Application Development book.

LemonController

Spring Lemon API endpoints are coded in the abstract LemonController class. Extend it as below:

import com.naturalprogrammer.spring.lemon.LemonController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/core")
public class MyController extends LemonController<User, Long> {

}

LemonService

Spring Lemon comes with an abstract LemonService class, which has the service methods used by LemonController. Extend it as below:

import com.naturalprogrammer.spring.lemon.LemonService;

@Service
public class MyService extends LemonService<User, Long> {

    @Override
    public User newUser() {
        return new User();
    }

    @Override
    public Long toId(String id) {
        
        return Long.valueOf(id);
    }
}

LemonService has modular methods that you can override. At the least, you need to override the newUser and toId methods, as above. We are going to do a couple of more overrides in the next section, and more details are discussed in our Spring Framework Recipes For Real World Application Development book.

AbstractUserRepository

Finally, you need to extend Spring Lemon's AbstractUserRepository, as below:

import com.naturalprogrammer.spring.lemon.domain.AbstractUserRepository;

public interface UserRepository extends AbstractUserRepository<User, Long> {

}

Customizing the user module

Whatever we have coded so far is already a complete API with a great user module. But, its users have only email and password, and no name! So, let's add a name field.

Adding a name to the User entity

Add the following lines to the User class that we had coded previously:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

import com.fasterxml.jackson.annotation.JsonView;
import com.naturalprogrammer.spring.lemon.commons.util.UserUtils.SignupInput;
import com.naturalprogrammer.spring.lemon.commons.util.UserUtils.SignUpValidation;
import com.naturalprogrammer.spring.lemon.commons.util.UserUtils.UpdateValidation;
import com.naturalprogrammer.spring.lemon.domain.AbstractUser;
...
public class User ...
...
    public static final int NAME_MIN = 1;
    public static final int NAME_MAX = 50;

    @JsonView(SignupInput.class)
    @NotBlank(message = "{blank.name}", groups = {SignUpValidation.class, UpdateValidation.class})
    @Size(min=NAME_MIN, max=NAME_MAX, groups = {SignUpValidation.class, UpdateValidation.class})
    @Column(nullable = false, length = NAME_MAX)
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

Note the following:

  1. @JsonView(SignupInput.class) tells Spring Lemon to recognize this field when signing up.
  2. groups = {SignUpValidation.class, UpdateValidation.class} tells Spring Lemon to apply the validation while signing up or updating profile.

The @NotBlank annotation above expects a blank.name entry in messages.properties. So, add the following line there:

blank.name: Name required

Spring Lemon stores loggedin-user data in a UserDto object. To add our name field to that, add some more lines in User:

    
    public static class Tag implements Serializable {
		
		private static final long serialVersionUID = -2129078111926834670L;
	
		private String name;
	
		public String getName() {
		    return name;
		}
	
		public void setName(String name) {
	            this.name = name;
		}
    }

    @Override
    public Tag toTag() {
		
		Tag tag = new Tag();
		tag.setName(name);
		return tag;
    }

For more details, refer our Spring Framework Recipes For Real World Application Development book.

Extracting name from Google, Facebook

For extracting name when a user does Google or Facebook signup, add the following lines to MyService that we coded previously:

    @Override
    public void fillAdditionalFields(String registrationId, User user, Map<String, Object> attributes) {
    	
    	String nameKey;
    	
    	switch (registrationId) {
    		
    	case "facebook":
    	case "google":
		nameKey = StandardClaimNames.NAME;
		break;
			
	default:
		throw new UnsupportedOperationException("Fetching name from " + registrationId + " login not supprrted");
    	}
    	
    	user.setName((String) attributes.get(nameKey));
    }

Updating name

For updating name when updating profile, add the following method to MyService:

    @Override
    protected void updateUserFields(User user, User updatedUser, UserDto currentUser) {

        super.updateUserFields(user, updatedUser, currentUser);

        user.setName(updatedUser.getName());

        LecjUtils.afterCommit(() -> {
            if (currentUser.getId().equals(user.getId()))
                currentUser.setTag(user.toTag());
        });
    }

As you see, the super method would be called first. Then, the name would be updated. Finally, if a user is updating his own profile, Spring Security's principal would also be updated.

Our Spring Framework Recipes For Real World Application Development book covers it in details. Also, a good way to get more familiar with Spring Lemon is to browse its source code. For example, if you are using STS, pressing F3 while your mouse is over super.updateUserFields(...) will show you its source code.

Adding name to the first admin user

If you remember, we told you that an ADMIN user is added at application startup if it doesn't already exist. To give it a name, add the following lines to MyService:

@Override
protected User createAdminUser() {

    User user = super.createAdminUser(); 
    user.setName("Administrator");
    return user;
}

If you are using a persistent database and tried running the application previously, the ADMIN user would already have been created in your local database. Unless that's deleted, the above code wouldn't be run by Spring Lemon, and so the name of the ADMIN user won't be set.

Your application is complete!

This completes your application! Here is the documentation of the API that you have just developed. This API can be used from single page JavaScript applications or any consumer. In fact, we have created a Demo AngularJS front-end application, which you can use to test your API out.

How to install and use the Demo AngularJS application

Installing the prerequisites

This project is built using yo angular generator. Below are the steps to install the development environment.

Installing Node.js

Install or upgrade Node.js by following steps given at https://nodejs.org.

Installing NPM, Yeoman, Bower and Grunt

Type the commands below in an ADMIN shell:

npm install --global npm
npm install --global yo bower grunt-cli

Use the commands below to verify that the installation was successful:

yo --version
bower --version
grunt --version

Checking out spring-lemon repository

Assuming git is installed on your machine, open a command prompt and cd to the folder under which you want to check out the spring-lemon repository. Then, run the following command:

git clone --depth 1 https://github.com/naturalprogrammer/spring-lemon.git

It will create a spring-lemon sub-directory and fetch the projects into it. cd to its lemon-demo-angularjs subdirectory:

cd spring-lemon\lemon-demo-angularjs

and then, give the following commands to fetch the Node.js and Bower dependencies:

npm install
bower install
npm install natives

Assuming the Lemon demo application is runnintg at http://localhost:8080, now give the following command to start the front-end:

grunt serve

This should start the front-end in a browser. Hitting Ctrl-C on the command prompt will stop it.

Got any issue?

  1. If you face any installation related issues with Node.js, Grunt, Bower, Yemoan or any of the related technologies, refer to the documentation of the respective project.
  2. If you find any bug in the code, see if a GitHub issue is already created, or else create one.

Next Steps

  1. Check the API that you have developed, using Postman or the Demo AngularJS front-end application.
  2. Go through the Official Documentation
  3. Check our Lemon Demo JPA Application, which is quite similar to the one we just developed, but additionally has automated tests.
  4. Go through our Spring Framework Recipes For Real World Application Development book.
  5. Ask questions at stackoverflow.com (use spring-lemon tag), submit an issue for any bug or enhancement (please check first that the issue isn't already reported earlier), or seek for professional help.
  6. Be a committer! Mail to info at naturalprogrammer dot com.