🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 1. 认证实现 登录成功、登录失败、登出成功的处理。 <br/> 首先要明白一点,我们不需要自己写登录登出的 controller 层,Spring Security 已经帮我们封装好了。登录地址默认为`POST /login`,登出默认为`GET /logout`。 <br/> 步骤如下: **1. 实现登录成功处理器接口AuthenticationSuccessHandler** ```java @Component public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException { //可以获取登录成功后的账号实体User User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Map<String, Object> map = new HashMap<>(16); map.put("code", 2000); map.put("message", "登录成功!"); map.put("user", user); response.setContentType("text/json;charset=utf-8"); response.getWriter().write(JSON.toJSONString(map)); } } ``` **2. 实现登录失败处理器接口AuthenticationFailureHandler** ```java @Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(16); //根据抛出不同异常来判断账号的状态 if (ex instanceof AccountExpiredException) { map.put("code", 1000); map.put("message", "账号过期"); } if (ex instanceof BadCredentialsException) { map.put("code", 1001); map.put("message", "密码错误"); } if (ex instanceof CredentialsExpiredException) { map.put("code", 1002); map.put("message", "密码过期"); } if (ex instanceof DisabledException) { map.put("code", 1003); map.put("message", "账号不可用"); } if (ex instanceof LockedException) { map.put("code", 1004); map.put("message", "账号锁定"); } if (ex instanceof InternalAuthenticationServiceException) { map.put("code", 1005); map.put("message", "用户不存在"); } if (map.get("code") == null) { map.put("code", 1006); map.put("message", "未知错误"); } response.setContentType("text/json;charset=utf-8"); response.getWriter().write(JSON.toJSONString(map)); } } ``` **3. 实现登出成功处理器接口LogoutSuccessHandler** ```java @Component public class CustomLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException, ServletException { Map<String, Object> map = new HashMap<>(16); map.put("code", 2000); map.put("message", "退出成功"); response.setContentType("text/json;charset=utf-8"); response.getWriter().write(JSON.toJSONString(map)); } } ``` **4. 将上面的处理器注册** ```java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private CustomAuthenticationSuccessHandler authenticationSuccessHandler; @Autowired private CustomAuthenticationFailureHandler authenticationFailureHandler; @Autowired private CustomLogoutSuccessHandler logoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { ... http.formLogin() .permitAll() .successHandler(authenticationSuccessHandler) .failureHandler(authenticationFailureHandler); http.logout() .permitAll() .logoutSuccessHandler(logoutSuccessHandler) .deleteCookies("JSESSIONID"); } ... } ``` **5. 演示** 经过用 postman 演示,登录登出可以正常完成了。 <br/> # 2. 账号不存在与密码错误返回同一个异常问题 **1. 问题描述** 我用的 Spring Security 版本如下。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.3.12.RELEASE</version> </dependency> ``` 不知道是 Spring Security 这样设计有什么目的,还是一个 BUG,那就是无论是账号不存在,或者密码错误,登录失败处理器 AuthenticationFailureHandler 都统一返回密码错误异常 BadCredentialsException。按照上面的代码编写则不论是账号不存在,还是密码错误,返回的 json 字符串始终如下。 ```json { "code": 1001, "message": "密码错误" } ``` <br/> **2. 原因** (1)认证逻辑接口 UserDetailsService 默认抛出的异常为 UsernameNotFoundException。 ```java @Service public class LoginServiceImpl implements UserDetailsService { @Autowired private AccountService accountService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据username查询数据库 Account account = accountService.findByUsername(username); if (account == null) { throw new UsernameNotFoundException("用户名不存在!"); } //用户权限 List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); return new User(username, account.getPassword(), authorities); } } ``` (2)`this.hideUserNotFoundExceptions`默认为`true`。 ```java /** org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider **/ public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider,... { public Authentication authenticate(Authentication authentication) throws AuthenticationException { ... if (user == null) { cacheWasUsed = false; try { user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch (UsernameNotFoundException var6) { this.logger.debug("User '" + username + "' not found"); //因为hideUserNotFoundExceptions属性默认为true,所以无论是抛出UsernameNotFoundException,还是BadCredentialsException //都统一抛出的是密码错误异常这个类BadCredentialsException。 if (this.hideUserNotFoundExceptions) { throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } throw var6; ... } ``` <br/> **3. 解决办法** 认证逻辑接口 UserDetailsService 抛出异常改为 BadCredentialsException,或 InternalAuthenticationServiceException 即可。 ```java public class LoginServiceImpl implements UserDetailsService { @Autowired private AccountService accountService; @Override public UserDetails loadUserByUsername(String username) throws BadCredentialsException{ Account account = accountService.findByUsername(username); if (account == null) { throw new BadCredentialsException("用户不存在!"); } List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); return new User(username, account.getPassword(), authorities); } } ----------------------------------------------------------------------------------------- public class LoginServiceImpl implements UserDetailsService { @Autowired private AccountService accountService; @Override public UserDetails loadUserByUsername(String username) throws InternalAuthenticationServiceException{ Account account = accountService.findByUsername(username); if (account == null) { throw new InternalAuthenticationServiceException("用户不存在!"); } List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); return new User(username, account.getPassword(), authorities); } } ```