一、请求参数名设置
之前的表单信息有一些要求:
1、action属性发送的地址是Security设置的URL
2、发送的请求方式是POST
3、请求的账户信息,也就是表单发送的参数,必须对应的是username & password
原因是因为这个过滤器类:
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
和下面的方法:
如果要改变默认的请求参数,可以设置:
package cn.zeal4j.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 httpSecurity.authorizeRequests(). antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 anyRequest().authenticated(); // 其他请求均需要被授权访问 // CSRF攻击拦截关闭 httpSecurity.csrf().disable(); } }
二、前后端分离登陆成功页的跳转:
问题的产生:
使用默认successForwardUrl并不能用来跳转到外部地址,也就是跨域访问
例如这样设置成功登陆页进行跳转:
successForwardUrl("http://www.baidu.com")
方法分析:
successForwardUrl方法的实现:
public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) { this.successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl)); return this; }
内部是调用了一个成功处理器方法,并且注入了一个跳转授权成功处理器对象,构造参数又是这个要登录的页面URL
跳转授权成功处理器类:
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 org.springframework.security.core.Authentication; import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; 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); } }
可以看到下面的这个方法是将URL进行转发处理的,所以像跨域处理的是没有办法解决的
这个处理器类是实现了一个接口:
org.springframework.security.web.authentication.AuthenticationSuccessHandler
接口的方法:
package org.springframework.security.web.authentication; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; 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 var1, HttpServletResponse var2, Authentication var3) throws IOException, ServletException; }
也就是说,我们可以通过自己写一个类实现这个接口,单独处理前后端分离的重定向登陆URL
package cn.zeal4j.handler; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 23:48 * 前后端分离url登陆处理 Front and rear separation (FARS) */ public class FarsAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; public FarsAuthenticationSuccessHandler(String url) { this.url = url; } @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.sendRedirect(url); } // @Override // public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { // // } }
改写登陆成功页面URL的方法:
package cn.zeal4j.configuration; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 httpSecurity.authorizeRequests(). antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 anyRequest().authenticated(); // 其他请求均需要被授权访问 // CSRF攻击拦截关闭 httpSecurity.csrf().disable(); } }
参数Authentication可以获取登陆账户的信息:
package cn.zeal4j.handler; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collection; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 23:48 * 前后端分离url登陆处理 Front and rear separation (FARS) */ public class FarsAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private String url; public FarsAuthenticationSuccessHandler(String url) { this.url = url; } @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { Object principal = authentication.getPrincipal(); User user = (User) principal; Collection<GrantedAuthority> authorities = user.getAuthorities(); System.out.println(user.getUsername()); System.out.println(user.getPassword()); for (GrantedAuthority authority : authorities) { System.out.println(authority.getAuthority()); } httpServletResponse.sendRedirect(url); } // @Override // public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { // // } }
打印结果:
admin null admin normal
密码输出NULL是因为Security的保护限制?
三、前后端分离登陆失败页的跳转:
有成功页面的跳转,对应的就有失败页面的跳转
public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) { this.failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl)); return this; }
也是同样的方法和类似的对象和相同的注入方式
// // 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 org.springframework.security.core.AuthenticationException; import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; public class ForwardAuthenticationFailureHandler implements AuthenticationFailureHandler { private final String forwardUrl; public ForwardAuthenticationFailureHandler(String forwardUrl) { Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> { return "'" + forwardUrl + "' is not a valid forward URL"; }); this.forwardUrl = forwardUrl; } public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { request.setAttribute("SPRING_SECURITY_LAST_EXCEPTION", exception); request.getRequestDispatcher(this.forwardUrl).forward(request, response); } }
所以要实现跨域跳转,一样是实现上面的接口,除了跳转,该方法还要求携带异常对象
自定义实现类
package cn.zeal4j.handler; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 28 3:44 */ public class FarsAuthenticationFailureHandler implements AuthenticationFailureHandler { private String url; public FarsAuthenticationFailureHandler(String url) { this.url = url; } @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { // 因为跨域,无法携带异常对象 httpServletResponse.sendRedirect(url); } }
更改Security跳转配置:
package cn.zeal4j.configuration; import cn.zeal4j.handler.FarsAuthenticationFailureHandler; import cn.zeal4j.handler.FarsAuthenticationSuccessHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 21:55 */ @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder getPasswordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.formLogin(). // 设置登陆行为方式为表单登陆 // 登陆请求参数设置 usernameParameter("username"). passwordParameter("password"). loginPage("/login.html"). // 设置登陆页面URL路径 loginProcessingUrl("/login.action"). // 设置表单提交URL路径 // successForwardUrl("/main.page"). // 设置认证成功跳转URL路径 POST请求 successHandler(new FarsAuthenticationSuccessHandler("https://www.acfun.cn/")). // 使用自定义的重定向登陆 // failureForwardUrl("/error.page"); // 设置认证失败跳转URL路径 POST请求 failureHandler(new FarsAuthenticationFailureHandler("/error.html")); // 跨域处理,不需要跳转了 httpSecurity.authorizeRequests(). antMatchers("/login.html").permitAll(). // 登陆页面允许任意访问 antMatchers("/error.html").permitAll(). // 失败跳转后重定向的页面也需要被允许访问 anyRequest().authenticated(); // 其他请求均需要被授权访问 // CSRF攻击拦截关闭 httpSecurity.csrf().disable(); } }
登陆控制器的跳转方法就不需要了
package cn.zeal4j.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; /** * @author Administrator * @file IntelliJ IDEA Spring-Security-Tutorial * @create 2020 09 27 22:35 */ @Controller public class LoginController { @RequestMapping("main.page") public String toMainPage() { return "main"; // 模版内的页面不允许重定向,忘了忘了 } @PostMapping("error.page") // 控制器不支持POST请求跳转解析, 需要控制器跳转 Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported] public String redirectToErrorPage() { return "redirect:/error.html"; // 重定向要写/标识 区分模版解析 } }