Article part of the picture source network, assault deleted, thank you

What you can learn from this article:

  • What is SpringSecurity, how is it configured, and the authentication process
  • How to authenticate based on Token (JWT)
  • How to authenticate users, based on THE RBAC model

introduce

SpringSecurity is a WEB services authentication and authorization framework developed based on Spring. Its core functions include:

  • User Authentication (Who are you)
  • Resource Authorization (What you can do)
  • Prevent CSRF cross-site requests, session attacks, etc

The installation

<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>
    <version>2.4.0</version>
</dependency>
Copy the code

When you introduce dependencies, you don’t need to do any configuration, and your project is already managed by Spring SecurityHttp basicMode, when you start a project you can see this line of message on the console, which is a random password generated by Spring Security that is regenerated every time you start.At this point, if you visit the API again, the login request will be automatically redirected and this page will pop up:This is what SpringSecurity does for us, and the default username isuserPassword is a random Password for the console, which accesses our API interface. We can also customize username and password, just configure it in application.yml:

spring:
  security:
    user:
      name: admin
      password: 123456
Copy the code

The principle of

You’ve just seen the Spring SecurityHttp Basic pattern. This is the most Basic and simplest pattern. It works by encrypting the user name and password through Base64 and passing it through Authorization in the request header. Server in BasicAuthenticationFilter filters for checking and then returns the encrypted string parsing results, this approach is very unsafe, and users can only be defined in a configuration file, and can be customized to the login page, so basically the scenario used much. (Don’t use too much and talk too much? Familiarize yourself with SpringSecurity! In addition to Http Basic mode, formLogin mode is also provided. However, because the front and back ends are basically separated, I will not write session-based authentication here. In this paper, the authentication is mainly based on Token mode, that is, the two modes are not used.

Before learning how to use It, we need to understand the process of SpringSecurity, understand the process is convenient to start later.

Core components

There are several core components of SpringSecurity that we need to understand first:

  • Authentication: an authentication body that stores detailed user information
  • SecurityContext: storage authentication body
  • SecurityContextHolderBuild SecurityContext, which is thread-safe and managed using ThreadLocal.

That is to say, after the user has been authenticated, we can obtain the user’s information through this line of code in the program.

SecurityContextHolder.getContext().getAuthentication()
Copy the code

The above mentioned BasicAuthenticationFilter, so what it is.

The core of SpringSecurity authentication is the filter chain, and the whole authentication and authorization process is actually completed in the filter

  • When a user requests an Api, it is first authenticated through a series of Authentication filters (part 2 in the figure). If one filter is authenticated, an Authentication object is set.
    • The above authentication filters are not all, and we can customize filters
  • The final filter is FilterSecurityInterceptor, it will have to judge the user authentication, if certified, you can access the Api
  • If after all filter the user has not yet been certified, so FilterSecurityInterceptor throws an exception, and then by ExceptionTranslationFilter catch exceptions for processing, handling such as jump to the login page or the JSON response

The configuration class

How does SpringSecurity know which apis need to be authenticated to access and which users can be authenticated and have permissions? We tell SpringSecurity nature is needed, so you need to create a SpringSecurity configuration class, inheritance WebSecurityConfigurerAdapter, rewrite the three methods, used to configure the user, resource access rules, and so on. Understand first.

@EnableWebSecurity(debug = true) // Enable debug To view detailed logs
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    public WebSecurityConfig(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    // Configure resource permission rules
    @Override
    protected void configure(HttpSecurity http) throws Exception {}// Configure the user
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {}// Ignore static resources and do not participate in authentication
    @Override
    public void configure(WebSecurity web) throws Exception {}// Password encoder
    @Bean
    public PasswordEncoder passwordEncoder(a) {
           return newBCryptPasswordEncoder(); }}Copy the code

Cipher encoder

As an aside, about the history of passwords, at the beginning, we used plaintext to store passwords (remembering the 2011 CSDN password leakage event of millions of users), and then started to use one-way hash algorithms (MD5 and SHA256). One-way hash means that the hash value cannot be reversed, but this method has many disadvantages. For example, if the same value is hashed, the same value will be obtained, resulting in hash collision and fast calculation. Based on these three weaknesses, hackers can generate a dictionary table of random combinations of numbers and letters up to 20 digits, which can be searched to retrieve the original values during decryption. The next step is to “salt” passwords, adding globally unique values to each user password, so that there is no dictionary table to use.

But for now, it’s best to use a slower algorithm like Bcrypt rather than a fast one like MD5. Making MD5 dictionary tables with 8-bit passwords takes five months, whereas BCrypt takes decades. And the salt value of BCrypt is calculated many times.

SpringSecurity provides the PasswordEncoder and provides the BCryptPasswordEncoder implementation class. The user uses BCrypt for encryption when registering, and the plaintext password entered by the user will be matched when logging in. We need to inject it into the Spring container as a global Bean.

@Bean
public PasswordEncoder passwordEncoder(a){
    return new BCryptPasswordEncoder();
}
Copy the code

But there is a scenario, is the development of the project for a long time, at the beginning of the use of MD5 algorithm, now if you want to use BCrypt how to solve. , that is, if for old user MD5 can also match is successful, the new user BCrypt can match, if users change the password again using BCrypt algorithm to encrypt, equivalent to a process of substitution, provided the DelegatingPasswordEncoder SpringSecurity5, It can define a collection of cipher encoders.

@Bean
public PasswordEncoder passwordEncoder(a) {
    String idForDefault = "bcrypt"; // Default encoding
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put(idForDefault, new BCryptPasswordEncoder());
    encoders.put("MD5".new MessageDigestPasswordEncoder("MD5"));
    return new DelegatingPasswordEncoder(idForDefault, encoders);
}
Copy the code

But in this case, the password in our database should be stored in the form of {algorithm} password, for example:

{bcrypt}$2a$10$G9URrvDzboJXt4fOawOohOD2QbtnniwVD9IEmxNrbHvPtoCnPIXSq
{MD5}4xAGajgDhbSnUgAZ+5jbJOKNwAbJ3fPMwNsaIohE2Ko=
Copy the code

Because when we configure the multi-password encoder, the password encrypted by SpringSecurity is in this form, otherwise it will not match.

Custom database

The source of SpringSecurity users can be in memory or in a database (SpringSecurity supports hard-coded configuration of user information), but in practice our users are basically in a database, so we need a way to load the database users in, SpringSecurity then performs authentication matches against these users. SpringSecurity provides UserDetails, which is the user information, and UserDetailsService, which is used to load the UserDetails, that is, to query the database and return the UserDetails.

UserDetails

This entity corresponds to the user table in the database, which contains user name, password, email, etc. (these are all fields in the user table), as well as a set of authorities that specify the user’s permissions. There will be a role table and an intermediate table in the database.

@Entity
@Getter
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private boolean enabled; // Whether the account is available

    @ManyToMany
    @Fetch(value = FetchMode.JOIN)
    @JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, InverseJoinColumns = {@joinColumn (name = "role_id", referencedColumnName = "id")}
    private Set<Role> authorities; // Permission set

    // The following three fields are related to whether the account is available. If you do not need to set the value to true, generally speaking, just use enable.
    @Override
    public boolean isAccountNonExpired(a) {
        return true;
    }

    @Override
    public boolean isAccountNonLocked(a) {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired(a) {
        return true; }}Copy the code
@Entity
@Getter
public class Role implements GrantedAuthority {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "role_name")
    private String authority;
}
Copy the code

UserDetailsService

The UserDetailsService is used to load user information. It has only one method, return the UserDetails defined above.

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepo userRepo;

    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepo.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User name not found" + username + "User of")); }}Copy the code
@Repository
public interface UserRepo extends JpaRepository<User.Long> {
    Optional<User> findByUsername(String username);
}
Copy the code

SecurityConfig

With UserDetails and UserDetailsService defined, we also need to configure in SecurityConfig to tell SpringSecurity to load user information this way.

@Autowired
private UserDetailsServiceImpl userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
}
Copy the code

The certification process

For example, use the username and password to log in:

  • Build one based on the username and passwordUsernamePasswordAuthenticationTokenObject, it inheritsAuthentication
  • The Authentication object is then handed overFilter chainGo to the certification
  • throughAuthenticationManagerThe implementation of the classProvidersGo to the certification
  • If you go to the database to load the data source will passDaoAuthenticationProviderTo authenticate, callUserDetailsServiceLoad user information
  • If authentication fails, the Authetication object is set to false,FilterSecurityInterceptorAn exception is thrown.

The last

Reference content of this paper:

Spring Security + OAuth2 focuses on creating enterprise-level authentication and authorization in multiple scenarios