在 Spring Security 中基于表單的認(rèn)證模式,默認(rèn)就是密碼帳號(hào)登錄認(rèn)證,那么對(duì)于短信驗(yàn)證碼+登錄的方式,Spring Security 沒有現(xiàn)成的接口可以使用,所以需要自己的封裝一個(gè)類似的認(rèn)證過(guò)濾器和認(rèn)證處理器實(shí)現(xiàn)短信認(rèn)證。
10余年專業(yè)網(wǎng)站制作公司歷程,堅(jiān)持以創(chuàng)新為先導(dǎo)的網(wǎng)站服務(wù),服務(wù)超過(guò)近1000家企業(yè)及個(gè)人,涉及網(wǎng)站設(shè)計(jì)、成都App制作、微信開發(fā)、平面設(shè)計(jì)、互聯(lián)網(wǎng)整合營(yíng)銷等多個(gè)領(lǐng)域。在不同行業(yè)和領(lǐng)域給人們的工作和生活帶來(lái)美好變化。
短信驗(yàn)證碼認(rèn)證
驗(yàn)證碼對(duì)象類設(shè)計(jì)
和圖片驗(yàn)證碼一樣,需要自己封裝一個(gè)驗(yàn)證碼對(duì)象,用來(lái)生成手機(jī)驗(yàn)證碼并發(fā)送給手機(jī)。因?yàn)閳D片驗(yàn)證碼和手機(jī)驗(yàn)證碼對(duì)象的區(qū)別就在于前者多了個(gè)圖片對(duì)象,所以兩者共同部分抽象出來(lái)可以設(shè)計(jì)成一個(gè)ValidateCode類,這個(gè)類里面只存放驗(yàn)證碼和過(guò)期時(shí)間,短信驗(yàn)證碼直接使用這個(gè)類即可:
import java.time.LocalDateTime;
import lombok.Data;
@Data
public class ValidateCode {
    private String code;
    private LocalDateTime expireTime;
    public ValidateCode(String code, int expireIn){
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
    }
    public boolean isExpried() {
        return LocalDateTime.now().isAfter(getExpireTime());
    }
    public ValidateCode(String code, LocalDateTime expireTime) {
        super();
        this.code = code;
        this.expireTime = expireTime;
    }圖片驗(yàn)證碼承繼此類:
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
import org.woodwhales.king.validate.code.ValidateCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper=false)
public class ImageCode extends ValidateCode {
    private BufferedImage image;
    public ImageCode(BufferedImage image, String code, int expireId) {
        super(code, LocalDateTime.now().plusSeconds(expireId));
        this.image = image;
    }
    public ImageCode(BufferedImage image, String code, LocalDateTime localDateTime) {
        super(code, localDateTime);
        this.image = image;
    }
}驗(yàn)證碼生成類設(shè)計(jì)
由于圖片和短信類均可以生成相應(yīng)的驗(yàn)證碼,所以直接設(shè)計(jì)一個(gè)驗(yàn)證碼生成接口,具體實(shí)現(xiàn)類根據(jù)業(yè)務(wù)進(jìn)行實(shí)現(xiàn):
import org.springframework.web.context.request.ServletWebRequest;
public interface ValidateCodeGenerator {
    ValidateCode generate(ServletWebRequest request);
}這里的傳參設(shè)計(jì)成了ServletWebRequest是能夠根據(jù)前端請(qǐng)求中的參數(shù)進(jìn)行不同的業(yè)務(wù)實(shí)現(xiàn)
目前實(shí)現(xiàn)累只有圖片生成器和驗(yàn)證碼生成器:
// 圖片驗(yàn)證碼生成器
@Component("imageCodeGenerator")
public class ImageCodeGenerator implements ValidateCodeGenerator {
    /**
     * 生成圖形驗(yàn)證碼
     * @param request
     * @return
     */
    @Override
    public ValidateCode generate(ServletWebRequest request) {
        ……
        return new ImageCode(image, sRand, SecurityConstants.EXPIRE_SECOND);
    }
}
// 短信驗(yàn)證碼生成器
@Component("smsCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {
    @Override
    public ValidateCode generate(ServletWebRequest request) {
        String code = RandomStringUtils.randomNumeric(SecurityConstants.SMS_RANDOM_SIZE);
        return new ValidateCode(code, SecurityConstants.SMS_EXPIRE_SECOND);
    }
}短信驗(yàn)證碼發(fā)送接口設(shè)計(jì)
短信驗(yàn)證碼生成之后,需要設(shè)計(jì)接口依賴短信服務(wù)提供商進(jìn)行驗(yàn)證碼發(fā)送,因此至少設(shè)計(jì)一個(gè)統(tǒng)一的接口,供短信服務(wù)提供商生成發(fā)送短信服務(wù):
public interface SmsCodeSender {
// 至少需要手機(jī)號(hào)和驗(yàn)證碼
void send(String mobile, String code);
}
為了演示,設(shè)計(jì)一個(gè)虛擬的默認(rèn)×××,只在日志文件中打印一行l(wèi)og:
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
/**
*/@Slf4j
br/>@Slf4j
public class DefaultSmsCodeSender implements SmsCodeSender {
@Override
public void send(String mobile, String code) {
log.debug("send to mobile :{}, code : {}", mobile, code);
}
}
短信驗(yàn)證碼請(qǐng)求Controller
所有驗(yàn)證碼的請(qǐng)求都在統(tǒng)一的ValidateCodeController里,這里注入了兩個(gè)驗(yàn)證碼生成器ValidateCodeGenerator,后期可以利用 spring 的依賴查找/搜索技巧來(lái)重構(gòu)代碼,另外所有的請(qǐng)求也是可以做成動(dòng)態(tài)配置,這里臨時(shí)全部 hardCode 在代碼里:
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import org.woodwhales.king.core.commons.SecurityConstants;
import org.woodwhales.king.validate.code.ValidateCode;
import org.woodwhales.king.validate.code.ValidateCodeGenerator;
import org.woodwhales.king.validate.code.image.ImageCode;
import org.woodwhales.king.validate.code.sms.DefaultSmsCodeSender;
@RestController
public class ValidateCodeController {
@Autowired
private SessionStrategy sessionStrategy;
@Autowired
private ValidateCodeGenerator imageCodeGenerator;
@Autowired
private ValidateCodeGenerator smsCodeGenerator;
@Autowired
private DefaultSmsCodeSender defaultSmsCodeSender;
@GetMapping("code/image")
public void createImageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
    ImageCode imageCode = (ImageCode)imageCodeGenerator.generate(new ServletWebRequest(request));
    sessionStrategy.setAttribute(new ServletWebRequest(request), SecurityConstants.SESSION_KEY, imageCode);
    ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}
@GetMapping("code/sms")
public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
    ValidateCode smsCode = smsCodeGenerator.generate(new ServletWebRequest(request));
    sessionStrategy.setAttribute(new ServletWebRequest(request), SecurityConstants.SESSION_KEY, smsCode);
    String mobile = ServletRequestUtils.getStringParameter(request, "mobile");
    defaultSmsCodeSender.send(mobile, smsCode.getCode());
}}
從上述代碼中可以看出圖片驗(yàn)證碼和短信驗(yàn)證碼的生成請(qǐng)求邏輯是相似的:首先調(diào)用驗(yàn)證碼生成接口生成驗(yàn)證碼,然后將驗(yàn)證碼放入 session 中,最后將驗(yàn)證碼返回給前端或者用戶。因此這個(gè)套路流程可以抽象成一個(gè)模板方法,以增強(qiáng)代碼的可維護(hù)性和可擴(kuò)展性。
用一張圖來(lái)表述重構(gòu)后的代碼結(jié)構(gòu):

隨機(jī)驗(yàn)證碼過(guò)濾器設(shè)計(jì)
由于圖片和手機(jī)都會(huì)產(chǎn)生驗(yàn)證碼,后期還可以通過(guò)郵件發(fā)送隨機(jī)驗(yàn)證碼的方式進(jìn)行隨機(jī)驗(yàn)證碼登錄驗(yàn)證,因此將隨機(jī)驗(yàn)證碼的認(rèn)證可以獨(dú)立封裝在一個(gè)隨機(jī)驗(yàn)證碼過(guò)濾器中,并且這個(gè)過(guò)濾器在整個(gè) spring security 過(guò)濾器鏈的最前端(它是第一道認(rèn)證墻)。
隨機(jī)驗(yàn)證碼過(guò)濾器只要繼承 spring 框架中的OncePerRequestFilter即可保證這個(gè)過(guò)濾器在請(qǐng)求來(lái)的時(shí)候只被調(diào)用一次,具體代碼實(shí)現(xiàn)參見文末源碼。
這里重點(diǎn)解釋一下如何將隨機(jī)驗(yàn)證碼過(guò)濾器配置到 spring security 過(guò)濾器認(rèn)證最前端,需要重寫SecurityConfigurerAdapter的configure()方法,并將自定義的過(guò)濾器放到AbstractPreAuthenticatedProcessingFilter過(guò)濾器之前即可:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
@Component
public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private Filter validateCodeFilter;
@Override
public void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
}}
短信驗(yàn)證碼認(rèn)證
在自定義短信登錄認(rèn)證流程之前,建議可以移步到之前的文章:SpringBoot + Spring Security 學(xué)習(xí)筆記(二)安全認(rèn)證流程源碼詳解,了解清除用戶密碼的認(rèn)證流程才能更容易理解下面這張經(jīng)典的流程圖:

左側(cè)是用戶+密碼的認(rèn)證流程,整體的流程就是經(jīng)過(guò)用戶名+密碼認(rèn)證過(guò)濾器認(rèn)證,將請(qǐng)求封裝成 token 并注入到 AutheticationMananger 中,之后由默認(rèn)的認(rèn)證校驗(yàn)器進(jìn)行校驗(yàn),在校驗(yàn)的過(guò)程中會(huì)調(diào)用 UserDetailsService 接口進(jìn)行 token 校驗(yàn),當(dāng)校驗(yàn)成功之后,就會(huì)將已經(jīng)認(rèn)證的 token 放到 SecurityContextHolder 中。
同理,由于短信登錄方式只需要使用隨機(jī)驗(yàn)證碼進(jìn)行校驗(yàn)而不需要密碼登錄功能,當(dāng)校驗(yàn)成功之后就認(rèn)為用戶認(rèn)證成功了,因此需要仿造左側(cè)的流程開發(fā)自定義的短信登錄認(rèn)證 token,這個(gè) token 只需要存放手機(jī)號(hào)即可,在token 校驗(yàn)的過(guò)程中,不能使用默認(rèn)的校驗(yàn)器了,需要自己開發(fā)校驗(yàn)當(dāng)前自定義 token 的校驗(yàn)器,最后將自定義的過(guò)濾器和校驗(yàn)器配置到 spring security 框架中即可。
注意:短信隨機(jī)驗(yàn)證碼的驗(yàn)證過(guò)程是在 SmsCodeAuthticationFIlter 之前就已經(jīng)完成。
短信登錄認(rèn)證Token
仿造UsernamePasswordAuthenticationToken設(shè)計(jì)一個(gè)屬于短信驗(yàn)證的認(rèn)證 token 對(duì)象,為什么要自定義一個(gè)短信驗(yàn)證的 token,spring security 框架不只提供了用戶名+密碼的驗(yàn)證方式,用戶認(rèn)證是否成功,最終看的就是SecurityContextHolder對(duì)象中是否有對(duì)應(yīng)的AuthenticationToken,因此要設(shè)計(jì)一個(gè)認(rèn)證對(duì)象,當(dāng)認(rèn)證成功之后,將其設(shè)置到SecurityContextHolder即可。
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
public SmsCodeAuthenticationToken(Object mobile) {
    super(null);
    this.principal = mobile;
    setAuthenticated(false);
}
public SmsCodeAuthenticationToken(Object mobile, Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = mobile;
    super.setAuthenticated(true); // must use super, as we override
}
public Object getPrincipal() {
    return this.principal;
}
public Object getCredentials() {
    return null;
}
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");
    }
    super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
    super.eraseCredentials();
}}
從AuthenticationToken接口可以看到,現(xiàn)在框架中有我們自己定義短信登錄的 token 了:
短信登錄認(rèn)證過(guò)濾器
短信驗(yàn)證碼的過(guò)濾器設(shè)計(jì)思路同理,仿造UsernamePasswordAuthenticationFilter過(guò)濾器,這里再次提醒,短信隨機(jī)驗(yàn)證碼
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.woodwhales.core.constants.SecurityConstants;
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
 * 請(qǐng)求中的參數(shù)
 */
private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
private boolean postOnly = true;
public SmsCodeAuthenticationFilter() {
    super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    // 獲取請(qǐng)求中的參數(shù)值
    String mobile = obtainMobile(request);
    if (Objects.isNull(mobile)) {
        mobile = "";
    }
    mobile = mobile.trim();
    SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
}
/**
 * 獲取手機(jī)號(hào)
 */
protected String obtainMobile(HttpServletRequest request) {
    return request.getParameter(mobileParameter);
}
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
    authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
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;
}
public final String getMobileParameter() {
    return mobileParameter;
}}
短信驗(yàn)證碼過(guò)濾器也成為了AbstractAuthenticationProcessingFilter其中一個(gè)子類,后期需要注冊(cè)到安全配置中,讓它成為安全認(rèn)證過(guò)濾鏈中的一環(huán):

短信登錄認(rèn)證校驗(yàn)器
短信登錄認(rèn)證校驗(yàn)器的作用就是調(diào)用UserDetailsService的loadUserByUsername()方法對(duì) authenticationToken 進(jìn)行校驗(yàn),所有校驗(yàn)器的根接口為:AuthenticationProvider,因此自定義的短信登錄認(rèn)證校驗(yàn)器實(shí)現(xiàn)這個(gè)接口,重寫authenticate()即可:
import java.util.Objects;
import org.springframework.security.authentication.AuthenticationProvider;
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 lombok.Data;
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
    /**
     * 調(diào)用 {@link UserDetailsService}
     */
    UserDetails user = userDetailsService.loadUserByUsername((String)authenticationToken.getPrincipal());
    if (Objects.isNull(user)) {
        throw new InternalAuthenticationServiceException("無(wú)法獲取用戶信息");
    }
    SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
    authenticationResult.setDetails(authenticationToken.getDetails());
    return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
    return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}}
注意,這里使用@Data注解生成 setter 和 getter 方法。

短信登錄認(rèn)證安全配置設(shè)計(jì)
設(shè)計(jì)一個(gè)封裝好的短信登錄認(rèn)證配置類,以供外部調(diào)用者直接調(diào)用:
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.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) throws Exception {
    SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
    smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
    smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
    smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
    // 獲取驗(yàn)證碼提供者
    SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
    smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
    // 將短信驗(yàn)證碼校驗(yàn)器注冊(cè)到 HttpSecurity, 并將短信驗(yàn)證碼過(guò)濾器添加在 UsernamePasswordAuthenticationFilter 之前
    http.authenticationProvider(smsCodeAuthenticationProvider).addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}}
當(dāng)外部想要引用這個(gè)封裝好的配置,只需要在自定義的AbstractChannelSecurityConfig安全認(rèn)證配置中添加進(jìn)去即可,注意這個(gè)配置對(duì)象使用了@Component注解,注冊(cè)到了spring 中,所以可以直接通過(guò)@Autowired引用,如:
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.stereotype.Component;
import org.woodwhales.core.authentication.sms.AbstractChannelSecurityConfig;
import org.woodwhales.core.authentication.sms.SmsCodeAuthenticationSecurityConfig;
import org.woodwhales.core.validate.code.config.ValidateCodeSecurityConfig;
@Component
public class BrowserSecurityConfig extends AbstractChannelSecurityConfig {
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private ValidateCodeSecurityConfig validateCodeSecurityConfig;
@Autowired
protected AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
protected AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
        .loginPage("/authentication/require") // 登錄頁(yè)面回調(diào)
        .successHandler(authenticationSuccessHandler)// 認(rèn)證成功回調(diào)
        .failureHandler(authenticationFailureHandler)
        // 以下驗(yàn)證碼的校驗(yàn)配置
        .and()
        .apply(validateCodeSecurityConfig) 
        // 以下短信登錄認(rèn)證的配置
        .and()
        .apply(smsCodeAuthenticationSecurityConfig)
        // 記住我的配置
        .and()
        .rememberMe()
        .tokenRepository(persistentTokenRepository())
        .tokenValiditySeconds(3600) // 設(shè)置記住我的過(guò)期時(shí)間
        .userDetailsService(userDetailsService)
        .and()
        // 請(qǐng)求做授權(quán)配置
        .authorizeRequests() 
        // 以下請(qǐng)求路徑不需要認(rèn)證
        .antMatchers("/authentication/require",
                "/authentication/mobile",
                "/login",
                "/code/*",
                "/")
        .permitAll() 
        .anyRequest() // 任何請(qǐng)求
        .authenticated() // 都需要身份認(rèn)證
        // 暫時(shí)將防護(hù)跨站請(qǐng)求偽造的功能置為不可用
        .and()
        .csrf().disable();
}
/**
 * 配置TokenRepository
 * @return
 */
@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    // 初始化記住我的數(shù)據(jù)庫(kù)表,建議通過(guò)看源碼直接創(chuàng)建出來(lái)
    // jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}}
這里的配置中有些代碼出現(xiàn)了冗余配置,可以全部封裝成抽象模板,完成一些基礎(chǔ)的配置。
                網(wǎng)頁(yè)標(biāo)題:SpringBoot+SpringSecurity學(xué)習(xí)筆記實(shí)現(xiàn)短信驗(yàn)證碼+登錄功能
                
                本文地址:http://chinadenli.net/article32/gghipc.html
            
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動(dòng)態(tài)網(wǎng)站、移動(dòng)網(wǎng)站建設(shè)、建站公司、微信公眾號(hào)、網(wǎng)站內(nèi)鏈、網(wǎng)站收錄
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
