- 输入正确的验证码
- 输入已经使用过的验证码 各位读者是不是会觉得既然继承了 Filter,那是不是每个接口都会进入到我们的自定义方法中呀!如果是继承了 GenericFilterBean、OncePerRequestFilter 那是肯定会的,需要手动处理。 但我们继承的是 UsernamePasswordAuthenticationFilter,security 已经帮忙处理了。处理逻辑在其父类 AbstractAuthenticationProcessingFilter#doFilter 中。
编写自定义认证逻辑
验证码逻辑编写完成,那接下来就自定义一个 VerifyCodeAuthenticationProvider 继承自 DaoAuthenticationProvider,并重写 authenticate 方法。
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 验证码验证器
*/
public class VerifyCodeAuthenticationProvider extends DaoAuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 获得请求验证码值
String code = req.getParameter("code");
// 获得 session 中的 验证码值
HttpSession session = req.getSession();
String sessionVerifyCode = (String) session.getAttribute("verify_code");
if (StringUtils.isEmpty(code)){
throw new AuthenticationServiceException("验证码不能为空!");
}
if(StringUtils.isEmpty(sessionVerifyCode)){
throw new AuthenticationServiceException("请重新申请验证码!");
}
if (!code.toLowerCase().equals(sessionVerifyCode.toLowerCase())) {
throw new AuthenticationServiceException("验证码错误!");
}
// 验证码验证成功,清除 session 中的验证码
session.removeAttribute("verify_code");
// 验证码验证成功,走原本父类认证逻辑
return super.authenticate(authentication);
}
}
自定义的认证逻辑完成了,剩下的问题就是如何让 security 走我们的认证逻辑了。
在 security 中,所有的 AuthenticationProvider 都是放在 ProviderManager 中统一管理的,所以接下来我们就要自己提供 ProviderManager,然后注入自定义的 VerifyCodeAuthenticationProvider。
- SecurityConfig
import cn.cxyxj.study02.config.MyAuthenticationFailureHandler;
import cn.cxyxj.study02.config.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("cxyxj").password("123").roles("admin").build());
manager.createUser(User.withUsername("security").password("security").roles("user").build());
return manager;
}
@Bean
VerifyCodeAuthenticationProvider verifyCodeAuthenticationProvider() {
VerifyCodeAuthenticationProvider provider = new VerifyCodeAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService());
return provider;
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
ProviderManager manager = new ProviderManager(verifyCodeAuthenticationProvider());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //开启配置
// 验证码接口放行
.antMatchers("/verify-code").permitAll()
.anyRequest() //其他请求
.authenticated()//验证 表示其他请求需要登录才能访问
.and()
.formLogin()
.loginPage("/login.html") //登录页面
.loginProcessingUrl("/auth/login") //登录接口,此地址可以不真实存在
.usernameParameter("account") //用户名字段
.passwordParameter("pwd") //密码字段
.successHandler(new MyAuthenticationSuccessHandler())
.failureHandler(new MyAuthenticationFailureHandler())
.permitAll() // 上述 login.html 页面、/auth/login接口放行
.and()
.csrf().disable(); // 禁用 csrf 保护
;
}
}
测试
- 不传入验证码发起请求。
- 请求获取验证码接口
- 输入错误的验证码