您好,登錄后才能下訂單哦!
現在主流的登錄方式主要有 3 種:賬號密碼登錄、短信驗證碼登錄和第三方授權登錄,前面一節Spring security(三)---認證過程已分析了spring security賬號密碼方式登陸,現在我們來分析一下spring security短信方式認證登陸。
??Spring security 短信方式、IP驗證等類似模式登錄方式驗證,可以根據賬號密碼方式登錄步驟仿寫出來,其主要以以下步驟進行展開:
自定義Filter:
自定義Authentication
自定義AuthenticationProvider
自定義UserDetailsService
SecurityConfig配置
??自定義filter可以根據UsernamePasswordAuthenticationFilter過濾器進行仿寫,其實質即實現AbstractAuthenticationProcessingFilter抽象類,主要流程分為:
構建構造器,并在構造器中進行配置請求路徑以及請求方式的過濾
自定義attemptAuthentication()認證步驟
在2步驟中認證過程中需要AuthenticationProvider進行最終的認證,在認證filter都需要將AuthenticationProvider設置進filter中,而管理AuthenticationProvider的是AuthenticationManager,因此我們創建過濾器filter的時候需要設置AuthenticationManager,這步具體詳情在5.1 SecurityConfig配置步驟。
在第2步中attemptAuthentication()認證方法主要進行以下步驟:
?? 1).post請求認證;
?? 2).request請求獲取手機號碼和驗證碼;
?? 3).用自定義的Authentication對象封裝手機號碼和驗證碼;
?? 4).使用AuthenticationManager.authenticate()方法進行驗證。
自定義filter實現代碼:
public?class?SmsAuthenticationfilter?extends?AbstractAuthenticationProcessingFilter?{????private?boolean?postOnly?=?true;????public?SmsAuthenticationfilter()?{??????super(new?AntPathRequestMatcher(SecurityConstants.APP_MOBILE_LOGIN_URL,?"POST")); ???} ????[@Override](https://my.oschina.net/u/1162528) ????public?Authentication?attemptAuthentication(HttpServletRequest?request,?HttpServletResponse?response)?throws?AuthenticationException,?IOException,?ServletException?{????????if?(postOnly?&&?!request.getMethod().equals("POST"))?{??????????????throw?new?AuthenticationServiceException(???????????????????"Authentication?method?not?supported:?"?+?request.getMethod()); ??????} ????????Assert.hasText(SecurityConstants.MOBILE_NUMBER_PARAMETER,?"mobile?parameter?must?not?be?empty?or?null"); ????? ?????????String?mobile?=?request.getParameter(SecurityConstants.MOBILE_NUMBER_PARAMETER); ????????String?smsCode?=?request.ge+tParameter(SecurityConstants.MOBILE_VERIFY_CODE_PARAMETER);????????if?(mobile?==?null)?{ ????????????mobile=""; ????????}????????if(smsCode?==?null){ ????????????smsCode=""; ????????} ????????mobile?=?mobile.trim(); ????????smsCode?=?smsCode.trim(); ????????SmsAuthenticationToken?authRequest?=?new?SmsAuthenticationToken(mobile,smsCode);????????//?Allow?subclasses?to?set?the?"details"?property ????????setDetails(request,?authRequest);???? ????????return?this.getAuthenticationManager().authenticate(authRequest); ????}protected?void?setDetails(HttpServletRequest?request, ??????????????????????????????SmsAuthenticationToken?authRequest)?{ ????????authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); ????}????public?void?setPostOnly(boolean?postOnly)?{????????this.postOnly?=?postOnly; ????} }
??在filter以及后面的認證都需要使用到自定義的Authentication對象,自定義Authentication對象可以根據UsernamePasswordAuthenticationToken進行仿寫,實現AbstractAuthenticationToken抽象類。https://www.jianshu.com/p/5e19f3a9f6dd
自定義SmsAuthenticationToken:
public?class?SmsAuthenticationToken?extends?AbstractAuthenticationToken?{????private?final?Object?principal;????private?Object?credentials;????public?SmsAuthenticationToken(Object?principal,Object?credentials?)?{????????super(null);????????this.principal?=?principal;????????this.credentials=credentials; ????????setAuthenticated(false); ????}????public?SmsAuthenticationToken(Object?principal,?Object?credentials,Collection<??extends?GrantedAuthority>?authorities)?{????????super(null);????????this.principal?=?principal;????????this.credentials=credentials; ????????setAuthenticated(true); ????} ????[@Override](https://my.oschina.net/u/1162528) ????public?Object?getCredentials()?{????????return?this.credentials=credentials; ????} ????[@Override](https://my.oschina.net/u/1162528) ????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"); ????????}????????super.setAuthenticated(false); ????} ????[@Override](https://my.oschina.net/u/1162528) ????public?void?eraseCredentials()?{????????super.eraseCredentials(); ????} }
??AuthenticationProvider最終認證策略入口,短信方式驗證需自定義AuthenticationProvider。可以根據AbstractUserDetailsAuthenticationProvider進行仿寫,實現AuthenticationProvider以及MessageSourceAware接口。認證邏輯可以定義實現。焦作國醫堂胃腸醫院正規嗎:http://jz.lieju.com/zhuankeyiyuan/37174867.htm
自定義AuthenticationProvider:
public?class?SmsAuthenticationProvide?implements?AuthenticationProvider,?MessageSourceAware?{??private?UserDetailsService?userDetailsService;??private?MessageSourceAccessor?messages?=?SpringSecurityMessageSource.getAccessor(); ????[@Override](https://my.oschina.net/u/1162528) ????public?void?setMessageSource(MessageSource?messageSource)?{????????this.messages?=?new?MessageSourceAccessor(messageSource); ????}????@Override ????public?Authentication?authenticate(Authentication?authentication)?{ ????????Assert.isInstanceOf(SmsAuthenticationToken.class,?authentication, ????????????????messages.getMessage(????????????????????????"AbstractUserDetailsAuthenticationProvider.onlySupports",????????????????????????"Only?UsernamePasswordAuthenticationToken?is?supported")); ????????SmsAuthenticationToken?authenticationToken?=?(SmsAuthenticationToken)?authentication;????????//將驗證信息保存在SecurityContext以供UserDetailsService進行驗證 ????????SecurityContext?context?=?SecurityContextHolder.getContext(); ????????context.setAuthentication(authenticationToken); ????????String?mobile?=?(String)?authenticationToken.getPrincipal();????????if?(mobile?==?null)?{????????????throw?new?InternalAuthenticationServiceException("can't?obtain?user?info?"); ????????} ????????mobile?=?mobile.trim();????????//進行驗證以及獲取用戶信息 ????????UserDetails?user?=?userDetailsService.loadUserByUsername(mobile);????????if?(user?==?null)?{????????????throw?new?InternalAuthenticationServiceException("can't?obtain?user?info?"); ????????} ????????SmsAuthenticationToken?smsAuthenticationToken?=?new?SmsAuthenticationToken(user,?user.getAuthorities());????????return?smsAuthenticationToken; ????}????@Override ????public?boolean?supports(Class<?>?authentication)?{????????return?(SmsAuthenticationToken.class.isAssignableFrom(authentication)); ????}????public?void?setUserDetailsService(UserDetailsService?userDetailsService)?{????????this.userDetailsService?=?userDetailsService; ????}????public?UserDetailsService?getUserDetailsService()?{????????return?userDetailsService; ????} }
??在AuthenticationProvider最終認證策略入口,認證方式實現邏輯是在UserDetailsService。可以根據自己項目自定義認證邏輯。
自定義UserDetailsService:
public?class?SmsUserDetailsService?implements?UserDetailsService?{????@Autowired ????private?RedisUtil?redisUtil;????@Override ????public?UserDetails?loadUserByUsername(String?username)?throws?UsernameNotFoundException?{????????//從SecurityContext獲取認證所需的信息(手機號碼、驗證碼) ????????SecurityContext?context?=?SecurityContextHolder.getContext(); ????????SmsAuthenticationToken?authentication?=?(SmsAuthenticationToken)?context.getAuthentication();????????if(!additionalAuthenticationChecks(username,authentication)){????????????return?null; ????????}????????//獲取用戶手機號碼對應用戶的信息,包括權限等 ????????return?new?User("admin",?"123456",?Arrays.asList(new?SimpleGrantedAuthority("admin"))); ????}????public?boolean?additionalAuthenticationChecks(String?mobile,?SmsAuthenticationToken?smsAuthenticationToken)?{????????//獲取redis中手機鍵值對應的value驗證碼 ????????String?smsCode?=?redisUtil.get(mobile).toString();????????//獲取用戶提交的驗證碼 ????????String?credentials?=?(String)?smsAuthenticationToken.getCredentials();????????if(StringUtils.isEmpty(credentials)){????????????return?false; ????????}????????if?(credentials.equalsIgnoreCase(smsCode))?{????????????return?true; ????????}????????return?false; ????} }
??將自定義組件配置SecurityConfig中,可以根據AbstractAuthenticationFilterConfigurer(子類FormLoginConfigurer)進行仿寫SmsAuthenticationSecurityConfig,主要進行以下配置:
將默認AuthenticationManager(也可以定義的)設置到自定義的filter過濾器中
將自定義的UserDetailsService設置到自定義的AuthenticationProvide中以供使用
將過濾器添加到過濾鏈路中,實施過濾操作。(一般以加在UsernamePasswordAuthenticationFilter前)
配置SmsAuthenticationSecurityConfig:
?@Component ?public?class?SmsAuthenticationSecurityConfig?extends?SecurityConfigurerAdapter<DefaultSecurityFilterChain,?HttpSecurity>?{????@Autowired ????private?UserDetailsService?userDetailsService;????@Override ????public?void?configure(HttpSecurity?http)?throws?Exception?{????????//創建并配置好自定義SmsAuthenticationfilter, ????????SmsAuthenticationfilter?smsAuthenticationfilter?=?new?SmsAuthenticationfilter(); ????????smsAuthenticationfilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); ????????smsAuthenticationfilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler()); ????????smsAuthenticationfilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler());????????//創建并配置好自定義SmsAuthenticationProvide ????????SmsAuthenticationProvide?smsAuthenticationProvide=new?SmsAuthenticationProvide(); ????????smsAuthenticationProvide.setUserDetailsService(userDetailsService); ????????http.authenticationProvider(smsAuthenticationProvide);????????//將過濾器添加到過濾鏈路中 ????????http.addFilterAfter(smsAuthenticationfilter,?UsernamePasswordAuthenticationFilter.class); ????}????@Bean ????public?CustomAuthenticationSuccessHandler?customAuthenticationSuccessHandler()?{????????return?new?CustomAuthenticationSuccessHandler(); ????}???? ????@Bean ????public?CustomAuthenticationFailureHandler?customAuthenticationFailureHandler()?{????????return?new?CustomAuthenticationFailureHandler(); ????} }
??SecurityConfig主配置可以參照第二節Spring Security(二)--WebSecurityConfigurer配置以及filter順序進行配置。
SecurityConfig主配置:
@Configurationpublic?class?WebSecurityConfig?extends?WebSecurityConfigurerAdapter?{????@Autowired ????private?SmsAuthenticationSecurityConfig?smsAuthenticationSecurityConfig;????@Autowired ????private?CustomAuthenticationSuccessHandler?authenticationSuccessHandler;????@Autowired ????private?CustomAuthenticationFailureHandler?authenticationFailureHandler;????@Override ????protected?void?configure(HttpSecurity?http)?throws?Exception?{ ????????http.headers().frameOptions().disable().and() ????????????????.formLogin() ????????????????.loginPage(SecurityConstants.APP_FORM_LOGIN_PAGE)????????????????//配置form登陸的自定義URL ????????????????.loginProcessingUrl(SecurityConstants.APP_FORM_LOGIN_URL) ????????????????.successHandler(authenticationSuccessHandler) ????????????????.failureHandler(authenticationFailureHandler) ????????????????.and()????????????????//配置smsAuthenticationSecurityConfig ????????????????.apply(smsAuthenticationSecurityConfig) ????????????????.and()????????????????//運行通過URL ????????????????.authorizeRequests() ????????????????.antMatchers(SecurityConstants.APP_MOBILE_VERIFY_CODE_URL, ?????????????????????????????SecurityConstants.APP_USER_REGISTER_URL) ????????????????.permitAll() ????????????????.and() ????????????????.csrf().disable(); ????}????@Bean ????public?ObjectMapper?objectMapper(){????????return?new?ObjectMapper(); ????} }
RedisUtil工具類:
@Componentpublic?class?RedisUtil?{????@Autowired ????private?RedisTemplate<String,?Object>?redisTemplate;????/** ?????*?普通緩存獲取 ?????* ?????*?@param?key?鍵 ?????*?@return?值 ?????*/ ????public?Object?get(String?key)?{????????return?key?==?null???null?:?redisTemplate.opsForValue().get(key); ????}????/** ?????*?普通緩存放入 ?????* ?????*?@param?key???鍵 ?????*?@param?value?值 ?????*?@return?true成功?false失敗 ?????*/ ????public?boolean?set(String?key,?Object?value)?{????????try?{ ????????????redisTemplate.opsForValue().set(key,?value);????????????return?true; ????????}?catch?(Exception?e)?{ ????????????e.printStackTrace();????????????return?false; ????????} ????}????/** ?????*?普通緩存放入并設置時間 ?????* ?????*?@param?key???鍵 ?????*?@param?value?值 ?????*?@param?time??時間(秒)?time要大于0?如果time小于等于0?將設置無限期 ?????*?@return?true成功?false?失敗 ?????*/ ????public?boolean?set(String?key,?Object?value,?long?time)?{????????try?{????????????if?(time?>?0)?{ ????????????????redisTemplate.opsForValue().set(key,?value,?time,?TimeUnit.SECONDS); ????????????}?else?{ ????????????????set(key,?value); ????????????}????????????return?true; ????????}?catch?(Exception?e)?{ ????????????e.printStackTrace();????????????return?false; ????????} ????} ?}
redisConfig配置類:
@Configurationpublic?class?RedisConfig?{@Autowiredprivate?RedisProperties?properties;@Bean@SuppressWarnings("all")@ConditionalOnClass(RedisConnectionFactory.class)public?RedisTemplate<String,?Object>?redisTemplate(RedisConnectionFactory?factory)?{ ????????RedisTemplate<String,?Object>?template?=?new?RedisTemplate<String,?Object>(); ????????template.setConnectionFactory(factory); ????????Jackson2JsonRedisSerializer?jackson2JsonRedisSerializer?=?new?Jackson2JsonRedisSerializer(Object.class); ????????ObjectMapper?om?=?new?ObjectMapper(); ????????om.setVisibility(PropertyAccessor.ALL,?JsonAutoDetect.Visibility.ANY); ????????om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); ????????jackson2JsonRedisSerializer.setObjectMapper(om); ????????StringRedisSerializer?stringRedisSerializer?=?new?StringRedisSerializer();????????//?key采用String的序列化方式 ????????template.setKeySerializer(stringRedisSerializer);????????//?hash的key也采用String的序列化方式 ????????template.setHashKeySerializer(stringRedisSerializer);????????//?value序列化方式采用jackson ????????template.setValueSerializer(jackson2JsonRedisSerializer);????????//?hash的value序列化方式采用jackson ????????template.setHashValueSerializer(jackson2JsonRedisSerializer); ????????template.afterPropertiesSet();????????return?template; ????}????@Bean ????@Qualifier("redisConnectionFactory")????public?RedisConnectionFactory?redisConnectionFactory(){ ????????RedisStandaloneConfiguration?redisConfig?=?new?RedisStandaloneConfiguration(); ????????redisConfig.setHostName(properties.getHost()); ????????redisConfig.setPort(properties.getPort()); ????????redisConfig.setPassword(RedisPassword.of(properties.getPassword())); ????????redisConfig.setDatabase(properties.getDatabase());????????//redis連接池數據設置 ????????JedisClientConfiguration.JedisClientConfigurationBuilder?builder?=?JedisClientConfiguration.builder();????????if?(this.properties.getTimeout()?!=?null)?{ ????????????Duration?timeout?=?this.properties.getTimeout(); ????????????builder.readTimeout(timeout).connectTimeout(timeout); ????????} ????????RedisProperties.Pool?pool?=?this.properties.getJedis().getPool();????????if?(pool?!=?null)?{ ????????????builder.usePooling().poolConfig(this.jedisPoolConfig(pool)); ????????} ????????JedisClientConfiguration?jedisClientConfiguration?=?builder.build();????????//根據兩個配置類生成JedisConnectionFactory ????????return?new?JedisConnectionFactory(redisConfig,jedisClientConfiguration); ????}????private?JedisPoolConfig?jedisPoolConfig(RedisProperties.Pool?pool)?{ ????????JedisPoolConfig?config?=?new?JedisPoolConfig(); ????????config.setMaxTotal(pool.getMaxActive()); ????????config.setMaxIdle(pool.getMaxIdle()); ????????config.setMinIdle(pool.getMinIdle());????????if?(pool.getMaxWait()?!=?null)?{ ????????????config.setMaxWaitMillis(pool.getMaxWait().toMillis()); ????????}????????return?config; ????} }
??可以根據短信驗證登陸模式去實現類似的驗證方式,可以結合本節的例子進行跟項目結合起來,減少開發時間。后續還有第三方登陸方式分析以案例。最后錯誤請評論指出!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。