Spring security(四)-spring boot +spring security短信认证+redis整合
关注我,可以获取最新知识、经典面试题以及技术分享
现在主流的登录方式主要有 3 种:账号密码登录、短信验证码登录和第三方授权登录,前面一节Spring security(三)---认证过程已分析了spring security账号密码方式登陆,现在我们来分析一下spring security短信方式认证登陆。
Spring security 短信方式、IP验证等类似模式登录方式验证,可以根据账号密码方式登录步骤仿写出来,其主要以以下步骤进行展开:
- 自定义Filter:
- 自定义Authentication
- 自定义AuthenticationProvider
- 自定义UserDetailsService
- SecurityConfig配置
1. 自定义filter:
自定义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; } }
2. Authentication:
在filter以及后面的认证都需要使用到自定义的Authentication对象,自定义Authentication对象可以根据UsernamePasswordAuthenticationToken进行仿写,实现AbstractAuthenticationToken抽象类。
自定义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。可以根据AbstractUserDetailsAuthenticationProvider进行仿写,实现AuthenticationProvider以及MessageSourceAware接口。认证逻辑可以定义实现。
自定义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最终认证策略入口,认证方式实现逻辑是在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; } }
5.SecurityConfig
5.1 自定义Sms短信验证组件配置SecurityConfig
将自定义组件配置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(); } }
5.2 SecurityConfig主配置
SecurityConfig主配置可以参照第二节Spring Security(二)--WebSecurityConfigurer配置以及filter顺序进行配置。
SecurityConfig主配置:
@Configuration public 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工具类:
@Component public 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配置类:
@Configuration public class RedisConfig { @Autowired private 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; } }
7.总结
可以根据短信验证登陆模式去实现类似的验证方式,可以结合本节的例子进行跟项目结合起来,减少开发时间。后续还有第三方登陆方式分析以案例。最后错误请评论指出!
最后麻烦各位看官点个赞,谢谢支持!!!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Python 中 -m 的典型用法、原理解析与发展演变
在命令行中使用 Python 时,它可以接收大约 20 个选项(option),语法格式如下: python [-bBdEhiIOqsSuvVWx?] [-c command | -m module-name | script | - ] [args] 本文想要聊聊比较特殊的“-m”选项:关于它的典型用法、原理解析与发展演变的过程。 首先,让我们用“--help”来看看它的解释: > -m mod run library module as a script (terminates option list) "mod"是“module”的缩写,即“-m”选项后面的内容是 module(模块),其作用是把模块当成脚本来运行。 “terminates option list”意味着“-m”之后的其它选项不起作用,在这点上它跟“-c”是一样的,都是“终极选项”。官方把它们定义为“接口选项”(Interface options),需要区别于其它的普通选项或通用选项。 -m 选项的五个典型用法 Python 中有很多使用 -m 选项的场景,相信大家可能会用到或者看见过,我在这里想分享 5 ...
- 下一篇
Spring Boot从零入门6_Swagger2生成生产环境中REST API文档
本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客 微信公众号小白AI或者网站 https://xiaobaiai.net [TOC] 1 前言 在如今前后端分离开发的模式下,前端调用后端提供的API去实现数据的展示或者相关的数据操作,保证及时更新和完整的REST API文档将会大大地提高两边的工作效率,减少不必要的沟通成本。本文采用的Swagger2就是一个当前流行的通过少量的注解就可以生成漂亮的API文档工具,且在生成的在线文档中提供类似POSTMAN直接调试能力,不仅仅是静态的文档。接下来将会利用这个工具与Spring Boot项目结合,最终生成我们上一篇文章中所涉及到的REST API文档。 这一篇文章基本将Swagger2在生产环境中可能会用到的配置都有涉及,慢慢看吧,看了这一篇因该是够了。 2 Swagger2简介 Swagger是与用于实现 OpenAPI 文档广泛使用的工具,Swagger工具集包括开源工具,免费工具和商业工具的组合,可在API生命周期的不同阶段使用。 Swagger Editor(开源):使用Swagger编辑器,可以在浏览器内的YAML文档中...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Mario游戏-低调大师作品
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用