西风吹老洞庭波,
一夜湘君白发多。
醉后不知天在水,
满船清梦压星河。
主要基于用户名密码的方式进行改造,不知道的可以先看一下: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();
}
这里注意配置一下登录和发送验证码的白名单
到这里就集成完了,可能并不完美,还需要根据自己的项目进行优化,后续我也会持续优化