Spring security implements login authentication

Implement AbstractAuthenticationToken custom authentication object

In the spring security Authentication process, the core object is Authentication, which is used to store various basic information of the subject (such as user name, password, Department, etc.) and Authentication information of the subject (such as interface permission).

We can customize the Authentication object by inheriting AbstractAuthenticationToken

public class MyUserNamePassToken extends AbstractAuthenticationToken {


    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

		//It is generally used to store subject information,
    private Object principal;
		//Generally used to store credentials for authenticating principal information
    private Object credentials;

		
    public MyUserNamePassToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

    public MyUserNamePassToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(true);
    }


    @Override
    public Object getCredentials() {
        return credentials;
    }

    @Override
    public Object getPrincipal() {
        return principal;
    }
}

We may still be confused. What's the use of defining this object? Don't worry. In fact, it doesn't matter what attributes of the current object are stored. You can save as you want. We focus on the input parameter authorities in the second construction method. This parameter is defined in AbstractAuthenticationToken and represents the collection of all permissions owned by the user.

Define login filter

There are many ways to customize the filter. By reading the source code of UsernamePasswordAuthenticationFilter provided by spring security, we imitate it and implement a filter by inheriting AbstractAuthenticationProcessingFilter. AbstractAuthenticationProcessingFilter is a filter abstract class defined by spring security, It makes a very good expansion based on the authentication process. The following is the dofilter method source code of AbstractAuthenticationProcessingFilter.

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		try {
      //First call the hook method to attempt authentication
			Authentication authenticationResult = attemptAuthentication(request, response);
      //If the authentication is not successful, return directly
			if (authenticationResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				return;
			}
      //session related functions are not used now. It doesn't matter here
			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
			//When the authentication is successful, continue to execute the remaining filters in the filter chain. By default, false is not executed; 
			if (this.continueChainBeforeSuccessfulAuthentication) {
				chain.doFilter(request, response);
			}
      //Execute authentication successful processor
			successfulAuthentication(request, response, chain, authenticationResult);
		}
    //If an exception occurs in authentication, the execution of authentication fails
		catch (InternalAuthenticationServiceException failed) {
			this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
			unsuccessfulAuthentication(request, response, failed);
		}
		catch (AuthenticationException ex) {
			unsuccessfulAuthentication(request, response, ex);
		}
	}

After reading the source code, it is found that as long as we inherit AbstractAuthenticationProcessingFilter, we only need to implement the attemptAuthentication(request, response) method to handle the specific authentication process, Defining an AuthenticationSuccessHandler to handle authentication success and an AuthenticationFailureHandler to handle authentication failure can implement a complete login filter.

Implement the attemptAuthentication method of AbstractAuthenticationProcessingFilter

public class MyLoginFilter extends AbstractAuthenticationProcessingFilter {
		//Define the request mapping intercepted by the filter
    private final static String matchUrl = "/login";

    public MyLoginFilter() {
        super(new AntPathRequestMatcher(matchUrl));
    }

	  //Define authentication logic
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        //Initialize a user-defined unauthenticated Authentication object
        MyUserNamePassToken userNamePassToken = new MyUserNamePassToken(request.getParameter("userName"), request.getParameter("UserPass"));
        //Call all Authentication processors to cycle for Authentication and return an authenticated Authentication object. Students who want to see the source code can see the ProviderManager class
      	return this.getAuthenticationManager().authenticate(userNamePassToken);
    }
}

On the last line of the code, we call this getAuthenticationManager(). Authenticate (usernamepasstoken). This line of code means to call all Authentication processors to process the current Authentication information. If the Authentication is successful, Authentication will be returned. We can customize a processor by implementing the AuthenticationProvider to process our MyUserNamePassToken separately.

Custom authentication processor

public class MyUserNamePassProvider implements AuthenticationProvider {

	  
    private final UserDetailsService userDetailsService;

    public MyUserNamePassProvider(UserDetailsService MyUserDetailsService) {
        this.userDetailsService = MyUserDetailsService;
    }

		//The authentication method is simply implemented here. The specific implementation depends on the specific business
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyUserNamePassToken myUserNamePassToken = (MyUserNamePassToken) authentication;
        User user = null;
        try {
            user = (User) userDetailsService.loadUserByUsername(myUserNamePassToken.getName());
            if (user == null) {
                throw new Exception("user does not exist");
            }
            if (!myUserNamePassToken.getCredentials().equals(user.getPassword())) {
                throw new Exception("The user password is incorrect");
            }
            String s = UUID.randomUUID().toString();
            LoginToken.tokens.put(s, user);
            myUserNamePassToken.setDetails(s);
            return myUserNamePassToken;
        } catch (Exception e) {
            throw new InternalAuthenticationServiceException(e.getMessage());
        }
    }
	  //What type of authentication is supported here
    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.isAssignableFrom(MyUserNamePassToken.class);
    }
}

This class implements only two methods. The first method, authenticate, is used for permission verification. If the verification is successful, one is returned

Define authentication success processor

We directly return the login success

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(R.ok(authentication.getDetails(), "Login succeeded")));
    }
}

Define authentication failure processor

Authentication failure directly returns authentication failure

public class LoginFailHandler implements AuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(objectMapper.writeValueAsString(R.failed(exception.getMessage(), "Login failed")));
    }
}

Configure login filter to spring security

Build a springSecurity configuration class

@Configuration
public class LoginAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    @Qualifier("MyUserDetailsService")
    UserDetailsService userDetailsService;

    @Bean
    public LoginSuccessHandler loginSuccessHandler() {
        return new LoginSuccessHandler();
    }

    @Bean
    public LoginFailHandler loginFailHandler() {
        return new LoginFailHandler();
    }

    @Override
    public void configure(HttpSecurity builder) throws Exception {
        builder.authenticationProvider(new MyUserNamePassProvider(userDetailsService));
        MyLoginFilter loginFilter = new MyLoginFilter();
        loginFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
        loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler());
        loginFilter.setAuthenticationFailureHandler(loginFailHandler());
        builder.addFilterAfter(loginFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

Create a new SpringSecurity general configuration class and add the login authentication configuration above.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    final LoginAuthenticationConfig loginAuthenticationConfig;

    public SecurityConfig(LoginAuthenticationConfig loginAuthenticationConfig) {
        this.loginAuthenticationConfig = loginAuthenticationConfig;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //Disable CSRF
                .csrf().disable()
                //Remove session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .logout().disable()
                .apply(loginAuthenticationConfig).and()
                .authorizeRequests().anyRequest().authenticated();
    }
}

So far, we have completed a login authentication configuration

Keywords: Spring Boot Spring Security

Added by Scott_J on Sun, 02 Jan 2022 11:41:11 +0200