100% found this document useful (1 vote)
842 views31 pages

Spring WebSocket With STOMP

This document discusses implementing WebSockets with Spring Boot using the STOMP protocol. It covers setting up the server-side with Spring Boot to enable WebSocket and STOMP messaging. It also covers building the WebSocket client by autowiring the Spring STOMP client and connecting, subscribing, and sending messages. Securing WebSockets and ensuring fallback options with SockJS are also briefly discussed.

Uploaded by

Day Night
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
842 views31 pages

Spring WebSocket With STOMP

This document discusses implementing WebSockets with Spring Boot using the STOMP protocol. It covers setting up the server-side with Spring Boot to enable WebSocket and STOMP messaging. It also covers building the WebSocket client by autowiring the Spring STOMP client and connecting, subscribing, and sending messages. Securing WebSockets and ensuring fallback options with SockJS are also briefly discussed.

Uploaded by

Day Night
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 31

Using Spring Boot for

WebSocket
Implementation with
STOMP
Table of Contents
Using Spring Boot for WebSocket Implementation with STOMP............................1
WebSocket and STOMP Protocols.............................................................................................2
The Server-side: Spring Boot and WebSockets.......................................................................3
Building the WebSocket Client....................................................................................................5
Securing WebSockets...................................................................................................................8
SockJS Fallback Options.............................................................................................................9
WebSocket in Production?..........................................................................................................9
UNDERSTANDING THE BASICS...............................................................................................10
What is STOMP?....................................................................................................................................10
What are WebSockets used for?....................................................................................................10
How does a WebSocket work?........................................................................................................10
What is Spring Boot and why it is used?....................................................................................10

The WebSocket protocol is one of the ways to make your application handle real-time
messages. The most common alternatives are long polling and server-sent events.
Each of these solutions has its advantages and drawbacks. In this article, I am going
to show you how to implement WebSockets with the Spring Boot Framework. I will
cover both the server-side and the client-side setup, and we will use STOMP over
WebSocket protocol to communicate with each other.

The server-side will be coded purely in Java. But, in the case of the client, I will show
snippets written both in Java and in JavaScript (SockJS) since, typically, WebSockets
clients are embedded in front-end applications. The code examples will demonstrate
how to broadcast messages to multiple users using the pub-sub model as well as how
to send messages only to a single user. In a further part of the article, I will briefly
discuss securing WebSockets and how we can ensure that our WebSocket-based
solution will stay operational even when the environment does not support the
WebSocket protocol.

Please note that the topic of securing WebSockets will only briefly be touched on here
since it is a complex enough topic for a separate article. Due to this, and several other
factors that I touch on in the WebSocket in Production? section in the end, I
recommend making modifications before using this setup in production,
read until the end for a production-ready setup with security measures in place.

WebSocket and STOMP Protocols

The WebSocket protocol allows you to implement bidirectional communication


between applications. It is important to know that HTTP is used only for the initial
handshake. After it happens, the HTTP connection is upgraded to a newly opened
TCP/IP connection that is used by a WebSocket.

The WebSocket protocol is a rather low-level protocol. It defines how a stream of


bytes is transformed into frames. A frame may contain a text or a binary message.
Because the message itself does not provide any additional information on how to
route or process it, It is difficult to implement more complex applications without
writing additional code. Fortunately, the WebSocket specification allows using of
sub-protocols that operate on a higher, application level. One of them, supported by
the Spring Framework, is STOMP.

STOMP is a simple text-based messaging protocol that was initially created for

scripting languages such as Ruby, Python, and Perl to connect to enterprise message
brokers. Thanks to STOMP, clients and brokers developed in different languages can
send and receive messages to and from each other. The WebSocket protocol is
sometimes called TCP for Web. Analogically, STOMP is called HTTP for Web . It

defines a handful of frame types that are mapped onto WebSockets frames,
e.g.,  CONNECT ,  SUBSCRIBE ,  UNSUBSCRIBE ,  ACK , or  SEND . On one hand, these

commands are very handy to manage communication while, on the other, they allow
us to implement solutions with more sophisticated features like message
acknowledgment.

The Server-side: Spring Boot and WebSockets

To build the WebSocket server-side, we will utilize the Spring Boot framework which
significantly speeds up the development of standalone and web applications in Java.
Spring Boot includes the  spring-WebSocket  module, which is compatible with the

Java WebSocket API standard (JSR-356).


Implementing the WebSocket server-side with Spring Boot is not a very complex task
and includes only a couple of steps, which we will walk through one by one.

Step 1. First, we need to add the WebSocket library dependency.


<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-websocket </artifactId>
</dependency>

If you plan to use JSON format for transmitted messages, you may want to include
also the GSON or Jackson dependency. Quite likely, you may additionally need a
security framework, for instance, Spring Security.

Step 2. Then, we can configure Spring to enable WebSocket and STOMP messaging.
Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements
WebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints(StompEndpointRegistry
registry) {
registry.addEndpoint( "/mywebsockets" )
.setAllowedOrigins( "mydomain.com" )
.withSockJS();
}

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker( "/topic/" , "/queue/" );
config.setApplicationDestinationPrefixes( "/app" );
}
}

The method  configureMessageBroker  does two things:

1. Creates the in-memory message broker with one or more destinations for


sending and receiving messages. In the example above, there are two
destination prefixes defined:  topic  and  queue . They follow the convention

that destinations for messages to be carried on to all subscribed clients via the
pub-sub model should be prefixed with  topic . On the other hand,
destinations for private messages are typically prefixed by  queue .
2. Defines the prefix  app  that is used to filter destinations handled by methods
annotated with  @MessageMapping  which you will implement in a controller.

The controller, after processing the message, will send it to the broker.

How messages are handled on server-side (source: Spring documentation)


Going back to the snippet above—probably you have noticed a call to the
method  withSockJS() —it enables SockJS fallback options. To keep things short, it

will let our WebSockets work even if the WebSocket protocol is not supported by an
internet browser. I will discuss this topic in greater detail a bit further.
There is one more thing that needs clarifying—why we
call  setAllowedOrigins()  method on the endpoint. It is often required because the

default behavior of WebSocket and SockJS is to accept only same-origin requests. So,
if your client and the server-side use different domains, this method needs to be
called to allow the communication between them.
Step 3. Implement a controller that will handle user requests. It will broadcast
received message to all users subscribed to a given topic.
Here is a sample method that sends messages to the destination  /topic/news .
@MessageMapping ( "/news" )
@SendTo ( "/topic/news" )
public void broadcastNews(@Payload String message) {
return message;
}

Instead of the annotation  @SendTo , you can also


use  SimpMessagingTemplate  which you can autowire inside your controller.
@MessageMapping ( "/news" )
public void broadcastNews(@Payload String message) {
this .simpMessagingTemplate.convertAndSend( "/topic/news" , message)
}
In later steps, you may want to add some additional classes to secure your endpoints,
like  ResourceServerConfigurerAdapter  or  WebSecurityConfigurerAdapter

from the Spring Security framework. Also, it is often beneficial to implement the
message model so that transmitted JSON can be mapped to objects.

Building the WebSocket Client

Implementing a client is an even simpler task.

Step 1. Autowire Spring STOMP client.

@Autowired
private WebSocketStompClient stompClient;

Step 2. Open a connection.

StompSessionHandler sessionHandler = new CustmStompSessionHandler();


StompSession stompSession = stompClient.connect(loggerServerQueueUrl,
sessionHandler).get();

Once this is done, it is possible to send a message to a destination. The message will
be sent to all users subscribed to a topic.

stompSession.send( "topic/greetings" , "Hello new user" );

It is also possible to subscribe for messages.

session.subscribe( "topic/greetings" , this );

@Override
public void handleFrame(StompHeaders headers, Object payload) {
Message msg = (Message) payload;
logger.info( "Received : " + msg.getText()+ " from : " +
msg.getFrom());
}

Sometimes it is needed to send a message only to a dedicated user (for instance when
implementing a chat). Then, the client and the server-side must use a separate
destination dedicated to this private conversation. The name of the destination may
be created by appending a unique identifier to a general destination name,
e.g.,  /queue/chat-user123 . HTTP Session or STOMP session identifiers can be

utilized for this purpose.


Spring makes sending private messages a lot easier. We only need to annotate a
Controller’s method with  @SendToUser . Then, this destination will be handled
by  UserDestinationMessageHandler , which relies on a session identifier. On the
client-side, when a client subscribes to a destination prefixed with  /user , this

destination is transformed into a destination unique for this user. On the server-side,
a user destination is resolved based on a user’s  Principal .
Sample server-side code with  @SendToUser  annotation:
@MessageMapping ( "/greetings" )
@SendToUser ( "/queue/greetings" )
public String reply(@Payload String message,
Principal user) {
return "Hello " + message;
}

Or you can use  SimpMessagingTemplate :


String username = ...
this .simpMessagingTemplate.convertAndSendToUser();
username, "/queue/greetings" , "Hello " + username);

Let’s now look at how to implement a JavaScript (SockJS) client capable of receiving
private messages which could be sent by the Java code in the example above. It is
worth knowing that WebSockets are a part of HTML5 specification and are
supported by most modern browsers (Internet Explorer supports them since version
10).

function connect() {
var socket = new SockJS( '/greetings' );
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.subscribe( '/user/queue/greetings' , function (greeting)
{
showGreeting( JSON .parse(greeting.body).name);
});
});
}

function sendName() {
stompClient.send( "/app/greetings" , {}, $( "#name" ).val());
}

As you have probably noted, to receive private messages, the client needs to
subscribe to a general destination  /queue/greetings  prefixed with  /user . It does

not have to bother with any unique identifiers. However, the client needs to login to
the application before, so the  Principal  object on the server-side is initialized.

Securing WebSockets

Many web applications use cookie-based authentication. For instance, we can use
Spring Security to restrict access to certain pages or Controllers only to logged users.
User security context is then maintained through cookie-based HTTP session that is
later associated with WebSocket or SockJS sessions created for that user.
WebSockets endpoints can be secured as any other requests, e.g., in
Spring’s  WebSecurityConfigurerAdapter .

Nowadays, web applications often use REST APIs as their back-end and OAuth/JWT
tokens for user authentication and authorization. The WebSocket protocol does not
describe how servers should authenticate clients during HTTP handshake. In
practice, standard HTTP headers (e.g., Authorization) are used for this purpose.
Unfortunately, it is not supported by all STOMP clients. Spring Java’s STOMP client
allows to set headers for the handshake:
WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders();
handshakeHeaders.add(principalRequestHeader, principalRequestValue);

But the SockJS JavaScript client does not support sending authorization header with
a SockJS request. However, it allows for sending query parameters that can be used
to pass a token. This approach requires writing custom code in the server-side that
will read the token from the query parameters and validate it. It is also important to
make sure that tokens are not logged together with requests (or logs are well
protected) since this could introduce a serious security violation.

SockJS Fallback Options

Integration with WebSocket may not always go smoothly. Some browsers (e.g., IE 9)
do not support WebSockets. What is more, restrictive proxies may make it
impossible to perform the HTTP upgrade or they cut connections that are open for
too long. In such cases, SockJS comes to the rescue.

SockJS transports fall in three general categories: WebSockets, HTTP Streaming, and
HTTP Long Polling. The communication starts with SockJS sending  GET /info  to

obtain basic information from the server. Basing on the response, SockJS decides on
the transport to be used. The first choice are WebSockets. If they are not supported,
then, if possible, Streaming is used. If this option is also not possible, then Polling is
chosen as a transport method.

WebSocket in Production?

While this setup works, it isn’t the “best.” Spring Boot allows you to use any full-
fledged messaging system with the STOMP protocol (e.g., ActiveMQ, RabbitMQ),
and an external broker may support more STOMP operations (e.g., acknowledges,
receipts) than the simple broker we used. STOMP Over WebSocket provides
interesting information about WebSockets and STOMP protocol. It lists messaging
systems that handle STOMP protocol and could be a better solution to use in
production. Especially if, due to the high number of requests, the message broker
needs to be clustered. (Spring’s simple message broker is not suitable for clustering.)
Then, instead of enabling the simple broker in  WebSocketConfig , it is required to

enable the Stomp broker relay that forwards messages to and from an external
message broker. To sum up, an external message broker may help you build a more
scalable and robust solution.
If you’re ready to continue your Java Developer journey exploring Spring Boot, try
reading Using Spring Boot for OAuth2 and JWT REST Protection next.

UNDERSTANDING THE BASICS

What is STOMP?

STOMP is the Simple (or Streaming) Text Oriented Messaging Protocol. It uses a set
of commands like CONNECT, SEND, or SUBSCRIBE to manage the conversation.
STOMP clients, written in any language, can talk with any message broker
supporting the protocol.

What are WebSockets used for?

WebSockets are typically used to make web applications more interactive. They can
be helpful when implementing social feeds, online chats, news updates, or location-
based apps.

How does a WebSocket work?


WebSockets provide bidirectional communication channel over a single TCP
connection. The client establishes a persistent connection through a process known
as the WebSocket handshake. The connection allows exchanging messages in real
time.

What is Spring Boot and why it is used?

Spring Boot is a Java-based framework that makes it easier to implement standalone


applications or microservices. It is commonly used because it greatly simplifies
integration with various products and frameworks. It also contains an embedded web
server so there is no need to deploy WAR files.

ABOUT THE AUTHOR

Tomasz is a creative developer with over ten years of experience at designing and implementing
Java applications. He is a team player, enthusiastic about learning new technologies and trying
out different ideas and approaches to process improvement. Tomasz has worked for
international companies (Hewlett-Packard) as well as for Silicon Valley startups. Currently he
helps small and medium companies to create Java-based solutions.

Using Spring Boot for


OAuth2 and JWT REST
Protection
This article is a guide on how to setup a server-side implementation of JSON Web
Token (JWT) - OAuth2 authorization framework using Spring Boot and Maven.
An initial grasp on OAuth2 is recommended and can be obtained reading the draft
linked above or searching for useful information on the web like this or this.
OAuth2 is an authorization framework superseding it first version OAuth, created
back in 2006. It defines the authorization flows between clients and one or more
HTTP services in order to gain access to protected resources.

OAuth2 defines the following server-side roles:

 Resource Owner: The service responsible for controlling resources’


access
 Resource Server: The service who actually supplies the resources
 Authorization Server: The service handling authorization process
acting as a middleman between client and resource owner
JSON Web Token, or JWT, is a specification for the representation of claims to be
transferred between two parties. The claims are encoded as a JSON object used as
the payload of an encrypted structure, enabling the claims to be digitally signed or
encrypted.

The containing structure can be JSON Web Signature (JWS) or JSON Web
Encryption (JWE).

JWT can be chosen as the format for access and refresh tokens used inside the
OAuth2 protocol.

OAuth2 and JWT gained a huge popularity over the last years because of the
following features:

 Provides a stateless authorization system for stateless REST protocol

 Fits well in a micro-service architecture in which multiple resource


servers can share a single authorization server

 Token content easy to manage on client’s side due to JSON format


However, OAuth2 and JWT are not always the best choice in case the following
considerations are important for the project:

 A stateless protocol doesn’t permit access revocation on the server side

 Fixed lifetime for token add additional complexity for managing long-
running sessions without compromising security (e.g. refresh token)

 A requirement for a secure store for a token on the client side

Expected Protocol Flow

While one of the main features of OAuth2 is the introduction of an authorization


layer in order to separate authorization process from resource owners, for the sake of
simplicity, the article’s outcome is the build of a single application impersonating
all resource owner, authorization server, and resource server roles. Because
of this, the communication will flow between two entities only, the server and the
client.
This simplification should help to focus on the aim of the article, i.e. the setup of such
a system in a Spring Boot’s environment.

The simplified flow is described below:

1. Authorization request is sent from client to server (acting as resource


owner) using password authorization grant
2. Access token is returned to the client (along with refresh token)
3. Access token is then sent from client to server (acting as resource
server) on each request for protected resource access

4. Server responds with required protected resources


Spring Security and Spring Boot

First of all, a brief introduction to the technology stack selected for this project.

The project management tool of choice is Maven, but due to the project’s simplicity,
it should not be difficult to switch to other tools like Gradle.
In the article’s continuation, we focus on Spring Security aspects only, but all code
excerpts are taken from a fully working server-side application which source code is
available in a public repository along with a client consuming its REST resources.

Spring Security is a framework providing an almost declarative security services for


Spring-based applications. Its roots are from the first beginning of Spring and it’s
organized as a set of modules due to the high number of different security
technologies covered.

Let’s take a quick look at Spring Security architecture (a more detailed guide can be
found here).
Security is mostly about authentication, i.e. the verification of the identity,
and authorization, the grant of access rights to resources.
Spring security supports a huge range of authentication models, either provided by
third parties or implemented natively. A list can be found here.
Regarding authorization, three main areas are identified:

1. Web requests authorization

2. Method level authorization

3. Access to domain object instances authorization

Authentication

The basic interface is  AuthenticationManager  which is responsible to provide an


authentication method. The  UserDetailsService  is the interface related to user’s

information collection, which could be directly implemented or used internally in


case of standard JDBC or LDAP methods.

Authorization

The main interface is  AccessDecisionManager ; which implementations for all three
areas listed above delegate to a chain of  AccessDecisionVoter . Each instance of the
latter interface represents an association between an  Authentication  (a user
identity, named principal), a resource and a collection of  ConfigAttribute , the set

of rules which describes how the resource’s owner allowed the access to the resource
itself, maybe through the use of user roles.
The security for a web application is implemented using the basic elements described
above in a chain of servlet filters, and the class  WebSecurityConfigurerAdapter  is

exposed as a declarative way to express resource’s access rules.


Method security is first enabled by the presence of
the  @EnableGlobalMethodSecurity(securedEnabled = true)  annotation, and
then by the use of a set of specialized annotations to apply to each method to be
protected such as  @Secured ,  @PreAuthorize , and  @PostAuthorize .

Spring Boot adds to all of this a collection of opinionated application configurations


and third-party libraries in order to ease the development while maintaining an high
quality standard.

JWT OAuth2 with Spring Boot

Let’s now move on the original problem to set up an application implementing


OAuth2 and JWT with Spring Boot.

While multiple server-side OAuth2 libraries exist in the Java world (a list can be
found here), the spring-based implementation is the natural choice as we expect to
find it well integrated into Spring Security architecture and therefore avoid the need
to handle much of the low-level details for its use.
All security-related library dependencies are handled by Maven with the help of
Spring Boot, which is the only component requiring an explicit version inside
maven’s configuration file pom.xml (i.e. library versions are automatically inferred
by Maven choosing the most up-to-date version compatible with the inserted Spring
Boot version).
Find below the excerpt from maven’s configuration file pom.xml containing the
dependencies related to Spring Boot security:
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId> spring-boot-starter-security </artifactId>
</dependency>
<dependency>
<groupId> org.springframework.security.oauth.boot </groupId>
<artifactId> spring-security-oauth2-autoconfigure </artifactId>
<version> 2.1.0.RELEASE </version>
</dependency>
The app acts both as OAuth2 authorization server/resource owner and as resource
server.

The protected resources (as resource server) are published under /api/ path, while
authentication path (as resource owner/authorization server) is mapped
to /oauth/token, following proposed default.
App’s structure:

 security  package containing security configuration

 errors  package containing error handling

 users ,  glee  packages for REST resources, including model,

repository, and controller


Next paragraphs cover the configuration for each one of the three OAuth2 roles
mentioned above. The related classes are inside  security  package:
 OAuthConfiguration ,

extending  AuthorizationServerConfigurerAdapter
 ResourceServerConfiguration ,

extending  ResourceServerConfigurerAdapter
 ServerSecurityConfig , extending  WebSecurityConfigurerAdapter

 UserService , implementing  UserDetailsService

Setup for Resource Owner and Authorization Server

Authorization server behavior is enabled by the presence


of  @EnableAuthorizationServer  annotation. Its configuration is merged with the

one related to the resource owner behavior and both are contained in the
class  AuthorizationServerConfigurerAdapter .

The configurations applied here are related to:

 Client access (using  ClientDetailsServiceConfigurer )


 Selection of use an in-memory or JDBC based storage for
client details with  inMemory  or  jdbc  methods

 Client’s basic authentication


using  clientId  and  clientSecret  (encoded with the
chosen  PasswordEncoder  bean) attributes

 Validity time for access and refresh tokens


using  accessTokenValiditySeconds  and  refreshTok
enValiditySeconds  attributes

 Grant types allowed


using  authorizedGrantTypes  attribute
 Defines access scopes with  scopes  method

 Identify client’s accessible resources

 Authorization server endpoint


(using  AuthorizationServerEndpointsConfigurer )

 Define the use of a JWT token


with  accessTokenConverter

 Define the use of


an  UserDetailsService  and  AuthenticationManager  

interfaces to perform authentication (as resource owner)


package net.reliqs.gleeometer.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.authentication.AuthenticationManager;
import
org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import
org.springframework.security.oauth2.config.annotation.configurers.Clie
ntDetailsServiceConfigurer;
import
org.springframework.security.oauth2.config.annotation.web.configuratio
n.AuthorizationServerConfigurerAdapter;
import
org.springframework.security.oauth2.config.annotation.web.configuratio
n.EnableAuthorizationServer;
import
org.springframework.security.oauth2.config.annotation.web.configurers.
AuthorizationServerEndpointsConfigurer;
import
org.springframework.security.oauth2.provider.token.store.JwtAccessToke
nConverter;

@Configuration
@EnableAuthorizationServer
public class OAuthConfiguration extends
AuthorizationServerConfigurerAdapter {

private final AuthenticationManager authenticationManager;

private final PasswordEncoder passwordEncoder;

private final UserDetailsService userService;

@Value ( "${jwt.clientId:glee-o-meter}" )
private String clientId;

@Value ( "${jwt.client-secret:secret}" )
private String clientSecret;

@Value ( "${jwt.signing-key:123}" )
private String jwtSigningKey;

@Value ( "${jwt.accessTokenValidititySeconds:43200}" ) // 12 hours


private int accessTokenValiditySeconds;

@Value ( "$
{jwt.authorizedGrantTypes:password,authorization_code,refresh_token}" )
private String[] authorizedGrantTypes;

@Value ( "${jwt.refreshTokenValiditySeconds:2592000}" ) // 30 days


private int refreshTokenValiditySeconds;

public OAuthConfiguration(AuthenticationManager
authenticationManager, PasswordEncoder passwordEncoder,
UserDetailsService userService) {
this .authenticationManager = authenticationManager;
this .passwordEncoder = passwordEncoder;
this .userService = userService;
}

@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.accessTokenValiditySeconds(accessTokenValiditySeconds)

.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.authorizedGrantTypes(authorizedGrantTypes)
.scopes( "read" , "write" )
.resourceIds( "api" );
}

@Override
public void configure(final AuthorizationServerEndpointsConfigurer
endpoints) {
endpoints
.accessTokenConverter(accessTokenConverter())
.userDetailsService(userService)
.authenticationManager(authenticationManager);
}

@Bean
JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new
JwtAccessTokenConverter();
return converter;
}

The next section describes the configuration to apply to the resource server.

Setup for Resource Server

The resource server behavior is enabled by the use


of  @EnableResourceServer  annotation and its configuration is contained in the
class  ResourceServerConfiguration .

The only needed configuration here is the definition of resource identification in


order to match the client’s access defined in the previous class.

package net.reliqs.gleeometer.security;

import org.springframework.context.annotation.Configuration;
import
org.springframework.security.oauth2.config.annotation.web.configuratio
n.EnableResourceServer;
import
org.springframework.security.oauth2.config.annotation.web.configuratio
n.ResourceServerConfigurerAdapter;
import
org.springframework.security.oauth2.config.annotation.web.configurers.
ResourceServerSecurityConfigurer;

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {

@Override
public void configure(ResourceServerSecurityConfigurer resources)
{
resources.resourceId( "api" );
}

The last configuration element is about the definition of web application security.

Web Security Setup

Spring web security configuration is contained in the class  ServerSecurityConfig ,


enabled by the use of  @EnableWebSecurity  annotation.
The  @EnableGlobalMethodSecurity  permits to specify security on the method
level. Its attribute  proxyTargetClass  is set in order to have this working
for  RestController ’s methods, because controllers are usually classes, not

implementing any interfaces.


It defines the following:

 The authentication provider to use, defining the


bean  authenticationProvider
 The password encoder to use, defining the bean  passwordEncoder

 The authentication manager bean

 The security configuration for the published paths


using  HttpSecurity
 Use of a custom  AuthenticationEntryPoint  in order to handle error

messages outside of standard Spring REST error


handler  ResponseEntityExceptionHandler
package net.reliqs.gleeometer.security;

import net.reliqs.gleeometer.errors.CustomAccessDeniedHandler;
import net.reliqs.gleeometer.errors.CustomAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.authentication.AuthenticationManager;
import
org.springframework.security.authentication.dao.DaoAuthenticationProvi
der;
import
org.springframework.security.config.annotation.method.configuration.En
ableGlobalMethodSecurity;
import
org.springframework.security.config.annotation.web.builders.HttpSecuri
ty;
import
org.springframework.security.config.annotation.web.configuration.Enabl
eWebSecurity;
import
org.springframework.security.config.annotation.web.configuration.WebSe
curityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity (prePostEnabled = true , proxyTargetClass =
true )
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter
{

private final CustomAuthenticationEntryPoint


customAuthenticationEntryPoint;

private final UserDetailsService userDetailsService;


public ServerSecurityConfig(CustomAuthenticationEntryPoint
customAuthenticationEntryPoint, @Qualifier("userService")
UserDetailsService userDetailsService) {
this .customAuthenticationEntryPoint =
customAuthenticationEntryPoint;
this .userDetailsService = userDetailsService;
}

@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new
DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super .authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATE
LESS)
.and()
.authorizeRequests()
.antMatchers( "/api/signin/**" ).permitAll()
.antMatchers( "/api/glee/**" ).hasAnyAuthority( "ADMIN" ,
"USER" )
.antMatchers( "/api/users/**" ).hasAuthority( "ADMIN" )
.antMatchers( "/api/**" ).authenticated()
.anyRequest().authenticated()

.and().exceptionHandling().authenticationEntryPoint(customAuthenticati
onEntryPoint).accessDeniedHandler( new CustomAccessDeniedHandler());
}

The code extract below is about the implementation


of  UserDetailsService  interface in order to provide the resource owner’s

authentication.
package net.reliqs.gleeometer.security;

import net.reliqs.gleeometer.users.User;
import net.reliqs.gleeometer.users.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import
org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import
org.springframework.security.core.userdetails.UserDetailsService;
import
org.springframework.security.core.userdetails.UsernameNotFoundExceptio
n;
import org.springframework.stereotype.Service;

@Service
public class UserService implements UserDetailsService {

private final UserRepository repository;

public UserService(UserRepository repository) {


this .repository = repository;
}

@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User user = repository.findByEmail(username).orElseThrow(() ->
new RuntimeException( "User not found: " + username));
GrantedAuthority authority = new
SimpleGrantedAuthority(user.getRole().name());
return new
org.springframework.security.core.userdetails.User(user.getEmail(),
user.getPassword(), Arrays.asList(authority));
}
}

The next section is about the description of a REST controller implementation in


order to see how security constraints are mapped.

REST Controller

Inside the REST controller we can find two ways to apply access control for each
resource method:

 Using an instance of  OAuth2Authentication  passed in by Spring as a

parameter
 Using  @PreAuthorize  or  @PostAuthorize  annotations
package net.reliqs.gleeometer.users;

import lombok.extern.slf4j.Slf4j;
import net.reliqs.gleeometer.errors.EntityNotFoundException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import
org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.ConstraintViolationException;
import javax.validation.Valid;
import javax.validation.constraints.Size;
import java.util.HashSet;

@RestController
@RequestMapping ( "/api/users" )
@Slf 4j
@Validated
class UserController {

private final UserRepository repository;

private final PasswordEncoder passwordEncoder;

UserController(UserRepository repository, PasswordEncoder


passwordEncoder) {
this .repository = repository;
this .passwordEncoder = passwordEncoder;
}

@GetMapping
Page<User> all(@PageableDefault(size = Integer.MAX_VALUE) Pageable
pageable, OAuth2Authentication authentication) {
String auth = (String)
authentication.getUserAuthentication().getPrincipal();
String role =
authentication.getAuthorities().iterator().next().getAuthority();
if (role.equals(User.Role.USER.name())) {
return repository.findAllByEmail(auth, pageable);
}
return repository.findAll(pageable);
}

@GetMapping ( "/search" )
Page<User> search(@RequestParam String email, Pageable pageable,
OAuth2Authentication authentication) {
String auth = (String)
authentication.getUserAuthentication().getPrincipal();
String role =
authentication.getAuthorities().iterator().next().getAuthority();
if (role.equals(User.Role.USER.name())) {
return repository.findAllByEmailContainsAndEmail(email,
auth, pageable);
}
return repository.findByEmailContains(email, pageable);
}

@GetMapping ( "/findByEmail" )
@PreAuthorize ( "!hasAuthority('USER') || (authentication.principal
== #email)" )
User findByEmail(@RequestParam String email, OAuth2Authentication
authentication) {
return repository.findByEmail(email).orElseThrow(() -> new
EntityNotFoundException(User .class, "email", email)) ;
}

@GetMapping ( "/{id}" )
@PostAuthorize ( "!hasAuthority('USER') || (returnObject != null &&
returnObject.email == authentication.principal)" )
User one(@PathVariable Long id) {
return repository.findById(id).orElseThrow(() -> new
EntityNotFoundException(User .class, "id", id.toString())) ;
}

@PutMapping ( "/{id}" )
@PreAuthorize ( "!hasAuthority('USER') || (authentication.principal
== @userRepository.findById(#id).orElse(new
net.reliqs.gleeometer.users.User()).email)" )
void update(@PathVariable Long id, @Valid @RequestBody User res) {
User u = repository.findById(id).orElseThrow(() -> new
EntityNotFoundException(User .class, "id", id.toString())) ;
res.setPassword(u.getPassword());
res.setGlee(u.getGlee());
repository.save(res);
}

@PostMapping
@PreAuthorize ( "!hasAuthority('USER')" )
User create(@Valid @RequestBody User res) {
return repository.save(res);
}

@DeleteMapping ( "/{id}" )
@PreAuthorize ( "!hasAuthority('USER')" )
void delete(@PathVariable Long id) {
if (repository.existsById(id)) {
repository.deleteById(id);
} else {
throw new EntityNotFoundException(User .class, "id",
id.toString()) ;
}
}

@PutMapping ( "/{id}/changePassword" )
@PreAuthorize ( "!hasAuthority('USER') || (#oldPassword != null && !
#oldPassword.isEmpty() && authentication.principal ==
@userRepository.findById(#id).orElse(new
net.reliqs.gleeometer.users.User()).email)" )
void changePassword(@PathVariable Long id, @RequestParam(required =
false) String oldPassword, @Valid @Size(min = 3) @RequestParam String
newPassword) {
User user = repository.findById(id).orElseThrow(() -> new
EntityNotFoundException(User .class, "id", id.toString())) ;
if (oldPassword == null || oldPassword.isEmpty() ||
passwordEncoder.matches(oldPassword, user.getPassword())) {
user.setPassword(passwordEncoder.encode(newPassword));
repository.save(user);
} else {
throw new ConstraintViolationException( "old password
doesn't match" , new HashSet<>());
}
}
}

Conclusion

Spring Security and Spring Boot permit to quickly set up a complete OAuth2
authorization/authentication server in an almost declarative manner. The setup can
be further shortened by configuring OAuth2 client’s properties directly
from  application.properties/yml  file, as explained in this tutorial.

All source code is available in this GitHub repository: spring-glee-o-meter. An


Angular client which consumes the published resources can be found in this GitHub
repository: glee-o-meter.

UNDERSTANDING THE BASICS

What is OAuth2?

OAuth2 is an authorization framework to enable a third-party application to obtain


limited access to an HTTP service through the sharing of an access token. Its
specification supersedes and obsoletes OAuth 1.0 protocol.

What is JWT?
JWT stands for JSON Web Token, a specification for the representation of claims to
be transferred between two parties. The claims are encoded as a JSON object used as
the payload of an encrypted structure which enables the claims to be digitally signed
or encrypted.

What is Spring Security?

Spring Security is a framework focused on providing authentication and


authorization to Spring-based applications.

What is Spring Boot?

Spring Boot is an opinionated view of the Spring platform and third-party libraries
which permits to minimize the configuration of Spring-based application while
maintaining production-grade quality level.

You might also like