dineshonjava

Spring Boot Actuator A Complete Guide

Hello friends!!! In this article, we’re going to introduce Spring Boot Actuators for collecting metrics about your production grade applications and also talk about some simple ways to work with them in production. Spring Boot Actuator is a sub-project of Spring Boot. It adds several production grade services to your application with little effort on your part. Lets discuss here about spring boot actuator and different types of metrics that can be collected using this feature.

Table of Contents
  1. What Is An Actuator?
  2. How to Enable an Actuator?
  3. Endpoints
  4. Custom Metric Data
  5. Create A New Endpoint
  6. A New Endpoint To List All Endpoints
  7. Actuator Example application
  8. Summary
1. What Is An Actuator?
Spring Boot Actuator is sub-project of Spring Boot and it is one of the nice feature that adds great value to the Spring Boot applications. It adds several production grade services to your application with little effort on your part. There are also has many features added to your application out-of-the-box for managing the service in a production (or other) environment. They’re mainly used to expose different types of information about the running application – health, metrics, info, dump, env etc.

2. How To Enable Spring Boot Actuator?
The spring-boot-actuator module provides all of Spring Boot’s production-ready features. The simplest way to enable the features is to add a dependency to the spring-boot-starter-actuator ‘Starter’.

2.1 In Maven Project
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

2.2 In Gradle Project
dependencies {
    compile("org.springframework.boot:spring-boot-starter-actuator")
}
3. Endpoints
Actuator endpoints allow you to monitor and interact with your application. Spring Boot includes a number of built-in endpoints and you can also add your own. Most endpoints are sensitive – meaning they’re not fully public – while a handful are not: /health and /info.

Here’s some of the most common endpoints Boot provides out of the box:

  • /actuator- Provides a hypermedia-based “discovery page” for the other endpoints. Requires Spring HATEOAS to be on the classpath. Sensitive by Default.
  • /autoconfig- Displays an auto-configuration report showing all auto-configuration candidates and the reason why they ‘were’ or ‘were not’ applied. Sensitive by Default.
  • /beans- Displays a complete list of all the Spring beans in your application. Sensitive by Default.
  • /configprops- This endpoint shows configuration properties used by your application. Sensitive by Default.
  • /dump- Performs a thread dump. Sensitive by Default.
  • /env- Exposes spring’s properties from the configurations. Sensitive by Default.
  • /health – Shows application health information (a simple ‘status’ when accessed over an unauthenticated connection or full message details when authenticated). It is sensitive by default.
  • /info – Displays arbitrary application info. Not sensitive by default.
  • /metrics – Shows ‘metrics’ information for the current application. It is also sensitive by default.
  • /mappings- Displays a list of all @RequestMapping paths. Sensitive by Default.
  • /shutdown- This endpoint allows to shutdown the application. This is not enabled by default. Sensitive by Default.
  • /trace – Displays trace information (by default the last few HTTP requests). Sensitive by Default.
  • /logfile- Provides access to the configured log files (This feature supported since Spring Boot 1.3.0). Sensitive by Default.
  • /flyway- This endpoint provides the details of any flyway database migrations have been applied. Sensitive by Default.
  • /liquibase- This endpoint provides the details of any liquibase database migrations have been applied. Sensitive by Default.
3.1 Customizing Existing Endpoints
Above listed each endpoint can be customized with properties using the following format:
endpoints.[endpoint name].[property to customize]
  • id – by which this endpoint will be accessed over HTTP
  • enabled – if true then it can be accessed otherwise not
  • sensitive – if true then need authorization to show crucial information over HTTP

You can change how those endpoints are exposed using application.properties, the most common settings:
  • management.port=8081 - you can expose those endpoints on port other than the one application is using (8081 here).
  • management.address=127.0.0.1 - you can only allow to access by IP address (localhost here).
  • management.context-path=/actuator - allows you to have those endpoints grouped under specified context path rather than root, i.e. /actuator/health.
  • endpoints.health.enabled=false - allows to enable/disable specified endpoint by name, here /health is disabled.
Securing endpoints
For security purposes, you might choose to expose the actuator endpoints over a non-standard port – the management.port property can easily be used to configure that. The information exposed by endpoints is most of the time sensitive. While /health is usually harmless to be exposed, /metrics would be too much. Fortunately, you can use Spring Security for that purpose.

Adding starter to class path by adding following dependency to pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
You can disable basic security it in application.properties, so that it leaves only the sensitive Actuator endpoints secured and leaves the rest open for access:
security.basic.enabled=false
You can secure these endpoints by defining the default security properties – user name, password and role – in application.properties file:
security.user.name=admin
security.user.password=secret
management.security.role=SUPERUSER
In case you're using the security features across the application and decided to secure those endpoints yourself, you can disable default security for Actuator:
management.security.enabled=false
3.2 Health information- /health Endpoint
Health information can be used to check the status of your running application. It is often used by monitoring software to alert someone if a production system goes down. The default information exposed by the health endpoint depends on how it is accessed.
http://localhost:8080/health
{
"status" : "UP"
}
This health information is collected from all the beans implementing HealthIndicator interface configured in your application context.

3.3 Security with HealthIndicators
Some information returned by HealthIndicator is sensitive in nature – but you can configure endpoints.health.sensitive=false to expose the other information like diskspace, datasource etc.
endpoints.health.sensitive=false
You can write your custom health indicator to provide additional details to the user. The following are the list of health indication implementation classes available for customization:

  • DiskSpaceHealthIndicator
  • DataSourceHealthIndicator
  • MongoHealthIndicator
  • RabbitHealthIndicator
  • SolrHealthIndicator

The basic configurations for health endpoint is:
endpoints.health.id=health
endpoints.health.sensitive=true
endpoints.health.enabled=true
Customized Health checkup:
The basic idea for health checks is that they can provide more insightful information to you on the application's health. Besides checking if the application is UP or DOWN, which is done by default, you can add checks for things like database connectivity or whatever suits you. This is in fact what is being done when you add other Spring Boot starters, as they often provide additional health checks.
/**
 * 
 */
package com.dineshonjava.sba;

import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;

/**
 * @author Dinesh.Rajput
 *
 */
public class MyAppHealth implements HealthIndicator{

 @Override
 public Health health() {
  int errorCode = check(); // perform some specific health check
  if (errorCode != 0) {
   return Health.down().withDetail("Error Code", errorCode).build();
  }
  return Health.up().build();
 }

 private int check() {
           // Your logic to check health
  return 0;
 }

}
As you can see all it takes is to create a bean implementing HealthIndicator with a method health() returning appropriate Health object. The checks you create will appear on the /health endpoint, so the application can be monitored for them. The output will be:
http://localhost:8080/health
{
status: "UP",
diskSpace: {
status: "UP",
total: 240721588224,
free: 42078715904,
threshold: 10485760
}
}
3.4 Application information- /info Endpoint
Application information exposes various information collected from all InfoContributor beans defined in your ApplicationContext. Spring Boot includes a number of auto-configured InfoContributors and you can also write your own. The default value for sensitive is false. There is no harm in exposing this details as that is common details that has to be exposed to others.

You can also customize the data shown by /info endpoint – for example: Configuration in application.properties file:
{
endpoints.info.id=info
endpoints.info.sensitive=false
endpoints.info.enabled=true

info.app.name=Spring Boot Actuator Application
info.app.description=This is my first Working Spring Actuator Examples
info.app.version=0.0.1-SNAPSHOT
And the sample output:
http://localhost:8080/info
{
{
app: {
version: "0.0.1-SNAPSHOT",
description: "This is my first Working Spring Actuator Examples",
name: "Spring Boot Actuator Application"
}
}
3.5 Metrics Information-/metrics Endpoint
The metrics endpoint is one of the more important endpoints as it gathers and publishes information about OS, JVM and Application level metrics; out of the box, we get things like memory, heap, processors, threads, classes loaded, classes unloaded, thread pools along with some HTTP metrics as well. By default this endpoint is enabled under the HTTP URL /metrics.
he example configuration for this endpoint is:
{
endpoints.metrics.id=metrics
endpoints.metrics.sensitive=true
endpoints.metrics.enabled=true
Here’s what the output of this endpoint looks like out of the box:
http://localhost:8080/metrics
{
{
mem: 55470,
mem.free: 5398,
processors: 4,
instance.uptime: 9452,
uptime: 14466,
systemload.average: -1,
heap.committed: 35020,
heap.init: 16384,
heap.used: 29621,
heap: 253440,
nonheap.committed: 20800,
nonheap.init: 160,
nonheap.used: 20451,
nonheap: 0,
threads.peak: 17,
threads.daemon: 14,
threads.totalStarted: 20,
threads: 16,
classes: 6542,
classes.loaded: 6542,
classes.unloaded: 0,
gc.copy.count: 60,
gc.copy.time: 232,
gc.marksweepcompact.count: 2,
gc.marksweepcompact.time: 61,
httpsessions.max: -1,
httpsessions.active: 0
}

There are following multiple resources matrics exposed by spring boot actualtor
3.5.1. System Metrics
The following system metrics are exposed by Spring Boot:
  • The total system memory in KB (mem)
  • The amount of free memory in KB (mem.free)
  • The number of processors (processors)
  • The system uptime in milliseconds (uptime)
  • The application context uptime in milliseconds (instance.uptime)
  • The average system load (systemload.average)
  • Heap information in KB (heap, heap.committed, heap.init, heap.used)
  • Thread information (threads, thread.peak, thread.daemon)
  • Class load information (classes, classes.loaded, classes.unloaded)
  • Garbage collection information (gc.xxx.count, gc.xxx.time)
3.5.2. DataSource metrics
The following metrics are exposed for each supported DataSource defined in your application:
  • The number of active connections (datasource.xxx.active)
  • The current usage of the connection pool (datasource.xxx.usage).
3.5.3 Cache metrics
The following metrics are exposed for each supported cache defined in your application:
  • The current size of the cache (cache.xxx.size)
  • Hit ratio (cache.xxx.hit.ratio)
  • Miss ratio (cache.xxx.miss.ratio)
By default, Spring Boot provides cache statistics for EhCache, Hazelcast, Infinispan, JCache and Guava.
3.5.4 Tomcat session metrics
If you are using Tomcat as your embedded servlet container, session metrics will automatically be exposed. The httpsessions.active and httpsessions.max keys provide the number of active and maximum sessions.

there are many more matrics you can following spring boot docs for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/.

3.6 Endpoint – Logfile
This endpoint will be automatically enabled if logging.path or logging.file properties are configured in the application.properties file. When you access the logile endpoint, this will return the complete log of the application.

4. Custom Metric Data
To record your own metrics inject a CounterService and/or GaugeService into your bean. The CounterService exposes increment, decrement and reset methods; the GaugeService provides a submit method.

For example – we’ll customize the login flow to record a successful and failed login attempt:
/**
 * 
 */
package com.dineshonjava.sba;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.stereotype.Service;

/**
 * @author Dinesh.Rajput
 *
 */
@Service
public class LoginService {
 
 
 CounterService counterService;
 
 @Autowired
 public LoginService(CounterService counterService) {
        this.counterService = counterService;
        doLogin();
    }
 
 public boolean login(String userName, String password) {
  boolean success;
  if (userName.equals("admin") && "secret".equals(password)) {
   counterService.increment("counter.login.success");
   success = true;
  }
  else {
   counterService.increment("counter.login.failure");
   success = false;
  }
  return success;
 }
 
 private void doLogin() {
  for(int i=0; i<10; i++){
   login("admin", "secret");
  }
  login("admin", "scret");
  login("admin", "scret");
 }
}

Here’s what the output might look like:
......
counter.login.failure: 2,
counter.login.success: 10
....
}
Besides CounterService the other one provided by default is GaugeService that is used to collect a single double value, i.e. a measured execution time. You can also create and use your own implementations of these two.

5. Create A New Endpoint
Besides using the existing endpoints provided by Spring Boot – you can also create an entirely new endpoint. This is useful when you want to expose application details which are an added feature to your application.

/**
 * 
 */
package com.dineshonjava.sba;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.stereotype.Component;

/**
 * @author Dinesh.Rajput
 *
 */
@Component
public class MyCustomEndpoint implements Endpoint<List<String>>{

 @Override
 public String getId() {
  return "myCustomEndpoint";
 }

 @Override
 public List<String> invoke() {
  // Custom logic to build the output
        List<String> list = new ArrayList<String>();
        list.add("App message 1");
        list.add("App message 2");
        list.add("App message 3");
        list.add("App message 4");
        return list;
 }

 @Override
 public boolean isEnabled() {
  return true;
 }

 @Override
 public boolean isSensitive() {
  return true;
 }

}

The way to access this new endpoint is by its id, at /myCustomEndpoint.
http://localhost:8080/myCustomEndpoint
Output:
[
"App message 1",
"App message 2",
"App message 3",
"App message 4"
]
MyCustomEndpoint class implements Endpoint. Any class of type Endpoint will be exposed as an endpoint in the server If you look at the methods, getId(), isEnabled() and isSensitive(), which are the properties that are overridden by the application.properties file. invoke() is the important method responsible for writing the message.

6. A New Endpoint To List All Endpoints
It is quite useful if you have an endpoint to display all the endpoints in a single web page. There is a built-in endpoint /actuator for this purpose, but you have to add HateOAS in the classpath to enable that feature. To implement this one, you’ll need to extend the AbstractEndpoint class:
/**
 * 
 */
package com.dineshonjava.sba;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.stereotype.Component;

/**
 * @author Dinesh.Rajput
 *
 */
@Component
@SuppressWarnings("rawtypes")
public class MyListEndpoints extends AbstractEndpoint<List<Endpoint>>{
 
 List<Endpoint> endpoints;
 
 @Autowired
    public MyListEndpoints(List<Endpoint> endpoints) {
        super("myListEndpoints");
        this.endpoints = endpoints;
    }
 
 @Override
 public List<Endpoint> invoke() {
  return this.endpoints;
 }

}

Here’s how the output will look like:
When you implement the above class, there will be a new endpoint “myListEndpoints” will be registered and exposed to the users. The output will be:
http://localhost:8080/myListEndpoints
[
{
id: "myCustomEndpoint",
enabled: true,
sensitive: true
},
{
id: "mappings",
sensitive: true,
enabled: true
},
{
id: "env",
sensitive: true,
enabled: true
},
{
id: "health",
sensitive: false,
enabled: true,
timeToLive: 1000
},
{
id: "beans",
sensitive: true,
enabled: true
},
{
id: "info",
sensitive: false,
enabled: true
},
{
id: "metrics",
sensitive: false,
enabled: true
},
{
id: "trace",
sensitive: true,
enabled: true
},
{
id: "dump",
sensitive: true,
enabled: true
},
{
id: "autoconfig",
sensitive: true,
enabled: true
},
{
id: "shutdown",
sensitive: true,
enabled: false
},
{
id: "configprops",
sensitive: true,
enabled: true
}
]

7. Actuator Example application
In this section we will make the application which have all required configuration. For creating the application we have used here Spring Boot Web Initializr with Maven Build and following dependencies in pom.xml.
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.dineshonjava.sba</groupId>
 <artifactId>SpringBootActuator</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>SpringBootActuator</name>
 <description>SpringBootActuator project for Spring Boot Actuator</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.4.0.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>


</project>

And below is project struct as we have finally.

Spring Boot Actuator

application.properties file for configuration for actuator
#/info endpoint configuration
endpoints.info.id=info
endpoints.info.sensitive=false
endpoints.info.enabled=true
info.app.name=Spring Boot Actuator Application
info.app.description=This is my first Working Spring Actuator Examples
info.app.version=0.0.1-SNAPSHOT

#/metrics endpoint configuration
endpoints.metrics.id=metrics
endpoints.metrics.sensitive=false
endpoints.metrics.enabled=true

#securing endpoints by spring security
security.basic.enabled=true
security.user.name=admin
security.user.password=secret

#/health endpoint configuration (Comment when you are using customized health check)
endpoints.health.id=health
endpoints.health.sensitive=false
endpoints.health.enabled=true

#Management for endpoints
management.port=8080
management.context-path=/
management.security.enabled=true

Whole Code: Here I am not going put all required files for this application you can find whole application from following git repository.
https://github.com/DOJ-SoftwareConsultant/SpringBootActuator


8. Summary:
In this tutorial we had a first look at the interesting Actuator functionality provided by Spring Boot. I have explained about the basic concepts on actuators, endpoints, list of endpoints, creating custom endpoints, health check,metrics and provided a complete working example application for spring boot actuator.

Spring Boot Actuator Video: by Baeldung


Happy Spring Boot Learning!!!


@Async in Spring

In this tutorial we will discuss the asynchronous execution support in Spring and the @Async annotation. There are cases in which it is necessary to execute pieces of code asynchronous. An example is the sending of a (JMS) message from your system to another system. The advantage is that the user does not have to wait in the front-end while the message is being send. Another example of possible asynchronous execution is the case where messages have a clear ordering.
Annotating a method of a bean with @Async will make it execute in a separate thread i.e. the caller will not wait for the completion of the called method.

Enable Async Support
The @EnableAsync annotation switches on Spring’s ability to run @Async methods in a background thread pool. For Enabling asynchronous processing with Java configuration got by simply adding the @EnableAsync to a configuration class:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
Asynchronous processing can also be enabled using XML configuration – by using the task namespace:
<task:executor id="myexecutor" pool-size="5"  />
<task:annotation-driven executor="myexecutor"/>

Limitations for @Async

  1. it must be applied to public methods only
  2. self invocation – calling the async method from within the same class – won’t work

The reasons are simple – the method needs to be public so that it can be proxied. And self-invocation doesn’t work because it bypasses the proxy and calls the underlying method directly.

The @Async Annotation
Annotation that marks a method as a candidate for asynchronous execution. Can also be used at the type level, in which case all of the type's methods are considered as asynchronous.

1. Methods with void Return Type
Following is the simple way to configure a method with void return type to run asynchronously:
@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. "
      + Thread.currentThread().getName());
}
2. Methods With Return Type
@Async can also be applied to a method with return type – by wrapping the actual return in a Future:
@Service
public class GitHubLookupService {

    RestTemplate restTemplate = new RestTemplate();

    @Async
    public Future<User> findUser(String user) throws InterruptedException {
        System.out.println("Looking up " + user);
        User results = restTemplate.getForObject("https://api.github.com/users/" + user, User.class);
        // Artificial delay of 1s for demonstration purposes
        Thread.sleep(1000L);
        return new AsyncResult<User>(results);
    }

}
Spring also provides a AsyncResult class which implements Future. This can be used to track the result of asynchronous method execution.

Now, let’s invoke the above method and retrieve the result of the asynchronous process using the Future object
public void testAsyncAnnotationForMethodsWithReturnType()
    throws InterruptedException, ExecutionException {
    System.out.println("Invoking an asynchronous method. "
      + Thread.currentThread().getName());
    Future<User> future = GitHubLookupService.findUser(user);
 
    while (true) {
        if (future.isDone()) {
            System.out.println("Result from asynchronous process - " + future.get());
            break;
        }
        System.out.println("Continue doing something else. ");
        Thread.sleep(1000);
    }
}

The Executor
By default Spring uses a SimpleAsyncTaskExecutor to actually run these methods asynchronously. The defaults can be overridden at two levels – at the application level or at the individual method level.
1. Override the Executor at the Method Level
@Configuration
@EnableAsync
public class SpringAsyncConfig {
     
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}
Then the executor name should be provided as an attribute in @Async:
@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
    System.out.println("Execute method with configured executor - "
      + Thread.currentThread().getName());
}
2. Override the Executor at the Application Level

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
     
    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolTaskExecutor();
    }
     
}
The configuration class should implement the AsyncConfigurer interface – which will mean that it has the implement the getAsyncExecutor() method. It’s here that we will return the executor for the entire application – this now becomes the default executor to run methods annotated with @Async.

Exception Handling
We’ll create a custom async exception handler by implementing AsyncUncaughtExceptionHandler interface. The handleUncaughtException() method is invoked when there are any uncaught asynchronous exceptions:
public class CustomAsyncExceptionHandler  implements AsyncUncaughtExceptionHandler {
 
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception message - " + throwable.getMessage());
        System.out.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value - " + param);
        }
    }
     
}
When a method return type is a Future, exception handling is easy – Future.get() method will throw the exception.

But, if the return type is void, exceptions will not be propagated to the calling thread. Hence we need to add extra configurations to handle exceptions.

we also need to override the getAsyncUncaughtExceptionHandler() method to return our custom asynchronous exception handler:
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new CustomAsyncExceptionHandler();
}

Summary:
Congratulations! You’ve learn an asynchronous service. And also we have seen the how to enable async call in spring framework.


Happy Spring Learning.

Spring Related Topics you may like
  1. Spring Interview Questions and Answers
  2. Spring AOP Interview Questions and Answers
  3. Spring MVC Interview Questions
  4. Spring Security Interview Questions and Answers
  5. Spring REST Interview Questions and Answers
  6. Spring Boot Interview Questions and Answers
  7. Spring Boot Microservices Interview Questions and Answers
  8. Dependency Injection (DI) in Spring
  9. Spring IoC Container
  10. What is Bean Factory in Spring
  11. ApplicationContext in Spring
  12. Bean Autowiring in Spring
  13. Spring Bean Scopes
  14. Create Custom Bean Scope in Spring Example
  15. Using ApplicationContextAware in Spring
  16. Spring Bean Life Cycle and Callbacks
  17. BeanPostProcessor in Spring
  18. BeanFactoryPostProcessor in Spring
  19. Annotations in Spring and Based Configuration
  20. Spring JSR-250 Annotations
  21. JSR 330 Annotations in Spring
  22. Spring @Component, @Repository, @Service and @Controller Stereotype Annotations
  23. Method injection with Spring using Lookup method property
  24. Spring AOP-Introduction to Aspect Oriented Programming
  25. @Aspect Annotation in Spring
  26. Spring AOP AspectJ @Before Annotation Advice Example
  27. Spring AOP Before Advice Example using XML Config
  28. Spring AOP AspectJ @After Annotation Advice Example
  29. Spring AOP After Advice Example using XML Config
  30. Spring AOP AspectJ @AfterReturning Annotation Advice Example
  31. Spring AOP After-Returning Advice Example using XML Config
  32. Spring AOP AspectJ @AfterThrowing Annotation Advice Example
  33. Spring AOP After Throwing Advice Example using XML Config
  34. Spring AOP AspectJ @Around Annotation Advice Example
  35. Spring AOP Around Advice Example using XML Config
  36. Spring AOP Proxies in Spring
  37. Spring AOP Transaction Management in Hibernate
  38. Spring Transaction Management
  39. Spring Declarative Transaction Management Example
  40. Spring AOP-Ordering of Aspects with Example
  41. Spring Security Java Based Configuration with Example
  42. Spring Security XML Namespace Configuration Example


Spring Security using Spring Boot Example

Hello Friends!!! In this tutorial we will discuss the Spring Security with Spring Boot and also will see an example based on Spring security with Spring Boot.

1. Spring security Overview
Spring security is the highly customizable authentication and access-control framework. This is the security module for securing spring applications. But, this can also be used for non-spring based application with few extra configurations to enable the security features. Spring Security is a framework that focuses on providing both authentication and authorization to Java applications.
  • Authentication” is the process of establishing a principal is who they claim to be (a “principal” generally means a user, device or some other system which can perform an action in your application).
  • Authorization” refers to the process of deciding whether a principal is allowed to perform an action within your application. To arrive at the point where an authorization decision is needed, the identity of the principal has already been established by the authentication process. These concepts are common, and not at all specific to Spring Security.


2. Spring Security Modules
In Spring Security 3.0, the codebase has been sub-divided into separate jars which more clearly separate different functionality areas and third-party dependencies. The following are the list of modules currently shipped by spring security framework.

  • Core (spring-security-core.jar) – This module contains the APIs for basic authentication and access-control related mechanism. This is mandatory for ant spring security applications.
  • Remoting (spring-security-remoting.jar) – This module provides integration to the Spring Remoting. You don’t need to include this module unless you are writing remote client applications.
  • Web (spring-security-web.jar) – This module contains APIs for servlet filters and any web based authentication like access restriction for URLs. Any web application would require this module.
  • Config (spring-security-config.jar) - Contains the security namespace parsing code & Java configuration code. You need it if you are using the Spring Security XML namespace for configuration. If you are not using XML configurations, you can ignore this module.
  • LDAP (spring-security-ldap.jar)- Required if you need to use LDAP authentication or manage LDAP user entries.
  • ACL (spring-security-acl.jar) - Specialized domain object ACL implementation. Used to apply security to specific domain object instances within your application.
  • CAS (spring-security-cas.jar) - Spring Security’s CAS client integration. If you want to use Spring Security web authentication with a CAS single sign-on server.
  • OpenID (pring-security-openid.jar) - OpenID web authentication support. Used to authenticate users against an external OpenID server.
  • Test (spring-security-test.jar)- Support for testing with Spring Security.

3. Getting Spring Security
You can get hold of Spring Security in several ways.
3.1. Usage with Maven
A minimal Spring Security Maven set of dependencies typically looks like the following:
Pom.xml
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-web</artifactId>
 <version>4.1.2.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-config</artifactId>
 <version>4.1.2.RELEASE</version>
</dependency>
</dependencies>

3.2. Usage with Gradle
A minimal Spring Security Gradle set of dependencies typically looks like the following:
build.gradle
dependencies {
 compile 'org.springframework.security:spring-security-web:4.1.2.RELEASE'
 compile 'org.springframework.security:spring-security-config:4.1.2.RELEASE'
}

4. Core Components
Core Components represent the building blocks of spring security and what are the core components that are actually used while user is authenticating to your application. So if you ever need to go beyond a simple namespace configuration then it’s important that you understand what they are, even if you don’t actually need to interact with them directly.

4.1 SecurityContext
As the name implies, this interface is the corner stone of storing all the security related details for your application. When you enable spring security for your application, a SecurityContext will enable for each application and stores the details of authenticated user, etc. It uses Authentication object for storing the details related to authentications.

4.2 SecurityContextHolder
The most fundamental object is SecurityContextHolder. This class is important for accessing any value from the SecurityContext. This is where we store details of the present security context of the application, which includes details of the principal currently using the application. By default the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution, even if the security context is not explicitly passed around as an argument to those methods.
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}

4.3 Authentication
Let’s consider a standard authentication scenario that everyone is familiar with.
  1. A user is prompted to log in with a username and password.
  2. The system verifies that the password is correct for the username.
  3. The context information for that user is obtained their list of roles and so on.
  4. A security context is established for the user
  5. The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.


The following are the steps to achieve the authentication:

  1. Authentication is an interface which has several implementations for different authentication models. For a simple user name and password authentication, spring security would use UsernamePasswordAuthenticationToken. When user enters username and password, system creates a new instance of UsernamePasswordAuthenticationToken.
  2. The token is passed to an instance of AuthenticationManager for validation. Internally what AuthenticationManager will do is to iterate the list of configured AuthenticationProvider to validate the request. There should be at least one provider to be configured for the valid authentication.
  3. The AuthenticationManager returns a fully populated Authentication instance on successful authentication.
  4. The final step is to establish a security context by invoking SecurityContextHolder.getContext().setAuthentication(), passing in the returned authentication object.


4.4 UserDetailsService
UserDetails is a core interface in Spring Security. It represents a principal, but in an extensible and application-specific way. Think of UserDetails as the adapter between your own user database and what Spring Security needs inside the SecurityContextHolder. UserDetailsService is a core interface in spring security to load user specific data. This interface is considered as user DAO and will be implemented by specific DAO implementations. For example, for a basic in memory authentication, there is an InMemoryUserDetailsManager. This interface declares only one method loadUserByUsername (String username) which simplifies the implementation classes to write other specific methods.

Suppose you want to use your existing DAO classes to load the user details from the database, just implement the UserDetailsService and override the method loadUserByUsername(String username). An example of this implementation would look like this:
@Service
public class CurrentUserDetailsService implements UserDetailsService {
    private final UserService userService;
    @Autowired
    public CurrentUserDetailsService(UserService userService) {
        this.userService = userService;
    }
 
    public CurrentUser loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.getUserByUsername(username);
        return new CurrentUser(user);
    }
}
In the above code, the model CurrentUser must of of type org.springframework.security.userdetails.UserDetails. In the below example application, I have extended the org.springframework.security.userdetails.UserDetails class and written the custom user class. This will have the advantage of not exposing our domain class.

4.5 GrantedAuthority
Apart from authenticating to the application, another important component is to get the list of granted authorities for the logged in user. This comes as part of the authorization process. This is retrieved by calling the getAuthorities() in Authentication object. This returns the list of GrantedAuthority which denotes roles for the users. Such authorities are usually "roles", such as ROLE_ADMINISTRATOR orROLE_HR_SUPERVISOR. These roles are later on configured for web authorization, method authorization and domain object authorization.

Summary
The major building blocks of Spring Security that we’ve seen so far are:

  • SecurityContextHolder, to provide access to the SecurityContext.
  • SecurityContext, to hold the Authentication and possibly request-specific security information.
  • Authentication, to represent the principal in a Spring Security-specific manner.
  • GrantedAuthority, to reflect the application-wide permissions granted to a principal.
  • UserDetails, to provide the necessary information to build an Authentication object from your application’s DAOs or other source of security data.
  • UserDetailsService, to create a UserDetails when passed in a String-based username (or certificate ID or the like).

5. Spring Security Example Application
This section walks you through the process of creating a simple web application in Spring Boot with resources that are protected by Spring Security.
What you’ll build
You’ll build a Spring MVC application that secures the page with a login form backed by a fixed list of users. Let's start with the use cases for every common web application with some basic access restrictions. So, the requirements of such an app could be:

  • The app will have users, each with role Admin or User
  • They log in by their emails and passwords
  • Non-admin users can view their info, but cannot peek at other users
  • Admin users can list and view all the users, and create new ones as well
  • Customized form for login
  • "Remember me" authentication for laziest
  • Possibility to logout
  • Home page will be available for everyone, authenticated or not

Let’s create Web Project with Spring Boot Web Initialzr with Spring Web, Spring Data JPA, Spring Security and H2 Database. So following project structure we have after creating from web interface and adding necessary files for this example.
Spring Security Tutorial using Spring Boot

Let's discuss files of this example.
Dependencies pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

 <groupId>com.dineshonjava.sbsecurity</groupId>
 <artifactId>SpringBootSecurity</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <packaging>jar</packaging>

 <name>SpringBootSecurity</name>
 <description>SpringBootSecurity project for Spring Boot and Spring Security</description>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.4.0.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>


</project>

WebSecurityConfigurerAdapter
This is the Java configuration class for writing the web based security configurations. You can override the methods in this class to configure the following things:
  • Enforce the user to be authenticated prior to accessing any URL in your application
  • Create a user with the username user , password, and role of ROLE_USER
  • Enables HTTP Basic and Form based authentication
  • Spring Security will automatically render a login page and logout success page for you
SecurityConfig.java
/**
 * 
 */
package com.dineshonjava.sbsecurity.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author Dinesh.Rajput
 *
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
 
 @Autowired
    UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .authorizeRequests()
            .antMatchers("/", "/home", "/public/**").permitAll()
            .antMatchers("/users/**").hasAuthority("ADMIN")
            .anyRequest().fullyAuthenticated()
            .and()
        .formLogin()
            .loginPage("/login")
            .failureUrl("/login?error")
            .usernameParameter("email")
            .permitAll()
            .and()
        .logout()
         .logoutUrl("/logout")
            .logoutSuccessUrl("/")
            .permitAll();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }
}

Domain Classes
User.java

/**
 * 
 */
package com.dineshonjava.sbsecurity.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.dineshonjava.sbsecurity.role.Role;

/**
 * @author Dinesh.Rajput
 *
 */
@Entity
@Table(name = "user")
public class User implements Serializable{
 
 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "userid", nullable = false, updatable = false)
 Long userid;
 @Column(name = "username", nullable = false)
 String username;
 @Column(name = "email", nullable = false, unique = true)
 String email;
 @Column(name = "password", nullable = false)
 String password;
 @Column(name = "role", nullable = false)
 @Enumerated(EnumType.STRING)
 Role role;
 public Long getUserid() {
  return userid;
 }
 public void setUserid(Long userid) {
  this.userid = userid;
 }
 public String getUsername() {
  return username;
 }
 public void setUsername(String username) {
  this.username = username;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
 public Role getRole() {
  return role;
 }
 public void setRole(Role role) {
  this.role = role;
 }
 @Override
 public String toString() {
  return "User [userid=" + userid + ", username=" + username + ", email="
    + email + ", password=" + password + ", role=" + role + "]";
 }
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((email == null) ? 0 : email.hashCode());
  result = prime * result
    + ((password == null) ? 0 : password.hashCode());
  result = prime * result + ((role == null) ? 0 : role.hashCode());
  result = prime * result + ((userid == null) ? 0 : userid.hashCode());
  result = prime * result
    + ((username == null) ? 0 : username.hashCode());
  return result;
 }
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  User other = (User) obj;
  if (email == null) {
   if (other.email != null)
    return false;
  } else if (!email.equals(other.email))
   return false;
  if (password == null) {
   if (other.password != null)
    return false;
  } else if (!password.equals(other.password))
   return false;
  if (role != other.role)
   return false;
  if (userid == null) {
   if (other.userid != null)
    return false;
  } else if (!userid.equals(other.userid))
   return false;
  if (username == null) {
   if (other.username != null)
    return false;
  } else if (!username.equals(other.username))
   return false;
  return true;
 }
 
 
}

Role.java
/**
 * 
 */
package com.dineshonjava.sbsecurity.role;

/**
 * @author Dinesh.Rajput
 *
 */
public enum Role {
 USER, ADMIN
}

Besides that, a form for creating a new user will be nice to have:
UserBean.java
/**
 * 
 */
package com.dineshonjava.sbsecurity.bean;

import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.NotEmpty;

import com.dineshonjava.sbsecurity.role.Role;

/**
 * @author Dinesh.Rajput
 *
 */
public class UserBean {
 @NotEmpty
    private String username = "";
 
 @NotEmpty
    private String email = "";

    @NotEmpty
    private String password = "";

    @NotEmpty
    private String passwordRepeated = "";

    @NotNull
    private Role role = Role.USER;

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

 public String getPassword() {
  return password;
 }

 public void setPassword(String password) {
  this.password = password;
 }

 public String getPasswordRepeated() {
  return passwordRepeated;
 }

 public void setPasswordRepeated(String passwordRepeated) {
  this.passwordRepeated = passwordRepeated;
 }

 public Role getRole() {
  return role;
 }

 public void setRole(Role role) {
  this.role = role;
 }

 public String getUsername() {
  return username;
 }

 public void setUsername(String username) {
  this.username = username;
 }

}
This will function as a data transfer object (DTO) between the web layer and service layer. It's annotated by Hibernate Validator validation constraints and sets some sane defaults.

CurrentUser.java
/**
 * 
 */
package com.dineshonjava.sbsecurity.bean;

import org.springframework.security.core.authority.AuthorityUtils;

import com.dineshonjava.sbsecurity.model.User;
import com.dineshonjava.sbsecurity.role.Role;

/**
 * @author Dinesh.Rajput
 *
 */
public class CurrentUser extends org.springframework.security.core.userdetails.User {

 /**
  * 
  */
 private static final long serialVersionUID = 1L;
 
 private User user;
 
 public CurrentUser(User user) {
        super(user.getEmail(), user.getPassword(), AuthorityUtils.createAuthorityList(user.getRole().toString()));
        this.user = user;
    }

    public User getUser() {
        return user;
    }
    
    public Long getId() {
        return user.getUserid();
    }

    public Role getRole() {
        return user.getRole();
    }
    
    @Override
    public String toString() {
        return "CurrentUser{" +
                "user=" + user +
                "} " + super.toString();
}
}

Service Layer

In service layer, where the business logic should, we'd need something to retrieve the User by his id, email, list all the users and create a new one.
UserService .java
/**
 * 
 */
package com.dineshonjava.sbsecurity.service;

import java.util.Collection;

import com.dineshonjava.sbsecurity.bean.UserBean;
import com.dineshonjava.sbsecurity.model.User;

/**
 * @author Dinesh.Rajput
 *
 */
public interface UserService {
 
    User getUserById(long id);

    User getUserByEmail(String email);

    Collection<User> getAllUsers();

    User create(UserBean userBean);
}
UserServiceImpl .java
The implementation of the service:
/**
 * 
 */
package com.dineshonjava.sbsecurity.service;

import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import com.dineshonjava.sbsecurity.bean.UserBean;
import com.dineshonjava.sbsecurity.model.User;
import com.dineshonjava.sbsecurity.model.UserRepository;

/**
 * @author Dinesh.Rajput
 *
 */
@Service
public class UserServiceImpl implements UserService {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
 
 @Autowired
 UserRepository userRepository;
 
 @Override
 public User getUserById(long id) {
  LOGGER.debug("Getting user={}", id);
  return userRepository.findOne(id);
 }

 @Override
 public User getUserByEmail(String email) {
  LOGGER.debug("Getting user by email={}", email.replaceFirst("@.*", "@***"));
  return userRepository.findOneByEmail(email);
 }

 @Override
 public Collection<User> getAllUsers() {
  LOGGER.debug("Getting all users");
  return (Collection<User>) userRepository.findAll();
 }

 @Override
 public User create(UserBean userBean) {
  User user = new User();
  user.setUsername(userBean.getUsername());
        user.setEmail(userBean.getEmail());
        user.setPassword(new BCryptPasswordEncoder().encode(userBean.getPassword()));
        user.setRole(userBean.getRole());
        return userRepository.save(user);
 }

}

Spring Data Repository
This section explains the service classes and spring data repository implementation.
UserRepository .java
/**
 * 
 */
package com.dineshonjava.sbsecurity.model;

import org.springframework.data.repository.CrudRepository;

/**
 * @author Dinesh.Rajput
 *
 */
public interface UserRepository extends CrudRepository<User, Long>{
 
 User findOneByEmail(String email);
}
CurrentUserDetailsService.java
/**
 * 
 */
package com.dineshonjava.sbsecurity.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.dineshonjava.sbsecurity.bean.CurrentUser;
import com.dineshonjava.sbsecurity.model.User;

/**
 * @author Dinesh.Rajput
 *
 */
@Service
public class CurrentUserDetailsService implements UserDetailsService {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserDetailsService.class);
 
 @Autowired
 UserService userService;
 
 @Override
 public UserDetails loadUserByUsername(String email)
   throws UsernameNotFoundException {
  LOGGER.debug("Authenticating user with email={}", email.replaceFirst("@.*", "@***"));
  User user = userService.getUserByEmail(email);
  return new CurrentUser(user);
 }

}

Spring MVC Configurations Web Layer
Home Page
/**
 * 
 */
package com.dineshonjava.sbsecurity.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author Dinesh.Rajput
 *
 */
@Controller
public class HomeController {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(HomeController.class);
 
 @RequestMapping("/")
    public String getHomePage() {
  LOGGER.debug("Getting home page");
        return "home";
    }
}

Login Page
/**
 * 
 */
package com.dineshonjava.sbsecurity.controller;

import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author Dinesh.Rajput
 *
 */
@Controller
public class LoginController {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
 
 @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView getLoginPage(@RequestParam Optional<String> error) {
     LOGGER.debug("Getting login page, error={}", error);
        return new ModelAndView("login", "error", error);
    }
}

User Page
/**
 * 
 */
package com.dineshonjava.sbsecurity.controller;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.dineshonjava.sbsecurity.bean.UserBean;
import com.dineshonjava.sbsecurity.bean.validator.UserBeanValidator;
import com.dineshonjava.sbsecurity.service.UserService;

/**
 * @author Dinesh.Rajput
 *
 */
@Controller
public class UserController {
 
 private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
 
 @Autowired
 UserService userService;
 
 @Autowired
 UserBeanValidator userBeanValidator;
 
 @InitBinder("form")
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(userBeanValidator);
    }
 
 @RequestMapping("/users")
 public ModelAndView getUsersPage() {
  LOGGER.debug("Getting users page");
  return new ModelAndView("users", "users", userService.getAllUsers());
 }
 
 @PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)")
 @RequestMapping("/user/{id}")
    public ModelAndView getUserPage(@PathVariable Long id) {
  LOGGER.debug("Getting user page for user={}", id);
        return new ModelAndView("user", "user", userService.getUserById(id));
    }
 
 @PreAuthorize("hasAuthority('ADMIN')")
    @RequestMapping(value = "/user/create", method = RequestMethod.GET)
    public ModelAndView getUserCreatePage() {
      LOGGER.debug("Getting user create form");
        return new ModelAndView("user_create", "form", new UserBean());
    }
    
 @PreAuthorize("hasAuthority('ADMIN')")
    @RequestMapping(value = "/user/create", method = RequestMethod.POST)
    public String handleUserCreateForm(@Valid @ModelAttribute("form") UserBean form, BindingResult bindingResult) {
      LOGGER.debug("Processing user create form={}, bindingResult={}", form, bindingResult);
        if (bindingResult.hasErrors()) {
            return "user_create";
        }
        try {
            userService.create(form);
        } catch (DataIntegrityViolationException e) {
         LOGGER.warn("Exception occurred when trying to save the user, assuming duplicate email", e);
            bindingResult.reject("email.exists", "Email already exists");
            return "user_create";
        }
        return "redirect:/users";
    }
}

CurrentUserServiceImpl.java
/**
 * 
 */
package com.dineshonjava.sbsecurity.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.dineshonjava.sbsecurity.bean.CurrentUser;
import com.dineshonjava.sbsecurity.role.Role;

/**
 * @author Dinesh.Rajput
 *
 */
@Service
public class CurrentUserServiceImpl implements CurrentUserService {

 private static final Logger LOGGER = LoggerFactory.getLogger(CurrentUserDetailsService.class);

    @Override
    public boolean canAccessUser(CurrentUser currentUser, Long userId) {
        LOGGER.debug("Checking if user={} has access to user={}", currentUser, userId);
        return currentUser != null
                && (currentUser.getRole() == Role.ADMIN || currentUser.getId().equals(userId));
}

}


application.properties

#security.user.name=root
#security.user.password=111
#security.user.role=ADMIN
logging.level.org.springframework=WARN
logging.level.org.hibernate=WARN
logging.level.com.dineshonjava=DEBUG
spring.freemarker.template-loader-path=/WEB-INF/ftl
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-spring-macro-helpers=true

Here we are using freemarker template for views layers.

You can access this whole running project from below link.

https://github.com/DOJ-SoftwareConsultant/SpringBootSecurity


Happy Spring Boot Learning!!!:)