SpringSecurity之簡訊驗證碼
阿新 • • 發佈:2019-04-13
簡訊驗證碼
簡訊驗證碼的傳送
需要實現對手機的傳送驗證碼,這裡只是簡單的處理,打印出來
-
定義介面 //傳送簡訊的介面 public interface SmsCodeSender {
void send(String mobile, String code); } //生成簡訊介面 public interface ValidateCodeGenerator { ValidateCode generate(ServletWebRequest request); }
-
實現介面
//傳送簡訊的介面的實現 public class DefaultSmsCodeSender implements SmsCodeSender { /* (non-Javadoc) * [@see](https://my.oschina.net/weimingwei) com.imooc.security.core.validate.code.sms.SmsCodeSender#send(java.lang.String, java.lang.String) //生成簡訊介面的實現 [@Override](https://my.oschina.net/u/1162528) public void send(String mobile, String code) { System.out.println("向手機"+mobile+"傳送簡訊驗證碼"+code); } }
-
生成手機驗證碼 @Component("smsValidateCodeGenerator") public class SmsCodeGenerator implements ValidateCodeGenerator {
@Autowired private SecurityProperties securityProperties; /* * (non-Javadoc) * * [@see](https://my.oschina.net/weimingwei) * com.imooc.security.core.validate.code.ValidateCodeGenerator#generate(org. * springframework.web.context.request.ServletWebRequest) */ public ValidateCode generate(ServletWebRequest request) { String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLegth()); return new ValidateCode(code, securityProperties.getCode().getSms().getExpireIn()); } public SecurityProperties getSecurityProperties() { return securityProperties; } public void setSecurityProperties(SecurityProperties securityProperties) { this.securityProperties = securityProperties; } }
-
傳送手機驗證碼
@GetMapping("/code/sms") public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException { ImageCode imageCode = generate(request); ValidateCode smsCode = imageCodeGenerator.generate(new ServletWebRequest(request)); sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, smsCode); String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile"); smsCodeSender.send(mobile,smsCode.getCode()); }
上面的部分實現了手機驗證碼的生成,接下來要做的就是手機驗證碼的驗證。
手機驗證碼的驗證
手機驗證碼的驗證因為不像使用者名稱密碼那樣可以直接登入,為了實現手機的驗證,需要模仿使用者名稱和密碼的登入方式,對使用者名稱登入的那一套進行改造,需要建立一個簡訊驗證過濾器SmsAuthenticationFilter,簡訊驗證碼的token,SmsAuthenticaitonToken,然後需要一個驗證手機號的Provider,SmsAuthenticationProvider,然後將手機號傳遞給UserDetailService進行相關的處理.
SmsAuthenticationToken的程式碼如下:
/**
* 封裝登入資訊
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 420L;
//放認證資訊(登入之前放手機號,登入成功之後存放使用者的資訊)
private final Object principal;
public SmsCodeAuthenticationToken(String mobile) {
super((Collection)null);
this.principal = mobile;
this.setAuthenticated(false);
}
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();
}
}
SmsCodeAuthenticationFilter.java
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
//攔截的引數的名稱
private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
//表示是否只允許post請求
private boolean postOnly = true;
// ~ Constructors
// ===================================================================================================
/**
* 指定過濾器攔截的請求
*/
public SmsCodeAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));
}
// ~ Methods
// ========================================================================================================
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//判斷是不是post請求
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
//認證前將請求的資訊放入SmsCodeAuthenticationToken
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 獲取手機號
*/
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
/**
* 把請求的數放到認證請求裡面去。
* Provided so that subclasses may configure what is put into the
* authentication request's details property.
*
* @param request
* that an authentication request is being created for
* @param authRequest
* the authentication request object that should have its details
* set
*/
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/**
* Sets the parameter name which will be used to obtain the username from
* the login request.
*
* @param usernameParameter
* the parameter name. Defaults to "username".
*/
public void setMobileParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.mobileParameter = usernameParameter;
}
/**
* Defines whether only HTTP POST requests will be allowed by this filter.
* If set to true, and an authentication request is received which is not a
* POST request, an exception will be raised immediately and authentication
* will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
* will be called as if handling a failed authentication.
* <p>
* Defaults to <tt>true</tt> but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getMobileParameter() {
return mobileParameter;
}
}
SmsCodeAuthenticationProvider.java
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
/*
* (non-Javadoc)
*
* @see org.springframework.security.authentication.AuthenticationProvider#
* authenticate(org.springframework.security.core.Authentication)
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
if (user == null) {
throw new InternalAuthenticationServiceException("無法獲取使用者資訊");
}
//將認證的使用者資訊重新構建成一個物件
SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
/*
SmsCodeAuthenticationFilter的
將之前沒驗證的SmsCodeAuthenticationToken內的請求資訊重新放入到新建立的token裡面
*/
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/*
*判斷傳遞進來的東西是不是SmsCodeAuthenticationToken
* (non-Javadoc)
*
* @see org.springframework.security.authentication.AuthenticationProvider#
* supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
SmsCodeAuthenticationSecurityConfig.java(短息驗證的配置)
/**
* 該模組是要在app,也要在browser裡面用
*/
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity>{
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity builder) throws Exception {
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
//設定manager
smsCodeAuthenticationFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
//設定Provider
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
//將我們自定義的Provider加入到所有provider的集合裡面
builder.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/**
* 專門用來做web安全應用的介面卡WebSecurityConfigurerAdapter
*/
@Configuration
public class BrowswerSecurityConfig extends AbstractChannelSecurityConfig {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private ValidateCodeSecurityConfig validateCodeSecurityConfig;
/**
* 配置密碼編碼器
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Primary
@Bean(name = "dataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
/**
* @param
* @throws Exception
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
//配置資料來源
tokenRepository.setDataSource(dataSource);
//配置在啟動的時候建立表
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
applyPasswordAuthenticationConfig(http);
http.apply(validateCodeSecurityConfig)
.and()
.apply(smsCodeAuthenticationSecurityConfig)
.and()
// .apply(imoocSocialSecurityConfig)
// .and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
.userDetailsService(userDetailsService)
.and()
.authorizeRequests()
.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
securityProperties.getBrowser().getLoginPage(),
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
}
之後就可以實