The background,

In our actual development process, sometimes there may be such situations. Some APIS, such as/API /**, are used by the App side. The data is returned in JSON format, and the authentication mode of these apis is TOKEN authentication. In addition to/API /** these API, are for the use of the web side, the need to use form authentication, to the front end return is a page.

Second, the demand for

1. API for the client

  1. Intercept/API /** all requests.

  2. / API /** all requests require the ROLE_ADMIN role.

  3. Obtain the token from the request header. Once the token value is obtained, the authentication is considered successful and ROLE_ADMIN is assigned to the role.

  4. If not, return a JSON object to the front end {message:” You do not have permission to access “}

  5. Access the/API /userInfo endpoint

    1. Request header carrytokenYes.
    2. The request header is not carriedtokenNot accessible.

2. Apis for the site

  1. interceptAll requests, but not/API /**Request at the beginning.
  2. All requests are requiredROLE_ADMINThe permissions.
  3. No permission, you need to log in using the form.
  4. After successful login, access to the request without permission, directly jump to Baidu.
  5. Build two built-in users
    1. User 1: admin/admin Has the ROLE_ADMIN role
    2. User 2: dev/dev Has the ROLE_DEV role
  6. access/indexThe endpoint
    1. adminUser access, yes access.
    2. devUser access, cannot access, insufficient permissions.

Iii. Implementation scheme

Solution a:

Split multiple services directly, of which/API /** becomes a single service. Non-/ API /** split into another service. Each service uses its own configuration and does not affect each other.

Scheme 2

Written in the same service. Different requests are implemented using different SecurityFilterChain.

After consideration, plan 2 is adopted here, because Plan 1 is simple and can be realized by plan 2. Multiple filter chains can also be recorded in the same project, because not all the time can be divided into multiple projects.

Extension:

Structure of Spring Security SecurityFilterChain

2. Control the execution sequence of SecurityFilterChain

Using org. Springframework. Core. The annotation. The Order comments.

How is the SecurityFilterChain selected

Check the org. Springframework. Web. Filter. DelegatingFilterProxy# doFilter method

Four, implementation,

1. Configuration of Spring Security on the app side

package com.huan.study.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;

/** * Security configuration for app end **@authorHuan.fu 2021/7/13-9:06 PM */
@Configuration
public class AppSecurityConfig {

    /** * The filter chain used by the app side is returned to the front end in JSON data format */
    @Bean
    @Order(1)
    public SecurityFilterChain appSecurityFilterChain(HttpSecurity http) throws Exception {
        // Only requests beginning with/API are processed
        return http.antMatcher("/api/**")
                .authorizeRequests()
                // All requests starting with/API require ADMIN privileges
                    .antMatchers("/api/**")
                    .hasRole("ADMIN")
                    .and()
                // If an exception is caught, return a JSON string directly to the front end
                .exceptionHandling()
                    .authenticationEntryPoint((request, response, authException) -> {
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                        response.setContentType(MediaType.APPLICATION_JSON.toString());
                        response.getWriter().write("{\"message:\":\" You do not have access to 01\"}");
                    })
                    .accessDeniedHandler((request, response, accessDeniedException) -> {
                        response.setStatus(HttpStatus.UNAUTHORIZED.value());
                        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
                        response.setContentType(MediaType.APPLICATION_JSON.toString());
                        response.getWriter().write("{\"message:\":\" You do not have access to 02\"}");
                    })
                    .and()
                // User authentication
                .addFilterBefore((request, response, chain) -> {
                    // Here you can simulate the resolution of user names and permissions from tokens
                    String token = ((HttpServletRequest) request).getHeader("token");
                    if(! StringUtils.hasText(token)) { chain.doFilter(request, response);return;
                    }
                    Authentication authentication = new TestingAuthenticationToken(token, null,
                            AuthorityUtils.createAuthorityList("ROLE_ADMIN")); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); }, UsernamePasswordAuthenticationFilter.class) .build(); }}Copy the code

2. Configuration of Spring Secuirty on the website

package com.huan.study.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.WebSecurityCustomizer;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

/** * Security configuration for web applications **@authorHuan.fu 2021/7/14-9:09 am */
@Configuration
public class WebSiteSecurityFilterChainConfig {
    /** * The filter chain that is processed for use by the webSite end is returned to the front end in page format */
    @Bean
    @Order(2)
    public SecurityFilterChain webSiteSecurityFilterChain(HttpSecurity http) throws Exception {

        AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);

        // Create a user
        authenticationManagerBuilder.inMemoryAuthentication()
                .withUser("admin")
                    .password(new BCryptPasswordEncoder().encode("admin"))
                    .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN"))
                    .and()
                .withUser("dev")
                    .password(new BCryptPasswordEncoder().encode("dev"))
                    .authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_DEV"))
                    .and()
                .passwordEncoder(new BCryptPasswordEncoder());

        // Only all initial requests are processed
        return http.antMatcher("/ * *")
                .authorizeRequests()
                // All requests must be authenticated to be accessible
                    .anyRequest()
                    .hasRole("ADMIN")
                    .and()
                / / disable CSRF
                .csrf()
                    .disable()
                // Enable form login
                .formLogin()
                    .permitAll()
                    .and()
                // If an unauthorized access exception is caught after successful authentication, the system switches to Baidu
                .exceptionHandling()
                    .accessDeniedHandler((request, response, exception) -> {
                        response.sendRedirect("http://www.baidu.com");
                    })
                    .and()
                .build();
    }

    /** * ignore static resources */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer( ){
        return web -> web.ignoring()
                .antMatchers("/**/js/**")
                .antMatchers("/**/css/**"); }}Copy the code

3. Controller writing

/** * Resource controller **@authorHuan.fu 2021/7/13-9:33 PM */
@Controller
public class ResourceController {

    /** * returns user information */
    @GetMapping("/api/userInfo")
    @ResponseBody
    public Authentication showUserInfoApi(a) {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    @GetMapping("/index")
    public String index(Model model){
        model.addAttribute("username"."Zhang");
        return "index"; }}Copy the code

4. Introduce jar packages

<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-thymeleaf</artifactId>
</dependency>
Copy the code

Five, to achieve the effect

1. App has access to API

2. App has no permission to access API

3. The admin user has the permission to access the website API

4. User dev has no permission to access the website API

Access to the API without permission directly jump to baidu home page.

Vi. Complete code

Gitee.com/huan1993/Sp…