spring security :ajax登录
这一篇文章便不是具体的教程,只会说我这个登录涉及到的相关东西。
WebSecurityConfigurerAdapter的实现类:
值得注意的是,userService应该是实现了UserDetailsService接口。setHideUserNotFoundExceptions(false)这个设置为false的话,可以返回UserNotFoundException,从而可以区分是用户名还是密码有问题。
值得注意的是,failureForwardUrl和failureUrl的区别是前者访问的是 /user/login/failure 的@PostMapping请求,而后者访问的则是@GetMapping请求。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 设置不隐藏 未找到用户异常
provider.setHideUserNotFoundExceptions(false);
// 用户认证service - 查询数据库的逻辑
provider.setUserDetailsService(userService);
// 设置密码加密算法
provider.setPasswordEncoder(passwordEncoder());
auth.authenticationProvider(provider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//用于配置直接放行的请求
.antMatchers("/user/login/failure","/login").permitAll()
//其余请求都需要验证
.anyRequest().authenticated()
.and().formLogin()
//自定登录页面
.loginPage("/login")
//登录处理url,在ajax post请求/base/user/login会被这里捕获
.loginProcessingUrl("/user/login")
//登录成功会跳到controller的这个地址
.successForwardUrl("/user/login/success")
//登录失败会跳到controller的这个地址
.failureForwardUrl("/user/login/failure")
//也可以用failureUrl,登录失败会跳到controller的这个地址
//.failureUrl("/user/login/failure")
//禁用跨站伪造
.and().csrf().disable();
}
UserController.java中捕获返回,这里直接返回String,可以改成返回JSON对象
值得注意的是,如果用的是failureForwardUrl,则exception 是存到request中的,而用的是failureUrl的话,则exception 是存到session中的。
推荐是用failureForwardUrl喽,session干净点,心情好一点。
@GetMapping("/user/login/failure")
@ResponseBody
public Stringfailure1(HttpServletRequest request){
AuthenticationException exception = (AuthenticationException)request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
String msg = exception.getMessage();
if (exception instanceof UsernameNotFoundException) {
msg = "用户名不正确";
} else if (exception instanceof BadCredentialsException) {
msg = "密码错误";
} else if (exception instanceof DisabledException) {
msg = "用户已被禁用";
} else if (exception instanceof LockedException) {
msg = "账户被锁定";
} else if (exception instanceof AccountExpiredException) {
msg = "账户过期";
} else if (exception instanceof CredentialsExpiredException) {
msg = "证书过期";
}
return msg;
}
@PostMapping("/user/login/failure")
@ResponseBody
public String failure(HttpServletRequest request){
AuthenticationException exception = (AuthenticationException)request.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
String msg = exception.getMessage();
if (exception instanceof UsernameNotFoundException) {
msg = "用户名不正确";
} else if (exception instanceof BadCredentialsException) {
msg = "密码错误";
} else if (exception instanceof DisabledException) {
msg = "用户已被禁用";
} else if (exception instanceof LockedException) {
msg = "账户被锁定";
} else if (exception instanceof AccountExpiredException) {
msg = "账户过期";
} else if (exception instanceof CredentialsExpiredException) {
msg = "证书过期";
}
return msg;
}
@PostMapping("/user/login/success")
@ResponseBody
public String success(){
return "登录成功";
}
值得注意的是,这个东西我测试了几次,是成功的,如果你测试到失败了,请务必留下信息。
另外在需要抛出自定义信息的时候,可以创建AuthenticationException的内部类转发到该接口,如校验验证码的时候:
public class VerifyFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger("VerifyFilter");
private static final PathMatcher pathMatcher = new AntPathMatcher();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(isProtectedUrl(request)) {
String verifyCode = request.getParameter("verifyCode");
if(StringUtils.isEmpty(verifyCode)){
error(request, response, new AuthenticationException("验证码不能为空"){});
} else if(!validateVerify(verifyCode)) {
error(request, response, new AuthenticationException("验证码输入错误"){});
} else {
filterChain.doFilter(request,response);
}
} else {
filterChain.doFilter(request,response);
}
}
private boolean validateVerify(String inputVerify) {
//获取当前线程绑定的request对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 不分区大小写
// 这个validateCode是在servlet中存入session的名字
String validateCode = ((String) request.getSession().getAttribute("loginVerifyCode")).toLowerCase();
inputVerify = inputVerify.toLowerCase();
logger.info("验证码:" + validateCode + " 用户输入:" + inputVerify);
return validateCode.equals(inputVerify);
}
// 拦截 /base/user/login
private boolean isProtectedUrl(HttpServletRequest request) {
return "POST".equals(request.getMethod()) && pathMatcher.match("/base/user/login", request.getServletPath());
}
private void error(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws ServletException, IOException {
//手动设置异常
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION,exception);
// 转发到错误Url
request.getRequestDispatcher("/user/login/failure").forward(request,response);
}
}