1. Introduction to Spring Security
Spring Security is a powerful and highly customizable authentication and access control framework. It is the standard for ensuring Spring-based applications — from the official reference manual
Like Shiro, Spring Security has authentication, authorization, encryption, and other capabilities for permission management. Unlike Shiro, Spring Security has more functionality than Shiro, and Spring Security is a better fit for Springboot than Shiro because they are members of the Spring family. Today, we’ll integrate Spring Security for the SpringBoot project.
The version used in this article:
SpringBoot: 2.2.6.RELEASE Spring Security: 5.2.2
2. Configure Spring Security
Integrating Spring Security with SpringBoot is as simple as adding the following code to pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>Copy the code
Instead of specifying the version number of Spring Security, it will match the corresponding SpringBoot version, which is 2.2.6.RELEASE and Spring Security 5.2.2.RELEASE.
Then we can start SpringBoot.
When we try to access the project, it jumps to this screen:
Right! Until then, you don’t have to do anything. This is the beauty of Spring Security. You just need to import the Spring Security package and it will work in your project. Because it already helps you to implement a simple login interface. According to the official introduction, the login account is user, and the password is a random password, which can be found in the console, like this sentence:
Using generated security password: 1cb77bc5-8d74-4846-9b6c-4813389ce096Copy the code
Using generated security password after the random password, we can use this password to log in. Random passwords are generated every time you start the service (keep an eye on the console if you’ve configured DevTools for hot deployment, because every time you change the code, the system will reboot and the random passwords will be generated again).
Of course, that’s not what you want, and it’s certainly not what you give your users. So, next, let’s configure it the way we want.
To implement a custom configuration, the first to create an inheritance in WebSecurityConfigurerAdapter configuration class:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
Copy the code
This uses the @enablewebsecurity annotation, which Spring Security uses to EnableWebSecurity. I won’t go into the implementation here.
To implement a custom interception configuration, Spring Security must first be told where to get the user’s information and what role the user corresponds to. There needs to be rewritten WebSecurityConfigurerAdapter configure (AuthenticationManagerBuilder auth) method. This method instructs Spring Security to find the list of users, compare it to the user who wants to pass the interceptor, and perform the following steps.
Spring Security has several options for user storage configuration, including:
- Memory user storage
- Database user storage
- LDAP User Storage
- Custom user storage
Let’s take a look at each of these user storage configurations:
1. Memory User storage
This configuration directly stores user information in memory, which is by far the fastest. But it only works for a limited number of users, and those users rarely change. Let’s look at the configuration method:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())
.withUser("zhangsan").password(passwordEncoder().encode("123456")).authorities("ADMIN")
.and()
.withUser("lisi").password(passwordEncoder().encode("123456")).authorities("ORDINARY");
}
private PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}Copy the code
As you can see, AuthenticationManagerBuilder use initializer way to build. In the above method, the inMemoryAuthentication() method is first called, which specifies that the user is stored in memory. You then call the passwordEncoder() method, which tells Spring Security how to encrypt the authentication password. Because after Spring Security5, you must specify some kind of encryption or the program will report an error. The withUser(), password(), and authorities() methods are then invoked to specify the user’s account, password, and authority name, respectively. After adding a user, the and() method is used to connect the addition of the next user.
If you use this configuration approach, you’ll find that you have to change the code when you change the user. For most projects, this approach is not sufficient, at least we need a registration function.
2. Database user storage
The user information is stored in the database, so that we can easily add, delete, change and check the user information. It can also add additional information to the user in addition to authentication information, which is the way many of our careful applications are designed. Let’s implement the following:
@Autowired private DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder()) .usersByUsernameQuery( "select username, password, status from Users where username = ?" ) .authoritiesByUsernameQuery( "select username, authority from Authority where username = ?" ); } private PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }Copy the code
Call jdbcAuthentication() to tell Spring Security to use JDBC to query users and permissions, the dataSource() method specifies database connection information, and the passwordEncoder() method specifies password encryption rules, The user’s password data should be encrypted and stored in the same way. Otherwise, two passwords with different encryption methods will be matched and replaced. UsersByUsernameQuery () and authoritiesByUsernameQuery () method, respectively, defines the SQL query information users and permissions. In fact, Spring Security provides us with default SQL for querying users, permissions, and even group user authorization, The three default SQL stored in org. Springframework. Security. Core. The populated userdetails. JDBC. JdbcDaoImpl, interested friends can go in and have a look. If you want to use the default, key fields in your table must match those in the statement.
Using a database to store user and permission information already meets most of the requirements. But Spring Security provides another way to configure, and let’s take a look.
3.LDAP user storage
LDAP: Lightweight directory access protocol. It is an open, neutral, and industry-standard application protocol that provides access control and maintains directory information of distributed information over IP. In simple terms, it is the technology of storing user information on another server (of course, it can also be on the same server, but we generally do not do this) and accessing it over the network.
Let’s configure it briefly:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer = auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}");
configurer.passwordCompare()
.passwordEncoder(passwordEncoder())
.passwordAttribute("passcode");
configurer.contextSource().url("ldap://xxxxx.com:33389/dc=xxxxxx,dc=com");
}
private PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}Copy the code
UserSearchFilter () and groupSearchFilter() set the filter criteria for users and groups, while userSearchBase() and groupSearchBase() set the search start location. ContextSource ().url() Sets the IP address of the LDAP server. If no remote server is available, the embedded LDAP server can be used using contextSource().root(), which uses user data files in the project to provide authentication services.
If the above methods do not meet our requirements, we can use a custom way to configure.
4. Customize user storage
Custom user storage, which uses the authentication name to find the corresponding user data and hand it over to Spring Security for use. We need to define a service class that implements UserDetailsService:
@Service
public class MyUserDetailsService implements UserDetailsService{
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getUserByUsername(username);
return user == null ? new User() : user;
}
}
public class User implements UserDetails {
...
}Copy the code
The class only needs to implement one method: loadUserByUsername(). What this method does is use the passed username to match a user entity with information such as a password. Note that the User class needs to implement UserDetails, which means that the information retrieved must have the information required by Spring Security.
Let’s continue with the configuration:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean private PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}Copy the code
This configuration is simple, just tell Spring Security what your UserDetailsService implementation class is, and it calls loadUserByUsername() to find the user.
These are the four ways Spring Security provides for storing users, and the next thing you need to think about is how to intercept requests.
Request interception
1. Safety rules
Spring Security’s request interception configuration method is an overloading of the user storage configuration method. Let’s start with a brief configuration:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.hasRole("ADMIN")
.antMatchers("/", "/**").permitAll();
}
}Copy the code
After calling the authorizeRequests() method, you can add a custom interception path. The antMatchers() method configures the request path, hasRole() and permitAll() specify access rules, indicating that only users with ADMIN permission can access and all users can access, respectively.
Note that the configurations need to come in pairs, and the order in which they are configured is important. Rules declared earlier have higher precedence. That is, if we put.antmatchers (“/”, “/”).permitall ()** first, like this:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/**").permitAll()
.antMatchers("/user", "/menu")
.hasRole("ADMIN");
}Copy the code
Then, the following configuration of “/user” and “/menu” is futile, because the previous rule already indicates that all paths can be accessed by everyone. Of course, there are many more rules for permissions, but I’ve just listed two here. The following are common built-in expressions:
express | describe |
---|---|
hasRole(String role) | returntrue Whether the current client has a specified role. For example,hasRole('admin') By default, provided roles that do not start with “ROLE_” are added. You can modify itdefaultRolePrefix On customizationDefaultWebSecurityExpressionHandler . |
HasAnyRole (String... roles) |
returntrue Whether the current principal has any of the roles provided (in the form of a comma-separated list of strings). For example,hasAnyRole('admin', 'user') By default, provided roles that do not start with “ROLE_” are added. You can modify itdefaultRolePrefix On customizationDefaultWebSecurityExpressionHandler . |
hasAuthority(String authority) |
returntrue Whether the current client has designated authority. For example,hasAuthority('read') |
HasAnyAuthority (String... authorities) |
returntrue If the current body has any of the supplied authorities (given a comma-separated list of strings) for example,hasAnyAuthority('read', 'write') |
principal |
Allows direct access to the principal object representing the current user |
authentication |
Allow direct accessAuthentication fromSecurityContext |
permitAll |
Always evaluated astrue |
denyAll |
Always evaluated asfalse |
isAnonymous() |
returntrue Whether the current client is an anonymous user |
isRememberMe() |
returntrue Whether the current subject is a Remember Me user |
isAuthenticated() |
true Returns if the user is not anonymous |
isFullyAuthenticated() |
returntrue If the user is not anonymous or remember my user |
hasPermission(Object target, Object permission) |
returntrue Whether a user can access a given target for a given permission. For example,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) |
returntrue Whether a user can access a given target for a given permission. For example,hasPermission(1, 'com.example.domain.Message', 'read') |
In addition, there is another method that supports the evaluation of SpEL expressions, which can be used as follows:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.access("hasRole('ADMIN')")
.antMatchers("/", "/**").permitAll();
}Copy the code
It implements the same rules as the above method. Spring Security also provides other rich SpEL expressions, such as:
express | describe |
---|---|
hasRole(String role) |
returntrue Whether the current client has a specified role. For example,hasRole('admin') By default, provided roles that do not start with “ROLE_” are added. You can modify itdefaultRolePrefix On customizationDefaultWebSecurityExpressionHandler . |
HasAnyRole (String... roles) |
returntrue Whether the current principal has any of the roles provided (in the form of a comma-separated list of strings). For example,hasAnyRole('admin', 'user') By default, provided roles that do not start with “ROLE_” are added. You can modify itdefaultRolePrefix On customizationDefaultWebSecurityExpressionHandler . |
hasAuthority(String authority) |
returntrue Whether the current client has designated authority. For example,hasAuthority('read') |
HasAnyAuthority (String... authorities) |
returntrue If the current body has any of the supplied authorities (given a comma-separated list of strings) for example,hasAnyAuthority('read', 'write') |
principal |
Allows direct access to the principal object representing the current user |
authentication |
Allow direct accessAuthentication fromSecurityContext |
permitAll |
Always evaluated astrue |
denyAll |
Always evaluated asfalse |
isAnonymous() |
returntrue Whether the current client is an anonymous user |
isRememberMe() |
returntrue Whether the current subject is a Remember Me user |
isAuthenticated() |
true Returns if the user is not anonymous |
isFullyAuthenticated() |
returntrue If the user is not anonymous or remember my user |
hasPermission(Object target, Object permission) |
returntrue Whether a user can access a given target for a given permission. For example,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) |
returntrue Whether a user can access a given target for a given permission. For example,hasPermission(1, 'com.example.domain.Message', 'read') |
2. Login
If we have our own login interface and need to replace the default interface provided by Spring Security, we can use the fromLogin() and loginPage() methods to do this:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.access("hasRole('ADMIN')")
.antMatchers("/", "/**").permitAll()
.and()
.formLogin()
.loginPage("/login");
}Copy the code
This points the login address to “/login”. To specify where to jump to if the login succeeds, use the defaultSuccessUrl() method:
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")Copy the code
After the user logs in, it will jump to the main page.
Next, let’s look at logout.
3. Log out
Similar to login, the logout() and logoutSuccessUrl() methods can be used to do this:
.and()
.logout()
.logoutSuccessUrl("/login")Copy the code
In the preceding example, the login page is displayed after the user logs out.
4, summary
Now that we have a basic understanding of the Spring Security configuration, we can configure it as we want (basic). There is more Spring Security can do than just look at this article. The most effective way to learn it is to read the official documentation. Spring Security has the most up-to-date knowledge about Spring Security! IO /projects/sp…
Finally, recently, many friends asked me for Linux learning roadmap, so I stayed up for a month in my spare time according to my own experience, and sorted out an e-book. Whether you are interviewing or self-improvement, I believe will help you! The directory is as follows:
Free to everyone, just ask everyone to point to me!
Ebook | Linux development learning roadmap
Also hope to have a small partner can join me, do this e-book more perfect!
Have a harvest? Hope the old iron people come to a triple whammy, give more people to see this article
Recommended reading:
- Dry goods | programmers advanced architect necessary resources free of charge
- Artifact | support resource site search