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

    @Override
    public 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;
    }

    @Override
    public 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:

    @Override
    public 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:

@Override
    protected 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> {
    @Resource
    private SecurityUserServiceImpl userDetailsService;
    @Resource
    private LoginSuccessHandler customAuthenticationSuccessHandler;
    @Resource
    private LoginFailureHandler customAuthenticationFailureHandler;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public 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 {

    @Resource
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Resource
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Resource
    private SecurityUserServiceImpl securityUserService;

    @Resource
    private LoginFailureHandler loginFailureHandler;

    @Resource
    private LoginSuccessHandler loginSuccessHandler;

    @Resource
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
        return jwtAuthenticationFilter;
    }

    @Bean
    @Override
    public 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())
//    ;
//    }

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值