SpringSecurity之短信驗證碼

短信驗證碼

短信驗證碼的發送

須要實現對手機的發送驗證碼,這裏只是簡單的處理,打印出來java

  • 定義接口 //發送短信的接口 public interface SmsCodeSender {web

    void send(String mobile, String code);
      }
      //生成短信接口
      public interface ValidateCodeGenerator {
    
      ValidateCode generate(ServletWebRequest  request);
    
      }
  • 實現接口spring

    //發送短信的接口的實現
      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;
      	}
      }
  • 發送手機驗證碼session

    @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());
      }

上面的部分實現了手機驗證碼的生成,接下來要作的就是手機驗證碼的驗證。app

手機驗證碼的驗證

手機驗證碼的驗證由於不像用戶名密碼那樣能夠直接登陸,爲了實現手機的驗證,須要模仿用戶名和密碼的登陸方式,對用戶名登陸的那一套進行改造,須要建立一個短信驗證過濾器SmsAuthenticationFilter,短信驗證碼的token,SmsAuthenticaitonToken,而後須要一個驗證手機號的Provider,SmsAuthenticationProvider,而後將手機號傳遞給UserDetailService進行相關的處理.dom

SmsAuthenticationToken的代碼以下:ide

/**
 * 封裝登陸信息
 */
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.javaweb安全

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.javapost

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();

		}
	}

以後就能夠實現短信的驗證了

相關文章
相關標籤/搜索