溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring security(四)-spring boot

發(fā)布時間:2020-07-18 23:00:10 來源:網(wǎng)絡(luò) 閱讀:397 作者:艾弗森哇 欄目:開發(fā)技術(shù)

現(xiàn)在主流的登錄方式主要有 3 種:賬號密碼登錄、短信驗證碼登錄和第三方授權(quán)登錄,前面一節(jié)Spring security(三)---認證過程已分析了spring security賬號密碼方式登陸,現(xiàn)在我們來分析一下spring security短信方式認證登陸。

??Spring security 短信方式、IP驗證等類似模式登錄方式驗證,可以根據(jù)賬號密碼方式登錄步驟仿寫出來,其主要以以下步驟進行展開:

  1. 自定義Filter:

  2. 自定義Authentication

  3. 自定義AuthenticationProvider

  4. 自定義UserDetailsService

  5. SecurityConfig配置

1. 自定義filter:

??自定義filter可以根據(jù)UsernamePasswordAuthenticationFilter過濾器進行仿寫,其實質(zhì)即實現(xiàn)AbstractAuthenticationProcessingFilter抽象類,主要流程分為:

  1. 構(gòu)建構(gòu)造器,并在構(gòu)造器中進行配置請求路徑以及請求方式的過濾

  2. 自定義attemptAuthentication()認證步驟

  3. 在2步驟中認證過程中需要AuthenticationProvider進行最終的認證,在認證filter都需要將AuthenticationProvider設(shè)置進filter中,而管理AuthenticationProvider的是AuthenticationManager,因此我們創(chuàng)建過濾器filter的時候需要設(shè)置AuthenticationManager,這步具體詳情在5.1 SecurityConfig配置步驟

在第2步中attemptAuthentication()認證方法主要進行以下步驟:
?? 1).post請求認證;
?? 2).request請求獲取手機號碼和驗證碼;
?? 3).用自定義的Authentication對象封裝手機號碼和驗證碼;
?? 4).使用AuthenticationManager.authenticate()方法進行驗證。

自定義filter實現(xiàn)代碼:

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;
????}

}

2. Authentication:

??在filter以及后面的認證都需要使用到自定義的Authentication對象,自定義Authentication對象可以根據(jù)UsernamePasswordAuthenticationToken進行仿寫,實現(xiàn)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();

????}
}

3.AuthenticationProvider

??AuthenticationProvider最終認證策略入口,短信方式驗證需自定義AuthenticationProvider??梢愿鶕?jù)AbstractUserDetailsAuthenticationProvider進行仿寫,實現(xiàn)AuthenticationProvider以及MessageSourceAware接口。認證邏輯可以定義實現(xiàn)。焦作國醫(yī)堂胃腸醫(yī)院正規(guī)嗎: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;
????}
}

4. UserDetailsService

??在AuthenticationProvider最終認證策略入口,認證方式實現(xiàn)邏輯是在UserDetailsService??梢愿鶕?jù)自己項目自定義認證邏輯。

自定義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;
????????}????????//獲取用戶手機號碼對應(yīng)用戶的信息,包括權(quán)限等
????????return?new?User("admin",?"123456",?Arrays.asList(new?SimpleGrantedAuthority("admin")));
????}????public?boolean?additionalAuthenticationChecks(String?mobile,?SmsAuthenticationToken?smsAuthenticationToken)?{????????//獲取redis中手機鍵值對應(yīng)的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;
????}
}

5.SecurityConfig

5.1 自定義Sms短信驗證組件配置SecurityConfig

??將自定義組件配置SecurityConfig中,可以根據(jù)AbstractAuthenticationFilterConfigurer(子類FormLoginConfigurer)進行仿寫SmsAuthenticationSecurityConfig,主要進行以下配置:

  1. 將默認AuthenticationManager(也可以定義的)設(shè)置到自定義的filter過濾器中

  2. 將自定義的UserDetailsService設(shè)置到自定義的AuthenticationProvide中以供使用

  3. 將過濾器添加到過濾鏈路中,實施過濾操作。(一般以加在UsernamePasswordAuthenticationFilter前)

配置SmsAuthenticationSecurityConfig:

?@Component
?public?class?SmsAuthenticationSecurityConfig?extends?SecurityConfigurerAdapter<DefaultSecurityFilterChain,?HttpSecurity>?{????@Autowired
????private?UserDetailsService?userDetailsService;????@Override
????public?void?configure(HttpSecurity?http)?throws?Exception?{????????//創(chuàng)建并配置好自定義SmsAuthenticationfilter,
????????SmsAuthenticationfilter?smsAuthenticationfilter?=?new?SmsAuthenticationfilter();
????????smsAuthenticationfilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
????????smsAuthenticationfilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler());
????????smsAuthenticationfilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler());????????//創(chuàng)建并配置好自定義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();
????}
}

5.2 SecurityConfig主配置

??SecurityConfig主配置可以參照第二節(jié)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();
????}
}

6.其他

6.1 redis

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;
????????}
????}????/**
?????*?普通緩存放入并設(shè)置時間
?????*
?????*?@param?key???鍵
?????*?@param?value?值
?????*?@param?time??時間(秒)?time要大于0?如果time小于等于0?將設(shè)置無限期
?????*?@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連接池數(shù)據(jù)設(shè)置
????????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();????????//根據(jù)兩個配置類生成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;
????}
}

7.總結(jié)

??可以根據(jù)短信驗證登陸模式去實現(xiàn)類似的驗證方式,可以結(jié)合本節(jié)的例子進行跟項目結(jié)合起來,減少開發(fā)時間。后續(xù)還有第三方登陸方式分析以案例。最后錯誤請評論指出!


向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI