在之前的所有样例中,登录表单一直都是使用 Spring Security 提供的默认登录页,登录成功后也是默认的页面跳转。有时我们想要使用自定义的登录页,或者在前后端分离的开发方式中,前后端的数据交互通过 JSON 进行,这时登录成功后就不是页面跳转了,而是一段 JSON 提示。下面通过样例演示如何进行登录表单的个性化配置。
六、自定义登录页面、登录接口、登录成功或失败的处理逻辑
1、样例代码
(1)首先修改 Spring Security 配置,增加相关的自定义代码:
-
将登录页改成使用自定义页面,并配置登录请求处理接口,以及用户密码提交时使用的参数名。
-
自定义了登录成功、登录失败的处理逻辑,根据情况返回响应的 JSON 数据。
@Configuration public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { // 指定密码的加密方式 @SuppressWarnings("deprecation") @Bean PasswordEncoder passwordEncoder(){ // 不对密码进行加密 return NoOpPasswordEncoder.getInstance(); }
1// 配置用户及其对应的角色 2@Override 3protected void configure(AuthenticationManagerBuilder auth) throws Exception { 4 auth.inMemoryAuthentication() 5 .withUser("root").password("123").roles("DBA") 6 .and() 7 .withUser("admin").password("123").roles("ADMIN") 8 .and() 9 .withUser("hangge").password("123").roles("USER"); 10} 11 12// 配置 URL 访问权限 13@Override 14protected void configure(HttpSecurity http) throws Exception { 15 http.authorizeRequests() // 开启 HttpSecurity 配置 16 .antMatchers("/db/**").hasRole("DBA") // db/** 模式URL需DBA角色 17 .antMatchers("/admin/**").hasRole("ADMIN") // admin/** 模式URL需ADMIN角色 18 .antMatchers("/user/**").hasRole("USER") // user/** 模式URL需USER角色 19 .anyRequest().authenticated() // 用户访问其它URL都必须认证后访问(登录后访问) 20 .and().formLogin() // 开启登录表单功能 21 .loginPage("/login_page") // 使用自定义的登录页面,不再使用SpringSecurity提供的默认登录页 22 .loginProcessingUrl("/login") // 配置登录请求处理接口,自定义登录页面、移动端登录都使用该接口 23 .usernameParameter("name") // 修改认证所需的用户名的参数名(默认为username) 24 .passwordParameter("passwd") // 修改认证所需的密码的参数名(默认为password) 25 // 定义登录成功的处理逻辑(可以跳转到某一个页面,也可以返会一段 JSON) 26 .successHandler(new AuthenticationSuccessHandler() { 27 @Override 28 public void onAuthenticationSuccess(HttpServletRequest req, 29 HttpServletResponse resp, 30 Authentication auth) 31 throws IOException, ServletException { 32 // 我们可以跳转到指定页面 33 // resp.sendRedirect("/index"); 34 35 // 也可以返回一段JSON提示 36 // 获取当前登录用户的信息,在登录成功后,将当前登录用户的信息一起返回给客户端 37 Object principal = auth.getPrincipal(); 38 resp.setContentType("application/json;charset=utf-8"); 39 PrintWriter out = resp.getWriter(); 40 resp.setStatus(200); 41 Map<String, Object> map = new HashMap<>(); 42 map.put("status", 200); 43 map.put("msg", principal); 44 ObjectMapper om = new ObjectMapper(); 45 out.write(om.writeValueAsString(map)); 46 out.flush(); 47 out.close(); 48 } 49 }) 50 // 定义登录失败的处理逻辑(可以跳转到某一个页面,也可以返会一段 JSON) 51 .failureHandler(new AuthenticationFailureHandler() { 52 @Override 53 public void onAuthenticationFailure(HttpServletRequest req, 54 HttpServletResponse resp, 55 AuthenticationException e) 56 throws IOException, ServletException { 57 resp.setContentType("application/json;charset=utf-8"); 58 PrintWriter out = resp.getWriter(); 59 resp.setStatus(401); 60 Map<String, Object> map = new HashMap<>(); 61 // 通过异常参数可以获取登录失败的原因,进而给用户一个明确的提示。 62 map.put("status", 401); 63 if (e instanceof LockedException) { 64 map.put("msg", "账户被锁定,登录失败!"); 65 }else if(e instanceof BadCredentialsException){ 66 map.put("msg","账户名或密码输入错误,登录失败!"); 67 }else if(e instanceof DisabledException){ 68 map.put("msg","账户被禁用,登录失败!"); 69 }else if(e instanceof AccountExpiredException){ 70 map.put("msg","账户已过期,登录失败!"); 71 }else if(e instanceof CredentialsExpiredException){ 72 map.put("msg","密码已过期,登录失败!"); 73 }else{ 74 map.put("msg","登录失败!"); 75 } 76 ObjectMapper mapper = new ObjectMapper(); 77 out.write(mapper.writeValueAsString(map)); 78 out.flush(); 79 out.close(); 80 } 81 }) 82 .permitAll() // 允许访问登录表单、登录接口 83 .and().csrf().disable(); // 关闭csrf 84}}
(2)在 resource/templates 目录下创建一个登录页面 login_page.html,内容如下:
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6</head> 7<body> 8 <form action="/login" method="post"> 9 <div> 10 <label>用户名</label> 11 <input type="text" name="name"/> 12 </div> 13 <div> 14 <label>密码</label> 15 <input type="password" name="passwd"/> 16 </div> 17 <div> 18 <input type="submit" value="登陆"> 19 </div> 20 </form> 21</body> 22</html>
(3)最后我们自定义一个 MVC 配置,并重写 addViewControllers 方法进行映射关系配置即可。
1@Configuration 2public class WebMvcConfig implements WebMvcConfigurer { 3 @Override 4 public void addViewControllers(ViewControllerRegistry registry) { 5 registry.addViewController("/login_page").setViewName("login_page"); 6 } 7}
2、运行测试
(1) 随便访问一个接口,浏览器会自动跳转到我们自定义的登录页面:

(2)如果填写正确的用户名密码提交,则返回如下信息:

(3)如果填写错误的用户名密码条,则返回如下信息:
