一、請求參數名設置
之前的表單信息有一些要求:
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"; // 重定向要寫/標識 區分模版解析 } }