[In-depth explanation of Spring Security (5)] Custom filters for front-end and back-end login authentication

1. Custom filter

In [An in-depth introduction to Spring Security (2)] the implementation principle of Spring Security , the editor explained the filters loaded by default. Some of the filters in them sometimes cannot meet the actual needs in development. At this time, we need to customize the filters, and then Fill in or replace pre-existing filters.

First, let’s explain the four methods of adding filters (all are HttpSecuritymedium). The first parameter of the following method is the filter to be added, and the second parameter is the Class object for the existing filter:

  • addFilterAt(filter,atFilter): This is equivalent to the insertion operation of the linear table, inserting filter at the position of atFilter.
  • addFilterBefore(filter,atFilter): This is to insert a filter before atFilter;
  • addFilterAfter(filter,atFilter): As the name implies, a filter filter is inserted after atFilter;

Custom login authentication filter

In [Introduction to Spring Security (3)] The implementation principle of default login authentication, the editor described the default user login authentication is to go UsernamePasswordAuthenticationFilter, it is through the transmission of form data, and then through request.getParameter(username/password)the way to obtain the user name and password for authentication. But in the separation of the front and back ends, if the login data submitted to the back end is in JSON format, we need to customize the filter to process the data to obtain the username and password for authentication.

Since it is a different login authentication filter, and it is used to separate the front and back ends, we can pass some unnecessary default filters. When we call the formLogin() method in HttpSecurity, it will help us load the three filters circled below.

  1. UsernamePasswordAuthenticationFilter: Responsible for form authentication;
  2. DefaultLoginPageGeneratingFilter: Provides a login interface;
  3. DefaultLogoutPageGeneratingFilter: Provides a logout interface;

insert image description here
Then if we don't call the formLogin method when we customize the SecurityFilterChain, then these three filters are equivalent to being passed by us.

Custom LoginFilter

Since we are going to implement a JSON data format authentication, we must first make some judgments on the request:

  1. The request method should be POST method;
  2. The data sent by the request should be in JSON format.

If these are not met, you should adopt the default login authentication configuration.

The implementation is simple:

Let's inherit UsernamePasswordAuthenticationFilterand rewrite the attemptAuthentication method to implement our own authentication method.

code show as below:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    
    

    public LoginFilter() {
    
    
    }

    public LoginFilter(AuthenticationManager authenticationManager) {
    
    
        super(authenticationManager);
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
    
    

        // 判断请求方式是否是 POST 方式
        if (!request.getMethod().equals("POST")) {
    
    
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 然后判断是否是 JSON 格式的数据
        if(request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
    
    
            // 如果是的话就从 JSON 中取出用户信息进行认证
            try {
    
    
                Map<String,String> userInfo = JSONObject.parseObject(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter());
                String password = userInfo.get(getPasswordParameter());
                // 封装成Authentication
                UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                        password);
                // 仿造父类去调用AuthenticationManager.authenticate认证就可以了
                setDetails(request,authRequest);
                return getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
    
    
                throw new RuntimeException(e);
            }
        }

        // 否则用父类的方式去认证
        return super.attemptAuthentication(request, response);
    }
}

Configure LoginFilter

The injection method of the new version is used here instead of inheritance WebSecurityConfigurerAdapter, because WebSecurityConfigurerAdapter has been deprecated, let's keep pace with the times.

@EnableWebSecurity
public class SecurityConfig {
    
    

    /**
     * 构造基于内存的数据源管理
     * @return  InMemoryUserDetailsManager
     */
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
    
    
        return new InMemoryUserDetailsManager(User
                .withUsername("admin")
                .password("{noop}123")
                .authorities("admin")
                .build()
        );
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    
    
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .logout()
                .logoutSuccessHandler(this::onAuthenticationSuccess)
                .logoutUrl("/api/auth/logout")
                .and()
                .addFilterAt(loginFilter(http), UsernamePasswordAuthenticationFilter.class) // 注意配置 filter 哦
                .csrf()
                .disable()
                .build();
    }

    /**
     * 自定义 AuthenticationManager
     * @param http
     * @return  AuthenticationManager
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
    
    
        return http.
                getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(inMemoryUserDetailsManager())
                .and()
                .build();
    }

    @Bean
    public LoginFilter loginFilter(HttpSecurity http) throws Exception {
    
    
        LoginFilter loginFilter = new LoginFilter(authenticationManager(http));
        // 自定义 JSON 的 key
        loginFilter.setUsernameParameter("username");
        loginFilter.setPasswordParameter("password");
        // 自定义接收的url,默认是login
        // 此过滤器的doFilter是在AbstractAuthenticationProcessingFilter,在那里进行的url是否符合的判定
        loginFilter.setFilterProcessesUrl("/api/auth/login");
        // 设置login成功返回的JSON数据
        loginFilter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
        // 设置login失败返回的JSON数据
        loginFilter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
        return loginFilter;
    }

    @Resource
    private ObjectMapper objectMapper;
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException{
    
    
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        if(request.getRequestURI().endsWith("/login"))
            out.write(objectMapper.writeValueAsString(JsonData.success("登录成功")));
        else if(request.getRequestURI().endsWith("/logout"))
            out.write(objectMapper.writeValueAsString(JsonData.success("注销成功")));
        out.close();
    }

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException{
    
    
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(objectMapper.writeValueAsString(JsonData.failure(401,exception.getMessage())));
        out.close();
    }

}

test

Please add a picture description

2. Summary

  1. There are three methods in HttpSecurity that can be used to add filters :
  • addFilterAt(filter,atFilter): This is equivalent to the insertion operation of the linear table, inserting filter at the position of atFilter.
  • addFilterBefore(filter,atFilter): This is to insert a filter before atFilter;
  • addFilterAfter(filter,atFilter): As the name implies, a filter filter is inserted after atFilter;
  1. Custom login authentication can be customized by inheriting the UsernamePasswordAuthenticationFilter filter and rewriting the attemptAuthentication method, and then configure it in the Spring container in the custom SecurityConfig configuration class.Note that after instantiating it, it must be initialized and assigned to its internal property AuthenticationManager, otherwise an error will be reported when it is handed over to the Spring container management; finally added to the filter chain.
  2. Do not call the formLogin method, then the following three filters are in the filter chain SecurityFilterChain.
    UsernamePasswordAuthenticationFilter: Responsible for form authentication;
    DefaultLoginPageGeneratingFilter: Provides a login interface;
    DefaultLogoutPageGeneratingFilter: Provides a logout interface;

Guess you like

Origin blog.csdn.net/qq_63691275/article/details/131029579