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