Aspect Oriented Programming(AOP) with Spring Boot Example - 2
This is the second post for a short series of aspect oriented programming with the spring boot. In this post, I am going to implement a simple project …
In these short series , I am going to dive into what Spring Security is, how Spring Security works. Most of the example application would be for web environment. Because I am going to use spring boot you should also use it or you have to to some setup to work with xml setup and others.
If you only need to see the code, here is the github link
Before diving into please create a new spring boot application which includes only these dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.mehmetozanguven</groupId>
<artifactId>springsecurityexample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springsecurityexample</name>
<description>Demo project for Spring Security using Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. It is a framework that focuses on providing both authentication and authorization to Java applications.
First, summarize the overall architecture:
AuthenticationFilter
pass your credentials (username:password) to the AuthenticationManager
. Therefore responsibility of AuthenticationFilter is just pass the credentials to the AuthenticationManagerAuthenticationProvider
s. (AuthenticationManager will find the proper AuthenticationProvider for the request(s))UserDetailsService
(UserDetailsService responsibility is the to find out the user in the database via credentials in the requests)PasswordEncoder
which is used by the AuthenticationProvider to implement authentication logic. Role of PasswordEncoder is to check whether password is correct or not. After UserDetailsService gets the details of the user from the database and these details also contain user’s password, then PasswordEncoder will try to match the passwords in the request(s) and the user’s details from the database.SecurityContext
That is the main architecture of Spring Security.
Now let’s create a controller and secure it.
package com.mehmetozanguven.springsecurityexample.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
@GetMapping("/test")
public String test(){
return "test";
}
}
If you run the application, because I am using the spring boot, spring-boot-starter-security dependency will protect the endpoint /test
, the reason is that this dependency called spring-boot-starter-security
added some default configuration for our spring application. Spring security will create a static username (which is user) and generated password in the console:
2020-12-27 16:41:07.760 INFO 18000 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 539983a1-3724-4fde-b757-da3818bc8f16
...
If you call this endpoint via curl (or postman), you will not access it:
curl -X GET http://localhost:8080/test | jq .
{
"timestamp": "2020-12-27T13:42:38.169+00:00",
"status": 401,
"error": "Unauthorized",
"message": "",
"path": "/test"
}
Because the default spring security configuration is the Basic Authentication, just add the basic authentication in your curl command:
Removed
.jq
pipeline because i am not returning json from the endpoint. jq is the command line JSON processor
curl --user user:539983a1-3724-4fde-b757-da3818bc8f16 -X GET http://localhost:8080/test
test
All these setups is done by BasicAuthenticationFilter
:
package org.springframework.security.web.authentication.www;
public class BasicAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// ....
if (authenticationIsRequired(username)) {
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
// That is the last step, writing the authenticated object to the SecurityContext
// to get back authenticated object for future logics..
SecurityContextHolder.getContext().setAuthentication(authResult);
}
// ....
}
}
Now let’s override the default configuration for spring security
Let’s create our own user instead of the generated one from spring security, for now I will store the user in the memory.
In my scenario I am just overriding the UserDetailsService
and do not forget that overriding the UserDetailsService forces us to override the PasswordEncoder
In default configuration, because there is no UserDetailsService and PasswordEncoder, these ones are generated by spring security.
There are multiple ways to configure spring security, I will start with basic one: creating the bean of type UserDetailsService:
package com.mehmetozanguven.springsecurityexample.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class ProjectBeanConfiguration {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
UserDetails testUser = User.withUsername("testUser")
.password("1234")
.authorities("read")
.build();
inMemoryUserDetailsManager.createUser(testUser);
return inMemoryUserDetailsManager;
}
}
In the configuration InMemoryUserDetailsManager
implements UserDetailsManager
interface which extends UserDetailsService
After that I am just creating the testUser of type UserDetails (that is the type spring security requests), and testUser has a username testUser with password: 1234 and one authority: read (testUser can read something)
After all I am just adding the testUser to the my InMemoryUserDetailsManager. At the end I just defined the my own UserDetailsService
Now, if I run the project, I will not see Using generated security password: … in the console, because I just told spring to use my own userDetailsService. However, because there is no default configuration for UserDetailsService there won’t be also default PasswordEncoder and if you run this curl command, you will get an exception:
curl --user testUser:1234 -X GET http://localhost:8080/test
and the exception will be:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:250) ~[spring-security-core-5.3.5.RELEASE.jar:5.3.5.RELEASE]
...
As I mentioned previously if you create your own UserDetailsService, you must create also PasswordEncoder, because there is no PasswordEncoder, authentication process fails.
Let’s create a passwordEncoder
@Configuration
public class ProjectBeanConfiguration {
@Bean
public UserDetailsService userDetailsService(){
// ...
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}
Don’t use NoOpPasswordEncoder in production, NoOpPasswordEncoder is provided for legacy and testing purposes only and is not considered secure.
Now let’s run the application again:
curl --user testUser:1234 -X GET http://localhost:8080/test
test
Now correct user can access the authenticated endpoints..
That’s the basic security in Spring Security, I will continue with the next post.
This is the second post for a short series of aspect oriented programming with the spring boot. In this post, I am going to implement a simple project …
In this post, we are going to look at what is AOP and how your spring application matches with AOP concepts using Spring AOP module. After that we are …