Springboot extended mobile phone SMS verification code login + vue front desk implementation

Springboot extended mobile phone SMS verification code login + vue front desk implementation

Today I will tell you how to expand and increase the login function of mobile phone SMS verification code in the springboot framework.

SmsAuthenticationToken

step:

The principal originally represented the user name, but reserved here, it just represents the mobile phone number. Credentials is the original code and password, which is not needed for SMS login, so delete it directly. SmsCodeAuthenticationToken() Two construction methods, one is constructed without authentication, and the other is constructed with authentication. The remaining methods can remove useless attributes.

package com.jjxt.b.config; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = 420L; private final Object principal; /** * Before logging in, principal we use the mobile number * @param mobile */ public SmsCodeAuthenticationToken(String mobile) { super((Collection)null); this.principal = mobile; this.setAuthenticated(false); } /** * After login authentication, principal we use user information * @param principal * @param authorities */ public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; super.setAuthenticated(true); } public Object getCredentials() { return null; } public Object getPrincipal() { return this.principal; } public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException("Cannot set this token to trusted-use constructor which takes a GrantedAuthority list instead"); } else { super.setAuthenticated(false); } } public void eraseCredentials() { super.eraseCredentials(); } } Copy code

SmsAuthenticationFilter

The original static fields are username and password, all of which are killed and replaced with our mobile phone number field. The interception Url of this filter is specified in SmsCodeAuthenticationFilter(), and I specified it as/sms/login in post mode. The rest of the method just changes invalid deletions.

package com.jjxt.b.config; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { /** * The field name of the mobile phone number in the form */ public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile"; private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY; /** * Whether only POST mode */ private boolean postOnly = true; public SmsCodeAuthenticationFilter() { ///Sms/login in the post mode of request for SMS login super(new AntPathRequestMatcher("/api/sms/login", "POST")); } @Override public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: "+ request.getMethod()); } System.out.println( request.getParameter("mobile")); String mobile = obtainMobile(request); if (mobile == null) { mobile = ""; } mobile = mobile.trim(); SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile); //Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } protected String obtainMobile(HttpServletRequest request) { return request.getParameter(mobileParameter); } protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } public String getMobileParameter() { return mobileParameter; } public void setMobileParameter(String mobileParameter) { Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null"); this.mobileParameter = mobileParameter; } public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } } Copy code

SmsAuthenticationProvider

step:

Implement the AuthenticationProvider interface and implement the authenticate() and supports() methods. The supports() method determines how this Provider should be picked by AuthenticationManager. Here, I use return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication) to process all SmsCodeAuthenticationToken and its subclasses or subinterfaces. The authenticate() method handles the authentication logic. 1. force the authentication to SmsCodeAuthenticationToken. Take out the registered principal, which is the mobile phone number. Call the checkSmsCode() method written by yourself to verify the verification code. If it is illegal, an AuthenticationException will be thrown. If there is still no abnormality at this time, read the user information in the database by calling loadUserByUsername(mobile). If it can still be read successfully and there are no exceptions, the verification here is complete. Reconstruct the authenticated SmsCodeAuthenticationToken and return it to SmsCodeAuthenticationFilter. The parent class of SmsCodeAuthenticationFilter handles whether there is an exception in the doFilter() method, whether it is successful, and jumps to the login success/failure logic according to the processing result

package com.jjxt.b.config; import lombok.Data; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Map; @Data public class SmsCodeAuthenticationProvider implements AuthenticationProvider { private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication; String mobile = (String) authenticationToken.getPrincipal(); checkSmsCode(mobile); UserDetails userDetails = userDetailsService.loadUserByUsername(mobile); //At this time, after the authentication is successful, you should renew an authenticationResult with authentication to return SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities()); authenticationResult.setDetails(authenticationToken.getDetails()); return authenticationResult; } private void checkSmsCode(String mobile) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String inputCode = request.getParameter("code"); Map<String, Object> smsCode = (Map<String, Object>) request.getSession().getAttribute("smscode"); if(smsCode == null) { throw new BadCredentialsException("Application verification code not detected"); } String applyMobile = (String) smsCode.get("mobile"); int code = (int) smsCode.get("code"); if(!applyMobile.equals(mobile)) { throw new BadCredentialsException("The applied mobile phone number is not consistent with the login mobile phone number"); } if(code != Integer.parseInt(inputCode)) { throw new BadCredentialsException("Verification code error"); } } @Override public boolean supports(Class<?> authentication) { //Determine if authentication is a subclass or subinterface of SmsCodeAuthenticationToken return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication); } public UserDetailsService getUserDetailsService() { return userDetailsService; } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } } Copy code

Success and failure processing logic

package com.jjxt.b.config.security; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.pdfbox.jbig2.util.log.Logger; import org.apache.pdfbox.jbig2.util.log.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { logger.info("Login successful"); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); } } Copy code

Verification failure handling

package com.jjxt.b.config.security; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.pdfbox.jbig2.util.log.Logger; import org.apache.pdfbox.jbig2.util.log.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Autowired private ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass()); @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("Login failed"); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage())); } } Copy code

SmsCodeAuthenticationSecurityConfig

package com.jjxt.b.config; import com.jjxt.b.config.security.CustomAuthenticationFailureHandler; import com.jjxt.b.config.security.CustomAuthenticationSuccessHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.stereotype.Component; @Component public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; @Autowired private CustomAuthenticationFailureHandler customAuthenticationFailureHandler; @Autowired private UserDetailsService userDetailsService; @Override public void configure(HttpSecurity http) throws Exception { SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter(); smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); smsCodeAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler); SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider(); smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService); http.authenticationProvider(smsCodeAuthenticationProvider) .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } } Copy code

SecurityConfiguration configuration SMS verification

package com.jjxt.b.config; import com.jjxt.b.config.security.*; import com.jjxt.b.service.sys.UsersService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.csrf.*; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; import java.io.IOException; import java.util.Arrays; import java.util.UUID; //import com.jjxt.b.config.SmsCodeAuthenticationSecurityConfig; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private String key; @Value("${feature.csrf.enabled}") private boolean enableCSRF; @Autowired private UsersService usersService; @Autowired private AuthenticationEntryPoint authenticationEntryPoint; @Autowired @Lazy private PasswordEncoder passwordEncoder; @Qualifier(PersistentConfiguration.DataSources.CORE) @Autowired private DataSource dataSource; @Autowired PersistentTokenRepository persistentTokenRepository; @Autowired AbstractRememberMeServices rememberMeServices; @Autowired CSRFProcessor csrfProcessor; @Autowired private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authProvider()); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } private static final String PROJECT_URL="/api/projects/"; private static final String BIDSECT_URL= PROJECT_URL+"bidsect/"; private static final String USER_URL = PROJECT_URL + "users/"; private static final String UNIT_URL = PROJECT_URL+"units/"; private static final String CONTRACT_URL = PROJECT_URL+"contracts/"; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/api/user/login","/api/user/sms/**","/api/sms/**", "/test/**").permitAll() .antMatchers("/api/admin/users/**").hasAnyRole("ADMIN", "SUPER_ADMIN") .antMatchers("/api/admin/company/all").hasAnyRole("ADMIN", "SUPER_ADMIN") .antMatchers("/api/admin/**").hasAnyRole( "SUPER_ADMIN") //can only edit by _PEDITOR. .antMatchers(Arrays.asList("create","update", "delete", "flowupdate").stream().map(i -> PROJECT_URL+i) .toArray(String[]::new)).hasAnyRole("_PEDITOR") .antMatchers(Arrays.asList("create","update", "delete").stream().map(i -> BIDSECT_URL+i) .toArray(String[]::new)).hasAnyRole("_PEDITOR") .antMatchers(Arrays.asList("add","delete").stream().map(i -> USER_URL+i) .toArray(String[]::new)).hasAnyRole("_PEDITOR") .antMatchers(Arrays.asList("create","update", "delete").stream().map(i -> UNIT_URL+i) .toArray(String[]::new)).hasAnyRole("_PEDITOR") .antMatchers(Arrays.asList("create","update", "delete").stream().map(i -> CONTRACT_URL+i) .toArray(String[]::new)).hasAnyRole("_PEDITOR") //mind the order. .antMatchers("/api/**").authenticated() .and().apply(new CustomizedRememberMeConfigurer<>()).key(key) .rememberMeServices(rememberMeServices).tokenRepository(persistentTokenRepository) //The following SMS login authentication configuration .and() .apply(smsCodeAuthenticationSecurityConfig) .and() .exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler()) .authenticationEntryPoint(authenticationEntryPoint); if (enableCSRF) { http.csrf().csrfTokenRepository(csrfTokenRepository()) .and() .addFilterAfter(new OncePerRequestFilter() { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { csrfProcessor.process(request, response); filterChain.doFilter(request, response); } }, CsrfFilter.class); } else { http.csrf().disable(); } } @Bean CSRFProcessor csrfProcessor(){ return enableCSRF? new DefaultCSRFProcessor(): new IgnoreCSRFProcessor(); } private HttpSessionCsrfTokenRepository csrfTokenRepository() { HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); repository.setHeaderName("X-XSRF-TOKEN"); return repository; } @Bean public CustomizedRememberMeServices rememberMeServices() { CustomizedRememberMeServices services = new CustomizedRememberMeServices(getKey(), usersService, persistentTokenRepository); return services; } String getKey() { if (this.key == null) { this.key = UUID.randomUUID().toString(); } return this.key; } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl(); db.setDataSource(dataSource); return db; } @Bean public AuthenticationEntryPoint securityException401EntryPoint() { return new AuthenticationEntryPoint() { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); } }; } @Bean public DaoAuthenticationProvider authProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(usersService); authProvider.setPasswordEncoder(passwordEncoder); return authProvider; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Bean @Override public UserDetailsService userDetailsService() { return usersService; } public static class CustomAccessDeniedHandler implements AccessDeniedHandler { private static final Logger LOG = LoggerFactory.getLogger(CustomAccessDeniedHandler.class); @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { LOG.error("access denied", accessDeniedException); } } } Copy code

Code structure logic

Send the verification code

@RequestMapping("/sms/code") @ResponseBody public Object sms(@RequestParam("mobile") String mobile, HttpSession session) throws ClientException { UserDetails u = usersService.getPhone(mobile); if(u!=null) { int code = (int) Math.ceil(Math.random() * 9000 + 1000); Map<String, Object> map = new HashMap<>(16); map.put("mobile", mobile); map.put("code", code); SmsUtil.sendSmsCode(mobile, code+""); session.setAttribute("smscode", map); return "1"; }else { return "0"; } } Copy code

Front-end vue code

export function plogin(data) { return request({ url:'/sms/login', method:'post', headers: { "Content-Type": "multipart/form-data" }, params:data }) } Copy code