写点什么

SpringSecurity 表单登录

作者:周杰伦本人
  • 2022 年 5 月 02 日
  • 本文字数:6606 字

    阅读完需:约 22 分钟

SpringSecurity 表单登录

@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .anyRequest().authenticated()                .and()                .formLogin()                .loginPage("/mylogin.html")                .loginProcessingUrl("/doLogin")                .defaultSuccessUrl("/index.html")                .failureHandler(new MyAuthenticationFailureHandler())                .usernameParameter("uname")                .passwordParameter("passwd")                .permitAll()                .and()                .logout()                .logoutRequestMatcher(new OrRequestMatcher(                        new AntPathRequestMatcher("/logout1", "GET"),                        new AntPathRequestMatcher("/logout2", "POST")))                .invalidateHttpSession(true)                .clearAuthentication(true)                .defaultLogoutSuccessHandlerFor((req,resp,auth)->{                    resp.setContentType("application/json;charset=utf-8");                    Map<String, Object> result = new HashMap<>();                    result.put("status", 200);                    result.put("msg", "使用 logout1 注销成功!");                    ObjectMapper om = new ObjectMapper();                    String s = om.writeValueAsString(result);                    resp.getWriter().write(s);                },new AntPathRequestMatcher("/logout1","GET"))                .defaultLogoutSuccessHandlerFor((req,resp,auth)->{                    resp.setContentType("application/json;charset=utf-8");                    Map<String, Object> result = new HashMap<>();                    result.put("status", 200);                    result.put("msg", "使用 logout2 注销成功!");                    ObjectMapper om = new ObjectMapper();                    String s = om.writeValueAsString(result);                    resp.getWriter().write(s);                },new AntPathRequestMatcher("/logout2","POST"))                .and()                .csrf().disable();    }}
复制代码


springSecurity 需要自定义配置值 基本都是继承 WebSecurityConfigurerAdapter


  1. authorizeRequests 表示开启权限配置,.anyRequest().authenticated()表示所有的请求都认证之后才能访问

  2. and()方法返回 HttpSecurity 的实例

  3. formLogin()表示开启表单登录配置

  4. loginPage 配置登录页面地址

  5. loginProcessingUrl 配置登录接口地址

  6. defaultSuccessUrl 登录成功后的跳转地址

  7. failureUrl 表示登录失败后的跳转地址

  8. usernameParameter 表示登录用户名的参数名

  9. passwordParameter 登录密码的参数名

  10. permitAll()表示和登录相关的页面和接口不做拦截 直接通过

  11. 其中 loginProcessingUrl usernameParameter passwordParameter 要和登录表单的配置一致。


    .loginPage("/mylogin.html")  //                    .loginProcessingUrl("/doLogin")                   .defaultSuccessUrl("/index.html")                   .failureHandler(new MyAuthenticationFailureHandler())                   .usernameParameter("uname")                   .passwordParameter("passwd")
复制代码


  1. csrf().disable()表示禁用 CSRF 防御功能

登录成功

用户登录成功后除了 defaultSuccessUrl 方法可以实现登录成功的跳转之外,successForwardUrl 也可以实现登录成功后的跳转,


defaultSuccessUrl 和 successForwardUrl 区别:


  1. defaultSuccessUrl 表示当用户登录成功后,会自动重定向到登录之前的地址,如果用户本身就是访问的登录页面,登录成功后就会重定向到 defaultSuccessUrl 指定页面

  2. successForwardUrl 不会考虑用户之前的访问地址,登录成功后通过服务器端跳转到 successForwardUrl 所指定的页面。


defaultSuccessUrl 是客户端跳转重定向,successForwardUrl 是通过服务端实现的跳转。


他们的接口都 AuthenticationSuccessHandler


AuthenticationSuccessHandler 有三个实现类


  1. SimpleUrlAuthenticationSuccessHandler 继承 AbstractAuthenticationTargetUrlRequestHandler 通过他的 handle 方法处理请求

  2. SavedRequestAwareAuthenticationSuccessHandler 在 SimpleUrlAuthenticationSuccessHandler 基础上增加了请求加缓存的功能,可以记录之前请求的地址,今儿在登录成功后重定向到开始访问的地址。

  3. ForwardAuthenticationSuccessHandler 是服务端的跳转

SavedRequestAwareAuthenticationSuccessHandler

defaultSuccessUrl 对应的是 SavedRequestAwareAuthenticationSuccessHandler


public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {    SavedRequest savedRequest = this.requestCache.getRequest(request, response);    if (savedRequest == null) {        super.onAuthenticationSuccess(request, response, authentication);    } else {        String targetUrlParameter = this.getTargetUrlParameter();        if (!this.isAlwaysUseDefaultTargetUrl() && (targetUrlParameter == null || !StringUtils.hasText(request.getParameter(targetUrlParameter)))) {            this.clearAuthenticationAttributes(request);            String targetUrl = savedRequest.getRedirectUrl();            this.logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);            this.getRedirectStrategy().sendRedirect(request, response, targetUrl);        } else {            this.requestCache.removeRequest(request, response);            super.onAuthenticationSuccess(request, response, authentication);        }    }}
复制代码


  1. 首先从 requestCache 中获取缓存下来的请求 如果没有获取到缓存请求,就说明用户在访问登录页面之前并没有访问其他页面,此时直接调用父类的 onAuthenticationSuccess 方法来处理,重定向到 defaultSuccessUrl 指定的地址。

  2. 获取 targetUrlParameter 拿到 target 参数后重定向地址。

  3. 如果 targetUrlParameter 不存在或者 alwaysUseDefaultTargetUrl 为 true 缓存下来的请求没有意义,直接调用父类的 onAuthenticationSuccess 方法完成重定向 。targetUrlParameter 存在 则重定向到 targetUrlParameter 中,alwaysUseDefaultTargetUrl 为 true 走默认

ForwardAuthenticationSuccessHandler

successForwardUrl 对应 ForwardAuthenticationSuccessHandler


public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {    private final String forwardUrl;
public ForwardAuthenticationSuccessHandler(String forwardUrl) { Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> { return "'" + forwardUrl + "' is not a valid forward URL"; }); this.forwardUrl = forwardUrl; }
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { request.getRequestDispatcher(this.forwardUrl).forward(request, response); }}
复制代码


主要调用 getRequestDispatcher 进行服务端请求转发

自定义 AuthenticationSuccessHandler 实现类

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler{    @Override    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {        response.setContentType("application/json;charset=utf-8");        Map<String, Object> resp = new HashMap<>();        resp.put("status", 200);        resp.put("msg", "登录成功!");        ObjectMapper om = new ObjectMapper();        String s = om.writeValueAsString(resp);        response.getWriter().write(s);    }}
复制代码


.successHandler(new MyAuthenticationSuccessHandler())
复制代码


通过 HttpServletResponse 对象返回登录成功的 json 给前端

登录失败

failureUrl 表示登录失败后的重定向到配置的页面,重定向是客户端的跳转,不方便携带请求失败的异常信息。


failureForwardUrl 是服务端的跳转,可以携带登录异常信息。登录失败,自动跳转回登录页面,将错误信息展示出来。


他们的配置的是 AuthenticationFailureHandler 接口的实现类

SimpleUrlAuthenticationFailureHandler

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//
package org.springframework.security.web.authentication;
import java.io.IOException;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.springframework.http.HttpStatus;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.DefaultRedirectStrategy;import org.springframework.security.web.RedirectStrategy;import org.springframework.security.web.util.UrlUtils;import org.springframework.util.Assert;
public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler { protected final Log logger = LogFactory.getLog(this.getClass()); private String defaultFailureUrl; private boolean forwardToDestination = false; private boolean allowSessionCreation = true; private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public SimpleUrlAuthenticationFailureHandler() { }
public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) { this.setDefaultFailureUrl(defaultFailureUrl); }
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { if (this.defaultFailureUrl == null) { this.logger.debug("No failure URL set, sending 401 Unauthorized error"); response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); } else { this.saveException(request, exception); if (this.forwardToDestination) { this.logger.debug("Forwarding to " + this.defaultFailureUrl); request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response); } else { this.logger.debug("Redirecting to " + this.defaultFailureUrl); this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl); } }
}
protected final void saveException(HttpServletRequest request, AuthenticationException exception) { if (this.forwardToDestination) { request.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception); } else { HttpSession session = request.getSession(false); if (session != null || this.allowSessionCreation) { request.getSession().setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception); } }
}}
复制代码


当用户构造 SimpleUrlAuthenticationFailureHandler 对象时候传入 defaultFailureUrl,也就是登录失败时要跳转的 url。在 onAuthenticationFailure 方法中


  • 如果 defaultFailureUrl 为 null,直接通过 response 返回异常信息,否则调用 saveException

  • saveException 如果 forwardToDestination 为 true,表示通过服务器端跳转回到登录页面,此时就把异常信息放到 request 中。

  • 回到 onAuthenticationFailure 方法,如果 forwardToDestination 为 true,就通过服务器端跳回到登录页面,否则重定向到登录页面。

自定义 AuthenticationFailureHandler 实现类

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {    @Override    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {        response.setContentType("application/json;charset=utf-8");        Map<String, Object> resp = new HashMap<>();        resp.put("status", 500);        resp.put("msg", "登录失败!" + exception.getMessage());        ObjectMapper om = new ObjectMapper();        String s = om.writeValueAsString(resp);        response.getWriter().write(s);    }}
复制代码


通过 HttpServletResponse 对象返回登录失败的 json 给前端

注销登录

.logout().logoutUrl("").logoutRequestMatcher(new OrRequestMatcher(        new AntPathRequestMatcher("/logout1", "GET"),        new AntPathRequestMatcher("/logout2", "POST"))).invalidateHttpSession(true).clearAuthentication(true).logoutSuccessUrl("")
复制代码


  • logout() 表示开启注销登录配置。

  • logoutUrl 指定注销登录请求地址,默认 GET 请求,路径 logout

  • invalidateHttpSession 表示是否使 session 失效,默认为 true

  • clearAuthentication 表示是否清除认证信息,默认为 true

  • logoutSuccessUrl 表示注销登录后的跳转地址。

  • logoutRequestMatcher 匹配多个注销登录

自定义注销成功的返回内容

.logout().logoutRequestMatcher(new OrRequestMatcher(        new AntPathRequestMatcher("/logout1", "GET"),        new AntPathRequestMatcher("/logout2", "POST"))).invalidateHttpSession(true).clearAuthentication(true).defaultLogoutSuccessHandlerFor((req,resp,auth)->{    resp.setContentType("application/json;charset=utf-8");    Map<String, Object> result = new HashMap<>();    result.put("status", 200);    result.put("msg", "使用 logout1 注销成功!");    ObjectMapper om = new ObjectMapper();    String s = om.writeValueAsString(result);    resp.getWriter().write(s);},new AntPathRequestMatcher("/logout1","GET")).defaultLogoutSuccessHandlerFor((req,resp,auth)->{    resp.setContentType("application/json;charset=utf-8");    Map<String, Object> result = new HashMap<>();    result.put("status", 200);    result.put("msg", "使用 logout2 注销成功!");    ObjectMapper om = new ObjectMapper();    String s = om.writeValueAsString(result);    resp.getWriter().write(s);},new AntPathRequestMatcher("/logout2","POST")).and().csrf().disable();
复制代码


defaultLogoutSuccessHandlerFor()两个参数 第一个是注销成功的回调,第二个是具体的注销请求。


总结


这篇文章主要讲了 Spring Security 登录成功和登录失败以及注销登录的逻辑,希望对你有帮助。

发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2020.02.29 加入

还未添加个人简介

评论

发布
暂无评论
SpringSecurity 表单登录_5月月更_周杰伦本人_InfoQ写作社区