Understand Spring Security Architecture and implement Spring Boot Security
In this tutorial we will be looking at how Spring Security works and its architecture. We will be creating a Spring Boot Project to expose two REST API's
研究Spring Security的工作方式及其架构
- /helloadmin
- /hellouser
We will then be implementing Spring Security such that a client having Admin role will be able to access both /helloadmin and /hellouser API. While a client having User role will be able to access only /hellouser API
Spring Boot Project to expose REST API's
Our Maven Project at the end of this tutorial will be as follows-
Go to Spring Initializr website and create a new Spring Boot Project. We will only include the Web dependency now.
The pom.xml will be as follows-
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.javainuse</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Next create the Controller class to expose the REST API's -
package com.javainuse.springsecurity.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResourceController {
@RequestMapping({"/hellouser"})
public String helloUser(){
return "Hello User";
}
@RequestMapping({"/helloadmin"})
public String helloAdmin(){
return "Hello Admin";
}
}
If we now start our Spring Boot project, we will be able to hit the webservice using Postman -
Test /hellouser API
Test /helloadmin API
Configure Spring Security for Spring Boot Project
We will be configuring Spring Security for the Spring Boot project we just created. Let us first understand the Spring Security Architecture.
将为刚刚创建的Spring Boot项目配置Spring Security。让我们首先了解一下Spring Security Architecture
Understanding Spring Security Architecture
Let us understand how Spring Security Works.
Spring Boot Security Architecture
Filters - Before the request reaches the Dispatcher Servlet, it is first intercepted by a chain of filters.
过滤器-在请求到达分派器Servlet之前,它首先被一系列过滤器拦截
These filters are responsible for Spring Security. So any incoming request will go through these filters and it is here that authentication and authorization takes place. Based on the type of requests there are different Authentication Filters like the BasicAuthenticationFilter,UsernamePasswordAuthenticationFilter etc
这些过滤器负责Spring Security。因此,任何传入的请求都将通过这些过滤器,并且在此处进行身份验证和授权。根据请求的类型,有不同的身份验证过滤器,例如BasicAuthenticationFilter,UsernamePasswordAuthenticationFilter等
Authentication Object Creation - When the request is intercepted by the appropriate AuthenticationFilter it retrieves the username and password from the request and creates the Authentication Object. If the extracted credentials are username and password, then UsernamePasswordAuthenticationToken is created.
当请求被适当的AuthenticationFilter拦截后,它将从请求中检索用户名和密码,并创建Authentication Object。如果提取的凭据是用户名和密码,则将创建UsernamePasswordAuthenticationToken。
AuthenicationManager - Using the Authentication Object created the filter will then call the authenticate method of the Authentication Manager. The Authentication Manager is only a interface and actual implementation of the authenticate method is provided by the ProviderManager.
AuthenicationManager-使用创建的身份验证对象,过滤器将调用身份验证管理器的authenticate方法。Authentication Manager只是一个接口,ProviderManager提供了authenticate方法的实际实现
Important point to note here is that the Authentication Manager takes an Authentication object as input and after successful authentication again returns an object of type Authentication.
这里要注意的重要一点是,身份验证管理器将身份验证对象作为输入,并且在成功身份验证之后再次返回身份验证类型的对象
The ProviderManager has a list of AuthenticationProviders. From it's authenticate method it calls the authenticate method of the appropriate AuthenticateProvider. In response it gets the Principal Authentication Object if the authentication is successful.
ProviderManager具有AuthenticationProviders列表。从它的authenticate方法中,它调用适当的AuthenticateProvider的authenticate方法。作为响应,如果身份验证成功,它将获取主体身份验证对象
AuthenticationProvider - The AuthenicationProvider is an interface with a single authenticate method.
AuthenticationProvider-AuthenicationProvider是具有单一身份验证方法的接口
It has various implementations like CasAuthenticationProvider,DaoAuthenticationProvider. Depending on the implementation an appropriate AuthenicationProvider implementation is used. It is in the AuthenticationProvider Implementation authenticate method where all the actual authentication takes place.
它具有各种实现,例如CasAuthenticationProvider,DaoAuthenticationProvider。根据实现,使用适当的AuthenicationProvider实现。它是在AuthenticationProvider实现身份验证方法中进行的所有实际身份验证
Using the UserDetails service the AuthenticationProvider fetches the User Object corresponding to the username. It fetches this User Object from either a database, internal memory or other sources. This User object credentials are then compared with the incoming Authentication Object credentials. If Authentication is successful then the Principal Authentication Object is returned in response.
使用UserDetails服务,AuthenticationProvider提取与用户名相对应的用户对象。它从数据库,内部存储器或其他来源获取此用户对象。然后将该用户对象凭据与传入的身份验证对象凭据进行比较。如果身份验证成功,则将返回主体身份验证对象作为响应
UserDetailsService - The UserDetailsService is an interface having a single method named loadUserByUsername.
UserDetailsService是具有名为loadUserByUsername的单个方法的接口
It has various implementations CachingUserDetailsService, JDBCDaoImpl etc. Based on the implementation an appropriate UserDetailsService is called.
它具有CachingUserDetailsService,JDBCDaoImpl等各种实现。基于该实现,将调用适当的UserDetailsService
It is responsible for fetching the User Object with username and password against which the incoming User Object will be compared.
它负责获取带有用户名和密码的用户对象,并将输入的用户对象与之进行比较
Add Spring Security to Spring Boot
We will need to add the Spring Security Starter dependency in the 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.javainuse</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
As soon as we add the spring security dependency to the project the basic authentication gets activated by default. If we now start the application, Basic Security is enabled by default by Spring security due to the spring auto configurations.
一旦将spring安全依赖项添加到项目中,默认情况下就会激活基本身份验证。如果现在启动应用程序,则由于spring自动配置,Spring安全默认情况下会启用Basic Security
In the console we get the password while the username is user-
Let us have a look Spring Security Autoconfigurations.
-
When no Spring Security dependency is added -
-
When Spring Security is added -
We will now be creating our own custom Spring Security Configuration by extending the WebSecurityConfigurerAdapter In this class we will be making use of the PasswordEncoder. In previous tutorial we have already seen the need for password encoder.
现在,我们将通过扩展WebSecurityConfigurerAdapter来创建自己的自定义Spring Security配置。在此类中,我们将使用PasswordEncoder。在先前的教程中,我们已经看到了对密码编码器的需求
package com.javainuse.springsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter{
@Autowired
CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/helloadmin")
.hasRole("ADMIN")
.antMatchers("/hellouser")
.hasAnyRole("ADMIN","USER")
.and().httpBasic();
}
}
Create a custom UserDetails Service class-
package com.javainuse.springsecurity.config;
import java.util.Arrays;
import java.util.List;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<SimpleGrantedAuthority> roles=null;
if(username.equals("admin"))
{
roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User("admin", "$2y$12$I0Di/vfUL6nqwVbrvItFVOXA1L9OW9kLwe.1qDPhFzIJBpWl76PAe",
roles);
}
else if(username.equals("user"))
{
roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
return new User("user", "$2y$12$VfZTUu/Yl5v7dAmfuxWU8uRfBKExHBWT1Iqi.s33727NoxHrbZ/h2",
roles);
}
throw new UsernameNotFoundException("User not found with username: " + username);
}
}
Start the Spring Boot Application
Download Source Code
Download it -
Spring Boot + Security