在 Spring Security 中默认的登陆是通过表单的形式进行的。但是在前后端分离的项目中很少会使用表单的形式登陆。大多数情况是由前端调用登陆接口,登陆后则会返回 JSON 格式的响应告诉前端是否成功,根据返回进行跳转页面或其他操作就可以由前端来进行判断了。
那接下来就看一看在 Spring Security 中如何使用 JSON 格式登陆吧!
本文配套的示例源码: https://github.com/lxiaocode/spring-security-examples
你将会学到什么
Spring Security 的默认配置。
Spring Security 是如何处理认证异常的?
Spring Security 是如何获取用户输入参数的?
自定义身份验证过滤器,实现 JSON 格式的登陆。
将自定义的过滤器配置到 Spring Security。
Spring Security 在登陆后会进行什么操作?
自定义登陆 成功/失败 处理器。
1. Spring Security 的默认配置 1.1 创建 Spring Security 项目 首先需要创建一个 Spring Security 的项目。你可以使用 Spring Initializr 进行创建,也可以使用 Maven 进行创建。因为以后可能还会继续写关于 Spring Security 相关的示例,所以本文配套的源码是使用 Maven 创建的一个多模块项目,以后的示例都会放到这个项目中。
创建项目之后添加以下依赖:
spring-boot-starter-web <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
spring-boot-starter-security <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency >
fastjson <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.62</version > </dependency >
编写一个用于测试的接口:
Application.java @SpringBootApplication @RestController @RequestMapping ("/" )public class Application { public static void main (String[] args) { SpringApplication.run(Application.class ) ; } @GetMapping ("" ) public String index () { return "index.html" ; } }
1.2 Spring Security 默认配置
启动默认配置后,该配置会创建一个名为 springSecurityFilterChain
的 Servlet 过滤器 bean。这个 bean 负责应用程序中所有的安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。
使用用户名和随机生成的密码创建一个 UserDetailsService
bean,并记录到控制台。
向 springSecurityFilterChain
注册过滤器。
1.2.1 默认配置实现的功能 虽然 Spring Security 的默认配置不多,但却实现了很多功能:
需要通过身份验证才能与应用程序进行交互。
为你生成一个默认的登陆表单。
让用户使用 user
用户名和密码通过基于表单的身份验证(再前面的示例中,密码为 8e557245-73e2-4286-969a-ff57fe326336
)。
使用 BCrypt 保护密码的储存。
允许用户注销。
预防 CSRF 攻击。
Session Fixation 保护。
Security Header 集成。
Servlet API方法集成。
HttpServletRequest#getRemoteUser()
HttpServletRequest.html#getUserPrincipal()
HttpServletRequest.html#isUserInRole(java.lang.String)
HttpServletRequest.html#login(java.lang.String, java.lang.String)
HttpServletRequest.html#logout()
1.2.2 默认的 Spring Security 项目 如果你看不懂上面的内容,那么你就当作都是废话。
现在启动项目,你会发现在控制中打印了一串字符:
Using generated security password: cb102f8a-8286-496b-92c0-d36989e55987
很明显这是 Spring Security 为我们自动生成密码,每次启动项目这个密码都会重新生成。有了密码,那么用户名时什么呢?没错就是 “user”。
为什么默认的用户名是 “user” 呢?这个密码又是在哪里生成的呢?这些问题我都会在以后的文章中解释,所以请密切关注我的博客( www.lxiaocode.com ) 或者公众号 (lxiao学习日记)。
然后访问我们刚刚编写的测试接口:http://localhost:8080/ 。你会发现会被重定向到 Spring Security 提供的默认登陆页面(/login)。默认的登陆页面和登陆接口的 URL 都是 “/login”,登陆页面为 GET 请求,登陆接口为 POST 请求。
在登陆表单中根据 Spring Security 提供的默认用户名和生成的密码就可以登陆了,登陆成功后会跳转到你之前访问的接口上。
以上就是 Spring Security 默认配置为我们提供的功能,就是一个典型的基于表单的登陆功能。
2. 认证异常处理 在默认的 Spring Security 登陆流程中,如果你在未登陆的情况下会被重定向到登陆页面。但在前后端分离的项目中,后端是没有登陆页面的,更不可能重定向到登陆页面。通常的做法是返回一串 JSON 格式的信息提示前端该用户没有登陆,由前端为用户跳转到登陆页面。
2.1 默认的认证异常处理 Spring Security 默认的认证异常处理在 LoginUrlAuthenticationEntryPoint
中执行,该类实现了 AuthenticationEntryPoint
接口。这个接口就是用于处理认证异常的:
AuthenticationEntryPoint.java public interface AuthenticationEntryPoint { void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException ;}
实现类中的 commence()
方法:
LoginUrlAuthenticationEntryPoint.java public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint , InitializingBean { public void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { String redirectUrl = null ; if (useForward) { if (forceHttps && "http" .equals(request.getScheme())) { redirectUrl = buildHttpsRedirectUrlForRequest(request); } if (redirectUrl == null ) { String loginForm = determineUrlToUseForThisRequest(request, response, authException); if (logger.isDebugEnabled()) { logger.debug("Server side forward to: " + loginForm); } RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm); dispatcher.forward(request, response); return ; } } else { redirectUrl = buildRedirectUrlToLoginPage(request, response, authException); } redirectStrategy.sendRedirect(request, response, redirectUrl); } }
从上面的实现方法中可以看出,请求会被重定向到登陆页面。
所以我们主要的思路就是提供一个自定义的 AuthenticationEntryPoint
接口实现类,然后替换掉默认的 LoginUrlAuthenticationEntryPoint
。
2.2 自定义认证处理异常 我们知道认证异常处理是由 AuthenticationEntryPoint
接口提供的,所以我们只需实现它即可:
JsonAuthenticationEntryPoint.java public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint { public void commence (HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); map.put("message" , "尚未登陆" ); map.put("data" , authException.getMessage()); JSONObject json = new JSONObject(map); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ }finally { if (out != null ){ out.close(); } } } }
2.3 配置认证异常处理 实现认证异常处理完成之后,我们要将它覆盖掉默认的认证异常处理。这时我们需要一个 Spring Security 配置类:
SecurityConfig.java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); map.put("message" , "尚未登陆" ); map.put("data" , authException.getMessage()); JSONObject json = new JSONObject(map); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ }finally { if (out != null ){ out.close(); } } }); } }
2.4 测试 现在启动项目,在没有登陆的情况下访问接口会得到以下信息:
{ "message" : "尚未登陆" , "data" : "Full authentication is required to access this resource" , "code" : 401 }
3. JSON 格式登陆 在默认的 Spring Security 登陆流程中,登陆的方式是通过表单进行的。同样的,在前后端分离的项目依然是通过 JSON 格式进行登陆而不是表单。下面就看一看 Spring Security 默认的登陆逻辑以及如何实现 JSON 格式登陆吧!
3.1 默认的表单登陆方式 Spring Security 是基于过滤器链来实现的,当请求进入 Spring Seucirty 后会经过一个个职责不同的过滤器。而其中负责处理登陆的过滤器就是 UsernamePasswordAuthenticationFilter
。
而在 UsernamePasswordAuthenticationFilter
过滤器中核心的方法就是 attemptAuthentication()
:
UsernamePasswordAuthenticationFilter.java public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username" ; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password" ; private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true ; public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (postOnly && !request.getMethod().equals("POST" )) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); String password = obtainPassword(request); if (username == null ) { username = "" ; } if (password == null ) { password = "" ; } username = username.trim(); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this .getAuthenticationManager().authenticate(authRequest); } @Nullable protected String obtainPassword (HttpServletRequest request) { return request.getParameter(passwordParameter); } @Nullable protected String obtainUsername (HttpServletRequest request) { return request.getParameter(usernameParameter); } }
从上面的方法可以看出,Spring Security 默认会在请求的参数中获取用户的输入。而我们所希望的是获取请求 Body 中的 JSON 信息。
同样实现方式很简单,提供一个自定义的 UsernamePasswordAuthenticationFilter
,然后替换掉默认的过滤器。
3.2 自定义身份验证过滤器 我们只需要继承默认的 UsernamePasswordAuthenticationFilter
过滤器,然后重写 attemptAuthentication()
方法即可:
JsonAuthenticationFilter.java public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!"POST" .equals(request.getMethod())) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){ Map<String, String> loginData = new HashMap<>(2 ); try { loginData = new ObjectMapper().readValue(request.getInputStream(), Map.class ) ; } catch (IOException e) { e.printStackTrace(); } String username = loginData.get(getUsernameParameter()); String password = loginData.get(getPasswordParameter()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authentication); return this .getAuthenticationManager().authenticate(authentication); }else { return super .attemptAuthentication(request, response); } } }
3.3 配置身份验证过滤器 回到我们的 SecurityConfig
配置类中,在这对自定义身份验证过滤器进行配置:
SecurityConfig.java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); map.put("message" , "尚未登陆" ); map.put("data" , authException.getMessage()); JSONObject json = new JSONObject(map); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ }finally { if (out != null ){ out.close(); } } }); UsernamePasswordAuthenticationFilter authenticationFilter = new JsonAuthenticationFilter(); authenticationFilter.setAuthenticationManager(authenticationManagerBean()); http.addFilterAt(authenticationFilter, UsernamePasswordAuthenticationFilter.class ) ; } }
3.4 测试 由于我们替换了认证异常处理,现在不会自动跳转到登陆页面了,所以需要我们自己调用 POST /login 接口进行登陆(这个接口是 Spring Security 默认为我们提供的不需要我们创建)。
在 Postman 中进行登陆测试,登陆成功后则会跳转到首页(默认为 “/“):
4. 配置登陆 成功/失败 处理器 在 Spring Security 的默认配置中,当用户登陆成功后会自动跳转到之前访问的接口或首页。在前后端分离的项目中,登陆成功的跳转往往是有前端控制,后端只需告诉前端用户是否登陆成功就可以了。
4.1 默认的登陆 成功/失败 处理 Spring Security 默认的登陆 成功/失败 处理在 UsernamePasswordAuthenticationFilter
的父类的 doFilter()
方法中:
AbstractAuthenticationProcessingFilter.java public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware , MessageSourceAware { public void doFilter (ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return ; } if (logger.isDebugEnabled()) { logger.debug("Request is to process authentication" ); } Authentication authResult; try { authResult = attemptAuthentication(request, response); if (authResult == null ) { return ; } sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user." , failed); unsuccessfulAuthentication(request, response, failed); return ; } catch (AuthenticationException failed) { unsuccessfulAuthentication(request, response, failed); return ; } if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); } protected void successfulAuthentication (HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } SecurityContextHolder.getContext().setAuthentication(authResult); rememberMeServices.loginSuccess(request, response, authResult); if (this .eventPublisher != null ) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this .getClass())); } successHandler.onAuthenticationSuccess(request, response, authResult); } protected void unsuccessfulAuthentication (HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); if (logger.isDebugEnabled()) { logger.debug("Authentication request failed: " + failed.toString(), failed); logger.debug("Updated SecurityContextHolder to contain null Authentication" ); logger.debug("Delegating to authentication failure handler " + failureHandler); } rememberMeServices.loginFail(request, response); failureHandler.onAuthenticationFailure(request, response, failed); } }
根据上面的源码解析,我们知道了 Spring Security 登陆成功/失败 处理器是哪里调用的了。我们只需要替换掉默认处理器即可。
4.2 自定义登陆 成功/失败 处理器 4.2.1 处理器接口 Spring Security 处理登陆成功和失败是由两个不同的接口实现的。我们需要分别实现这两个接口,然后将默认的处理替换掉即可:
AuthenticationSuccessHandler.java public interface AuthenticationSuccessHandler { default void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { onAuthenticationSuccess(request, response, authentication); chain.doFilter(request, response); } void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException ;}
AuthenticationFailureHandler.java public interface AuthenticationFailureHandler { void onAuthenticationFailure (HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException ;}
4.2.2 登陆成功处理器实现 逻辑非常简单,与认证异常处理的逻辑差不多:
LoginSuccessHandler public class LoginSuccessHandler implements AuthenticationSuccessHandler { public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 200 ); map.put("msg" , "登陆成功" ); map.put("data" , authentication.getPrincipal().toString()); JSONObject json = new JSONObject(map); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ e.printStackTrace(); }finally { if (out != null ){ out.close(); } } } }
4.2.3 登陆失败处理器实现 与登陆成功处理器很像,但是要处理一些登陆失败的异常:
LoginFailureHandler public class LoginFailureHandler implements AuthenticationFailureHandler { public void onAuthenticationFailure (HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); if (exception instanceof LockedException){ map.put("msg" , "账户被锁定" ); }else if (exception instanceof CredentialsExpiredException){ map.put("msg" , "密码过期" ); }else if (exception instanceof AccountExpiredException){ map.put("msg" , "账户过期" ); }else if (exception instanceof DisabledException){ map.put("msg" , "账户被禁用" ); }else if (exception instanceof BadCredentialsException){ map.put("msg" , "用户名或者密码输入错误" ); } map.put("data" , exception.getMessage()); JSONObject json = new JSONObject(map); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ e.printStackTrace(); }finally { if (out != null ){ out.close(); } } } }
4.3 配置处理器 因为这两个处理器是包含在身份验证过滤器中的,所以我们需要在身份验证过滤器中添加这两个过滤器。我在来到添加身份验证过滤器的 SecurityConfig
配置类中:
SecurityConfig @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); map.put("message" , "尚未登陆" ); map.put("data" , authException.getMessage()); JSONObject json = new JSONObject(map); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ }finally { if (out != null ){ out.close(); } } }); UsernamePasswordAuthenticationFilter authenticationFilter = new JsonAuthenticationFilter(); authenticationFilter.setAuthenticationSuccessHandler((request, response, authentication) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 200 ); map.put("msg" , "登陆成功" ); map.put("data" , authentication.getPrincipal().toString()); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ }finally { if (out != null ){ out.close(); } } }); authenticationFilter.setAuthenticationFailureHandler((request, response, exception) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); if (exception instanceof LockedException){ map.put("msg" , "账户被锁定" ); }else if (exception instanceof CredentialsExpiredException){ map.put("msg" , "密码过期" ); }else if (exception instanceof AccountExpiredException){ map.put("msg" , "账户过期" ); }else if (exception instanceof DisabledException){ map.put("msg" , "账户被禁用" ); }else if (exception instanceof BadCredentialsException){ map.put("msg" , "用户名或者密码输入错误" ); } map.put("data" , exception.getMessage()); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ }finally { if (out != null ){ out.close(); } } }); authenticationFilter.setAuthenticationManager(authenticationManagerBean()); http.addFilterAt(authenticationFilter, UsernamePasswordAuthenticationFilter.class ) ; } }
4.4 测试 现在已经配置完成处理器了,我们可以来分别对登陆成功和失败进行测试。
4.4.1 登陆失败
4.4.2 登陆成功
5. 总结 以上就是在 Spring Security 使用 JSON 格式登陆的全部内容。
AuthenticationEntryPoint
接口实现在未认证的情况下返回 JSON 信息。
继承 UsernamePasswordAuthenticationFilter
过滤器,并重写 attemptAuthentication()
方法,实现从请求 Body 中获取 JSON 格式的登陆信息。
AuthenticationSuccessHandler
和 AuthenticationFailureHandler
处理器实现在登陆成功或失败时返回 JSON 信息。
继承 WebSecurityConfigurerAdapter
对 Spring Security 进行配置。
5.1 一点小优化 5.1.1 封装重复的代码 在返回 JSON 格式信息时,我们都需要将 JSON 信息写入响应中:
response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ e.printStackTrace(); }finally { if (out != null ){ out.close(); } }
我们可以将它封装成一个方法,有利于代码的复用避免出现重复的代码:
ResponseUtil.java public class ResponseUtil { public static void send (HttpServletResponse response, Map data) { JSONObject json = new JSONObject(data); response.setCharacterEncoding("UTF-8" ); response.setContentType("application/json; charset=utf-8" ); PrintWriter out = null ; try { out = response.getWriter(); out.append(json.toString()); out.flush(); }catch (Exception e){ e.printStackTrace(); }finally { if (out != null ){ out.close(); } } } }
5.1.2 修改 Spring Security 配置文件 利用我们封装出来的方法,可以优化一下我们的配置文件:
SecurityConfig.java @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .csrf().disable(); UsernamePasswordAuthenticationFilter authenticationFilter = new JsonAuthenticationFilter(); authenticationFilter.setAuthenticationManager(authenticationManagerBean()); authenticationFilter.setAuthenticationSuccessHandler((request, response, authentication) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 200 ); map.put("msg" , "登陆成功" ); map.put("data" , authentication.getPrincipal().toString()); ResponseUtil.send(response, map); }); authenticationFilter.setAuthenticationFailureHandler((request, response, exception) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); if (exception instanceof LockedException){ map.put("msg" , "账户被锁定" ); }else if (exception instanceof CredentialsExpiredException){ map.put("msg" , "密码过期" ); }else if (exception instanceof AccountExpiredException){ map.put("msg" , "账户过期" ); }else if (exception instanceof DisabledException){ map.put("msg" , "账户被禁用" ); }else if (exception instanceof BadCredentialsException){ map.put("msg" , "用户名或者密码输入错误" ); } map.put("data" , exception.getMessage()); ResponseUtil.send(response, map); }); http.addFilterAt(authenticationFilter, UsernamePasswordAuthenticationFilter.class ) ; http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { Map<String, Object> map = new HashMap<String, Object>(3 ); map.put("code" , 401 ); map.put("message" , "尚未登陆" ); map.put("data" , authException.getMessage()); ResponseUtil.send(response, map); }); } }
本文配套的示例源码: https://github.com/lxiaocode/spring-security-examples