Today to tell you how to expand the springboot framework to increase the mobile phone SMS verification code login function
SmsAuthenticationToken
Steps:
Principal originally stands for user name, but here it stands for mobile phone number. The credentials in the original code and password cannot be used for SMS login. Delete them directly. SmsCodeAuthenticationToken is () two methods to construct a structure without authentication, one is the structure of authentication. The remaining methods simply remove unwanted 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 login principal we use the cell phone number * @ param mobile * / public SmsCodeAuthenticationToken (String mobile) { super((Collection)null); this.principal = mobile; this.setAuthenticated(false); } / * * * after login authentication, principal user information we use * @ 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 the code
SmsAuthenticationFilter
The original static fields include username and password, both of which are eliminated, and replaced with our mobile phone number field. SmsCodeAuthenticationFilter () to intercept the Url specified in the filter, I designated as the way of the post/SMS/login. The rest of the method is invalid delete delete change.
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 {/ * * * form form in the field name * / mobile phone number public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile"; private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY; /** * Private Boolean postOnly = true; Public SmsCodeAuthenticationFilter () {/ / message/SMS/login login request post way 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 the code
SmsAuthenticationProvider
Steps:
Implements the AuthenticationProvider interface and implements the Authenticate () and Supports () methods. The supports() method determines how this Provider is selected by the AuthenticationManager, I here by return SmsCodeAuthenticationToken. Class. IsAssignableFrom (authentication), Handle all SmsCodeAuthenticationToken and its subclasses or child interface. The authenticate() method processes the authentication logic. The first will turn SmsCodeAuthenticationToken strong authentication. Retrieve the principal of the login, which is the phone number. Call your checkSmsCode() method to verify the verification code and throw an AuthenticationException if it is not valid. If no exception is found, call loadUserByUsername(mobile) to read the user information in the database. If it still reads successfully, with no exceptions, the validation is done here. To reconstruct SmsCodeAuthenticationToken after the authentication, and returned to the SmsCodeAuthenticationFilter. SmsCodeAuthenticationFilter parent in doFilter () method of handling exception, success, according to the result of processing logic to jump to login success/failure
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); // After the authentication is successful, Should go back to the new one has the authentication authenticationResult 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 requested mobile phone number does not match the logged-in mobile phone number "); } if(code ! = integer.parseInt (inputCode)) {throw new BadCredentialsException(" Verification code error "); } } @Override public boolean supports(Class<? > authentication) {/ / figure out whether the authentication SmsCodeAuthenticationToken subclasses of return or child interface SmsCodeAuthenticationToken.class.isAssignableFrom(authentication); } public UserDetailsService getUserDetailsService() { return userDetailsService; } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; }}Copy the code
Success and failure handling 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, Throws IOException, ServletException {logger.info(" Login successful "); response.setContentType("application/json; charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(authentication)); }}Copy the 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) 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 the 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 the code
SecurityConfiguration Configures SMS authentication
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)
// 以下短信登录认证的配置
.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 the code
Code structure logic
Send 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 the 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 the code