> 文章列表 > springboot集成security(手机号+验证码)

springboot集成security(手机号+验证码)

springboot集成security(手机号+验证码)

西风吹老洞庭波,
一夜湘君白发多。
醉后不知天在水,
满船清梦压星河


主要基于用户名密码的方式进行改造,不知道的可以先看一下:springboot集成security(前后端分离 用户名密码登陆)

大概思路就是我们要改变security的验证方式,需要去实现AuthenticationProvider自定义一个token给security,改成我们自定义的验证方式,而不是使用账号密码的方式

SmsCodeAuthenticationProvider

@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {private SecurityUserServiceImpl userDetailsService;private RedisTemplate<String, Object> redisTemplate;public SmsCodeAuthenticationProvider(SecurityUserServiceImpl userService, RedisTemplate redisTemplate) {this.userDetailsService = userService;this.redisTemplate = redisTemplate;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;// 验证码校验String phone = (String) authenticationToken.getPrincipal();checkCode(phone);UserDetails user = userDetailsService.loadUserByUsername(phone);if (Objects.isNull(user)) {throw new InternalAuthenticationServiceException("用户名或密码错误!");}SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());authenticationResult.setDetails(authenticationToken.getDetails());return authenticationResult;}@Overridepublic boolean supports(Class<?> authentication) {return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);}private void checkCode(String phone) {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String inputCode = request.getParameter("captcha");Object code = redisTemplate.opsForValue().get(RedisKeyConstant.CAPTCHA + phone);if (ObjectUtils.isEmpty(code)){throw new CredentialsExpiredException("验证码过期,请重新获取!");}if(!Objects.equals(code, inputCode)) {throw new BadCredentialsException("验证码错误");}}} 

我是用阿里云发送的短信,然后存redis的,这个不用多说

验证码过滤器SmsCodeAuthenticationFilter:

public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {private boolean postOnly = true;private String mobileParameter = "phone";public SmsCodeAuthenticationFilter() {super(new AntPathRequestMatcher("/phone/login", HttpMethod.POST.name()));}public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (postOnly && !HttpMethod.POST.name().equals(request.getMethod())) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}// 获取请求中的参数值String mobile = request.getParameter("phone");if (Objects.isNull(mobile)) {mobile = "";}mobile = mobile.trim();SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {authRequest.setDetails(authenticationDetailsSource.buildDetails(request));}public void setMobileParameter(String mobileParameter) {Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");this.mobileParameter = mobileParameter;}public void setPostOnly(boolean postOnly) {this.postOnly = postOnly;}public final String getMobileParameter() {return mobileParameter;}} 

修改SecurityUserServiceImpl:

    @Overridepublic UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {SysUser sysUser = userService.getUserByPhone(phone);if (ObjectUtils.isEmpty(sysUser)) {throw new UsernameNotFoundException("用户名或密码不正确");}return new LoginUser(sysUser.getId().longValue(), sysUser.getName(), sysUser.getPassword(), getUserAuthority(sysUser.getId()));}

修改loadUserByUsername方法,之前是通过用户名查的,现在要通过手机号查

修改JwtAuthenticationFilter:

@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {String token = request.getHeader("Authorization");if(!StringUtils.hasText(token)){chain.doFilter(request,response);return;}Claims claim = jwtUtils.getClaimByToken(token);if(ObjectUtils.isEmpty(claim)){throw new JwtException("非法请求");}String username = claim.get("username").toString();Object redisToken = redisTemplate.opsForValue().get(RedisKeyConstant.USER_TOKEN + ":" + username);if (ObjectUtils.isEmpty(redisToken)){throw new AccountExpiredException("登录过期");}// 重置过期时间redisTemplate.opsForValue().set(RedisKeyConstant.USER_TOKEN + ":" + username, token, 30, TimeUnit.MINUTES);SysUser sysUser = userService.getOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getName, username));if (ObjectUtils.isEmpty(sysUser)){throw new CustomerException("非法请求");}
//        UsernamePasswordAuthenticationToken authenticationToken =
//                new UsernamePasswordAuthenticationToken(sysUser,null, securityUserService.getUserAuthority(sysUser.getId()));SmsCodeAuthenticationToken smsAbstractAuthenticationToken = new SmsCodeAuthenticationToken(sysUser, securityUserService.getUserAuthority(sysUser.getId()));SecurityContextHolder.getContext().setAuthentication(smsAbstractAuthenticationToken);chain.doFilter(request,response);}

之前是UsernamePasswordAuthenticationToken用户名密码方式的token,要改成我们自定义的短信验证方式的token

新加secruity的配置SmsCodeAuthenticationSecurityConfig:

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@Resourceprivate SecurityUserServiceImpl userDetailsService;@Resourceprivate LoginSuccessHandler customAuthenticationSuccessHandler;@Resourceprivate LoginFailureHandler customAuthenticationFailureHandler;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic void configure(HttpSecurity http) throws Exception {SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);smsCodeAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider(userDetailsService, redisTemplate);http.authenticationProvider(smsCodeAuthenticationProvider).addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}}

之前SecurityConfig里面配置额不要的东西可以删掉:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;@Resourceprivate JwtAccessDeniedHandler jwtAccessDeniedHandler;@Resourceprivate SecurityUserServiceImpl securityUserService;@Resourceprivate LoginFailureHandler loginFailureHandler;@Resourceprivate LoginSuccessHandler loginSuccessHandler;@Resourceprivate SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;@BeanBCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}@BeanJwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());return jwtAuthenticationFilter;}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}private static final String[] URL_WHITELIST = {"/login","/phone/login","/logout","/login/captcha","/favicon.ico",};
//    protected void configure(HttpSecurity http) throws Exception {
//        http.cors().and().csrf().disable()
//                .apply(smsCodeAuthenticationSecurityConfig).and().authorizeRequests().and()
//        //登录配置
//        .formLogin()
//        .successHandler(loginSuccessHandler)
//        .failureHandler(loginFailureHandler)
//
//        //禁用session
//        .and()
//                .sessionManagement()
//                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//        //配置拦截规则
//        .and()
//                .authorizeRequests()
//                .antMatchers(URL_WHITELIST).permitAll()
//                .anyRequest().authenticated()
//        //异常处理器
//                .and()
//                .exceptionHandling()
//                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
//                .accessDeniedHandler(jwtAccessDeniedHandler)
//        //配置自定义的过滤器
//                .and()
//                .addFilter(jwtAuthenticationFilter())
//    ;
//    }@Overrideprotected void configure(HttpSecurity http) throws Exception {http.apply(smsCodeAuthenticationSecurityConfig).and().authorizeRequests()// 如果有允许匿名的url,填在下面.antMatchers(URL_WHITELIST).permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin()// 设置登陆成功页.defaultSuccessUrl("/").permitAll().and().logout().permitAll();// 关闭CSRF跨域http.csrf().disable();}

这里注意配置一下登录和发送验证码的白名单

到这里就集成完了,可能并不完美,还需要根据自己的项目进行优化,后续我也会持续优化