SpringSecurity表单配置之登录成功及页面跳转原理解析
作者:陈橙橙丶
登录表单配置
在上一篇文章中,我们介绍了,基本认证以及默认用户名和密码以及页面SpringSecurity是怎样帮我们生成的,这里我们就来看一下登录表单的详细配置。
项目准备
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
编写登录页面(login.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>登录</title> <link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="external nofollow" rel="stylesheet" id="bootstrap-css" /> <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"> </script> <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"> </script> </head> <style> </style> <body> <div id="login"> <div class="container"> <div id="login-row" class="row justify-content-center align-item-center"> <div id="login-colum" class="col-md-6"> <div id="login-box" class="col-md-12"> <form id="login-form" class="form" action="/doLogin" method="post"> <h3 class="text-center text-info">登录</h3> <div class="form-group"> <label for="username" class="text-info">用户名:</label><br> <input type="text" name="uname" id="username" class="form-control"> </div> <div class="form-group"> <label for="password" class="text-info">密码:</label><br> <input type="text" name="passwd" id="password" class="form-control"> </div> <div class="form-group"> <input type="submit" name="submit" class="btn btn-info btn-md" value="登录"> </div> </form> </div> </div> </div> </div> </div> </body> </html>
提供两个测试接口
@GetMapping("/hello") public String hello(){ return "hello springboot security"; } @GetMapping("/index") public String index(){ return "login success"; }
自定义用户名密码
spring.security.user.name=test spring.security.user.password=123456 spring.security.user.roles=admin,user
提供SpringSecurity配置类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and().formLogin() .loginPage("/login.html") .loginProcessingUrl("/doLogin") .defaultSuccessUrl("/index") .failureUrl("/login.html") .usernameParameter("uname") .passwordParameter("passwd") .permitAll() .and() .csrf().disable(); } }
在SpringSecurity中,如果我们需要自定义配置,基本上都是继承WebSecurityConfigurerAdapter来实现的,当然WebSecurityConfigurerAdapter本身的配置还是比较复杂,同时也是比较丰富的,这里不细说,后续会详细介绍。
- 首先configure方法中是一个链式配置,当然也可以不用链式配置,每个属性配置完毕后再从http重新写起
- authorizeRequests()方法表示开启权限配置
- anyRequest().authenticated()表示所有的请求都要认证之后才能访问
- and()方法,该方法会返回一个HttpSecurityBuilder对象的一个子类(实际上就算HttpSecurity),所以and()方法相当于又回到HttpSecurity实例,重新开启新一轮的配置。
- formLogin()表示开启表单登录配置:
- loginPage:用来配置登录页面地址
- loginProcessingUrl:用来配置接口登录接口地址
- defaultSuccessUrl:表示登录成功后跳转地址
- failureUrl:表示登录失败后跳转的地址
- usernameParameter:表示登录用户名的参数名称
- passwordParameter:表示密码的参数名称
- permitAll:可以理解成两个and()之间的所有方法地址不需要认证拦截(白名单)。
需要注意的是loginProcessingUrl、usernameParameterpasswordParameter需要和login.html中登录表单配置一致
最后csrf().disable()表示禁用CSRF防御功能,SpringSecurity自带了CSRF防御机制,但是我们这里为了测试方便,先将CSRF防御机制关闭。
启动项目访问http://localhost:8080/index
输入配置的用户名/密码:test/123456,然后就能访问到/index接口了
配置细节
上面我们说到defaultSuccessUrl和failureUrl表示用户登录失败后的跳转地址。关于登录成功和登录失败,除了这两个方法之外,还有另外两个方法可以配置
登录成功
- successForwardUrl
- defaultSuccessUrl
defaultSuccessUrl前者表示当用户登录成功之后,会自动重定向到登录之前的地址上,如果用户本身就是直接访问的登录页面,则登录成功之后就会重定向到defaultSuccessUrl指定的页面中。例如用户在未认证的情况下,访问了/hello页面,此时会重定向到登录页面,登录成功后,就会自动重定向到/hello页面;而用户如果一开始就是访问到登录页面,则登录成功后就会自动重定向到defaultSuccessUrl指定的页面
successForwardUrl则不会考虑用户之前所访问地址,只要用户登录成功,就会通过服务器端跳转到successForwardUrl所指定的页面。
defaultSuccessUrl有一个重载方法,如果重载方法的第二个参数传入true,则它和successForwardUrl效果类似,即不考虑用户之前的访问地址,只要登录成功就重定向到指定页面。不同之处在于defaultSuccessUrl是通过重定向实现的跳转(客户端跳转),而successForwardUrl是通过服务器端跳转实现的。
无论是successForwardUrl还是defaultSuccessUrl,最终所有配置的都是AuthenticationSuccessHandler接口的实例。
SpringSecurity中专门提供了AuthenticationSuccessHandler接口用来处理登录成功事项
public interface AuthenticationSuccessHandler { default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { this.onAuthenticationSuccess(request, response, authentication); chain.doFilter(request, response); } void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException; }
由上述代码可以看到AuthenticationSuccessHandler接口中一共定义了两个方法,一个是default方法,此方法是SpringSecurity5.2开始加入的,在处理特定的认证请求Authentication Filter中会用到;另外一个非default方法,则用来处理登录成功的具体事项,其中authentication参数保存了登录成功的用户信息。
AuthenticationSuccessHandler接口一共有三个实现类:
(1)、SimpleUrlAuthenticationSuccessHandler继承自AbstractAuthenticationTargetUrlRequestHandler,通过AbstractAuthenticationTargetUrlRequestHandler中的handle方法实现请求重定向
(2)、SavedRequestAwareAuthenticationSuccessHandler在SimpleUrlAuthenticationSuccessHandler的基础之上增加了请求缓存的功能,可以记录之前请求的地址,进而在登录成功之后重定向到一开始访问的地址。
(3)、ForwardAuthenticationSuccessHandler的实现就比较容易,就是一个服务端跳转。
我们来重点看一下SavedRequestAwareAuthenticationSuccessHandler和ForwardAuthenticationSuccessHandler
public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { protected final Log logger = LogFactory.getLog(this.getClass()); private RequestCache requestCache = new HttpSessionRequestCache(); @Override 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); return; } String targetUrlParameter = getTargetUrlParameter(); if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) { this.requestCache.removeRequest(request, response); super.onAuthenticationSuccess(request, response, authentication); return; } clearAuthenticationAttributes(request); // Use the DefaultSavedRequest URL String targetUrl = savedRequest.getRedirectUrl(); getRedirectStrategy().sendRedirect(request, response, targetUrl); } public void setRequestCache(RequestCache requestCache) { this.requestCache = requestCache; } }
这里的核心方法就是 onAuthenticationSuccess
(1)、首先从requestCache中获取缓存下来的请求,如果没有获取到缓存请求,就说明用户在登录页面之前并没有访问其他页面,此时调用父类的方法来处理,最终会重定向到defaultSuccessUrl指定的地址
(2)、如果缓存请求不为空,则会获取一个targetUrlParameter,这个是用户显示指定的,希望登录成功重定向的地址,例如用户发送的登录请求是http://localhost:8080/doLogin?target=/hello,这就表示当用户登录成功之后。希望自动重定向到/hello这个接口,getTargetUrlParameter就是要获取重定向地址参数的key,也就是上面的target,拿到target之后,就可以获取到重定向地址了。
(3)、如果targetUrlParameter存在,或者用户设置了alwaysUseDefaultTargetUrl为true,这个时候缓存下来的请求就没有意义了。此时会直接调用父类的onAuthenticationSuccess方法完成重定向。targetUrlParameter存在,则直接重定向到targetUrlParameter指定的地址。alwaysUseDefaultTargetUrl为true,则直接重定向到defaultSuccessUrl指定的地址。如果alwaysUseDefaultTargetUrl和targetUrlParameter同时满足,则重定向到defaultSuccessUrl指定的地址。
(4)、如果前面的条件都不满足,那么最终会从缓存请求saveRequest中获取重定向地址,然后进行重定向操作。
这就是SavedRequestAwareAuthenticationSuccessHandler的实现逻辑,开发者也可以配置自己的SavedRequestAwareAuthenticationSuccessHandler,代码如下:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and().formLogin() .loginPage("/login.html") .loginProcessingUrl("/doLogin") .successHandler(successHandler()) // .defaultSuccessUrl("/index") .failureUrl("/login.html") .usernameParameter("uname") .passwordParameter("passwd") .permitAll() .and() .csrf().disable(); } SavedRequestAwareAuthenticationSuccessHandler successHandler(){ SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler(); handler.setDefaultTargetUrl("/hello"); handler.setTargetUrlParameter("target"); return handler; } }
然后在上篇文章中的表单中,修改一下action的参数
action="/doLogin?target=http://www.baidu.com"
这样当我们登录成功之后就可以跳转到百度了,如果不指定action的target就会跳转到我们上面默认的/hello接口
当我们通过successForwardUrl来设置登录成功后重定向地址时,实际上对应的实现类就是ForwardAuthenticationSuccessHandler,它的源码特别简单,就是一个服务端转发,如下:
public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final String forwardUrl; /** * @param forwardUrl */ public ForwardAuthenticationSuccessHandler(String forwardUrl) { Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> "'" + forwardUrl + "' is not a valid forward URL"); this.forwardUrl = forwardUrl; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { request.getRequestDispatcher(this.forwardUrl).forward(request, response); } }
上述代码可以看到,主要共嗯那个就是调用getRequestDispatcher方法进行服务端转发,AuthenticationSuccessHandler默认的三个实现类,无论是哪一个,都是用来处理页面跳转的。有时候页面跳转并不能满足我们的需求,特别是现在的前后的分离开发中,用户登录成功之后,就不需要跳转页面了,只需要给前端返回一个JSON数据即可,告诉前端登录成功还是失败,前端收到消息后自行处理,像这样的需求,我们可以通过自定义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"); HashMap<String,Object> resp = new HashMap<>(); resp.put("status",200); resp.put("msg","登录成功"); ObjectMapper om = new ObjectMapper(); final String writeValueAsString = om.writeValueAsString(resp); response.getWriter().write(writeValueAsString); } }
然后在WebSecurity中配置
http.successHandler(new MyAuthenticationSuccessHandler());
配置完成之后,此时登录成功,就不会进行页面添砖了,而是返回一段JSON字符串。
关于登录失败的,会在下一篇中详细讲解。
到此这篇关于SpringSecurity表单配置之登录成功及页面跳转原理的文章就介绍到这了,更多相关SpringSecurity登录成功跳转内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- springsecurity实现用户登录认证快速使用示例代码(前后端分离项目)
- springsecurity记住我登录时访问无权限接口跳转登录界面的处理方案
- SpringSecurity自定义资源拦截规则及登录界面跳转问题
- 解决springSecurity 使用默认登陆界面登录后无法跳转问题
- SpringSecurity 默认登录认证的实现原理解析
- SpringSecurity默认登录页的使用示例教程
- SpringSecurityOAuth2实现微信授权登录
- SpringSecurity实现前后端分离登录token认证详解
- springSecurity实现简单的登录功能
- SpringSecurity自定义登录成功处理
- SpringSecurity多表多端账户登录的实现