Hand-held guide to Spring Security!

Keywords: Java Spring Shiro JSON

Spring Security is a security management framework in Spring family. In fact, Spring Security had been developed for many years before Spring Boot came into being, but it was not widely used. Security management has always been Shiro's world.

<!--more-->

Compared with Shiro, integrating Spring Security in SSM/SSH is a more cumbersome operation, so although Spring Security is more powerful than Shiro, it is not used as much as Shiro (although Shiro does not have more functions than Spring Security, but Shiro is also enough for most projects).

Since Spring Boot came into being, Spring Boot has provided Spring Security with an automated configuration solution that allows zero configuration using Spring Security.

Therefore, in general, the common combination of security management technology stacks is as follows:

  • SSM + Shiro
  • Spring Boot/Spring Cloud + Spring Security

Note that this is only a recommended combination, and if it's technically simple, any combination can work.

Let's look at the specific use.

1. Project creation

Using Spring Security in Spring Boot is very easy, and introducing dependencies is enough:

Spring Security dependencies in pom.xml:

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

As long as dependencies are added, all interfaces of the project are automatically protected.

2. First experience

Let's create a Hello Controller:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

To access / hello, you need to log in before you can access it.

When a user sends a request to access / hello interface from the browser, the server will return 302 response codes, and let the client redirect to / login page. After the user logs in on / login page, the user will automatically jump to / hello interface.

In addition, POSTMAN can also be used to send requests. When POSTMAN is used to send requests, user information can be placed in the request header (thus avoiding redirection to the login page):

From the above two different login modes, we can see that Spring Security supports two different authentication modes:

  • form forms can be used to authenticate
  • It can be authenticated by HttpBasic

3. User name configuration

By default, the login username is user, and the password is a random string generated at project startup. You can see the default password from the startup console log:

This randomly generated password changes every time it starts. There are three different ways to configure the login username/password:

  • Configuration in application.properties
  • Configuration in memory through Java code
  • Loading from the database through Java

The first two are relatively simple, and the third one is a little larger. This article will first look at the first two, and the third one will be introduced separately later. You can also refer to my "micro-project"().

3.1 Profile Configuration User Name/Password

You can configure the user's basic information directly in the application.properties file:

spring.security.user.name=javaboy
spring.security.user.password=123

Once the configuration is complete, restart the project and log in using the username/password configured here.

3.2 Java Configuration User Name/Password

User name and password can also be configured in Java code. First, we need to create a Spring Security configuration class integrated from the WebSecurity Configurer Adapter class, as follows:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //The following two lines of configuration indicate that two users are configured in memory
        auth.inMemoryAuthentication()
                .withUser("javaboy").roles("admin").password("$2a$10$OR3VSksVAmCzc.7WeaRPR.t0wyCsIj24k0Bne8iKWV1o.V9wsP8Xe")
                .and()
                .withUser("lisi").roles("user").password("$2a$10$p1H8iWa8I4.CA.7Z8bwLjes91ZpY.rYREGHQEInNtAp4NzL6PLKxi");
    }
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Here we configure two users in the configure method. The passwords of users are encrypted strings (plaintext 123). Starting with Spring 5, the password is required to be encrypted. If you do not want to encrypt, you can use an outdated example of PasswordEncoder, NoOpPasswordEncoder, but it is not recommended to do so. After all, it's not safe.

Spring Security provides BCrypt Password Encoder password encoding tool. It is very convenient to realize encryption and salt of password. The result of encryption in the same plaintext is always different, so users do not need to save salt fields extra. This is much more convenient than Shiro.

4. Login Configuration

For the login interface, the response after successful login and the response after failed login, we can configure it in the implementation class of WebSecurity Configurer Adapter. For example:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    VerifyCodeFilter verifyCodeFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
        http
        .authorizeRequests()//Open login configuration
        .antMatchers("/hello").hasRole("admin")//Represents the access / hello interface, requiring the role of admin
        .anyRequest().authenticated()//Represents the remaining interfaces that can be accessed after login
        .and()
        .formLogin()
        //Define the login page. When not logged in, access to an interface that needs to be logged in before it can be accessed will automatically jump to that page.
        .loginPage("/login_p")
        //Login Processing Interface
        .loginProcessingUrl("/doLogin")
        //Define the key of the user name when logging in, default to username
        .usernameParameter("uname")
        //Define the key of the user's password when logging in, default to password
        .passwordParameter("passwd")
        //Processor with successful login
        .successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write("success");
                    out.flush();
                }
            })
            .failureHandler(new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write("fail");
                    out.flush();
                }
            })
            .permitAll()//All interface systems related to form login are directly passed through
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler(new LogoutSuccessHandler() {
                @Override
                public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write("logout success");
                    out.flush();
                }
            })
            .permitAll()
            .and()
            .httpBasic()
            .and()
            .csrf().disable();
    }
}

We can configure a successful logon callback in the successHandler method. If the front and back end are separated and developed, we can return to JSON after successful logon. Similarly, the failed logout SuccessHandler method configures a successful logout callback.

5. Ignore interception

If a request address does not need to be intercepted, there are two ways to implement it:

  • Set the address to be accessed anonymously
  • Filter out the address directly, that is, the address does not go through the Spring Security filter chain

The second scheme is recommended, which is configured as follows:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/vercode");
    }
}

Another powerful feature of Spring Security is that it can combine with OAuth2 to play more tricks, which we will introduce in detail in the following articles.

This article will start with this point. If you have any questions, please leave a message for discussion.

Focus on the public number, focus on Spring Boot + micro-services and front-end separation and other full stack technology, regular video tutorials to share, pay attention to Java after reply, pick up Songge's carefully prepared Java dry goods for you!

Posted by mrfruits on Wed, 24 Jul 2019 18:05:57 -0700