1. 程式人生 > >spring-security 個性化用戶認證流程——自定義登錄頁面(可配置)

spring-security 個性化用戶認證流程——自定義登錄頁面(可配置)

ron 進行 狀態 row 錯誤 this 力度 override all

1.定義自己的登錄頁面
我們需要根據自己的業務系統構建自己的登錄頁面以及登錄成功、失敗處理
在spring security提供給我的登錄頁面中,只有用戶名、密碼框,而自帶的登錄成功頁面是空白頁面(可以重定向之前請求的路徑中),而登錄失敗時也只是提示用戶被鎖定、過期等信息。

在實際的開發中,則需要更精細力度的登錄控制,記錄錯誤的日誌(錯誤的次數等)

2.自定義登錄頁面

  • 配置登錄頁面的路徑
    BrowserSecurityConfig類中配置登錄頁面的路徑
        http.formLogin()  
        .loginPage("/sign.html")   //位於resources/resources/sign.html
        .and()
        .authorizeRequests()
        .anyRequest().authenticated();

    頁面內容如下:

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>sign.html</title>
    </head>
    <body>
    <h2>標準登錄頁面</h2>
    <h3>表單登錄</h3>
    <form action="/authentication/form" method="post">
        <table>
            <tr>
                <td>用戶名:</td> 
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>密碼:</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td colspan="2"><button type="submit">登錄</button></td>
            </tr>
        </table>
    </form>
    </body>
    </html>

啟動之後,訪問我們的路徑:http://localhost:8080/sign.html
技術分享圖片
這個時候瀏覽器會報錯:重定向的次數過多,這是為什麽呢?
因為在配置中,我們只是配置了loginPage("/sign.html"),但是又沒有給該請求做授權,又因為沒有做授權,又被跳轉給sign.html頁面,所以會是:重定向的次數過多。
此時:需要再配置授權路徑,改造之後

        http.formLogin()  
        .loginPage("/sign.html")  
        .and()
        .authorizeRequests()
        .antMatchers("/sign.html").permitAll()  //當訪問sign.html這個頁面的時候不需要進行身份認證
        .anyRequest().authenticated();

在sign.html中,自己配置了一個post路徑,在spring security原理中,表單登錄實際上是由UsernamePasswordAuthenticationFilter這個過濾器來處理的,在這個過濾器中技術分享圖片
處理的是/login 請求,為了讓UsernamePasswordAuthenticationFilter這個過濾器知道處理我們自定義的登錄路徑/authentication/form,還需要再配置登錄的處理請求

        http.formLogin()  
        .loginPage("/sign.html")  
        .loginProcessingUrl("/authentication/form") //登錄請求
        .and()
        .authorizeRequests()
        .antMatchers("/sign.html").permitAll() 
        .anyRequest().authenticated();

啟動之後,繼續訪問我們的路徑:http://localhost:8080/sign.html
技術分享圖片
輸入賬戶名和密碼,登錄之後會報錯,403

技術分享圖片

在默認情況下,spring security提供了跨站請求偽造的防護,用CSRF Token來完成的,在***和防護的時候再細講,目前先把跨站請求偽造的功能先disable掉。

        http.formLogin()  
        .loginPage("/sign.html")  
        .loginProcessingUrl("/authentication/form") //登錄請求
        .and()
        .authorizeRequests()
        .antMatchers("/sign.html").permitAll() 
        .anyRequest().authenticated()
        .and()
        .csrf().disable();

啟動之後,繼續訪問我們的路徑:http://localhost:8080/user/1 ,系統會幫我們重定向到sign.html頁面
此時我們輸入正確的用戶名和密碼就可以訪問我們的請求了
技術分享圖片

3.優化rest請求和html請求
完成了基本功能之後還需要繼續優化我們的代碼結構,是要面向可重用的一種。
目前有2個問題:

  • 發送的請求,例如:localhost:8080/user/1 需要身份認證的話返回HTML是不合理的,rest服務應該返回json
    想要做到的效果,如果是html請求則返回登錄頁上,如果不是則返回json數據 帶未授權(401狀態碼)
  • 我們寫了一個標準的登錄頁面,但是我們的目標是提供可重用的安全模塊,這個時候就需要提供可配置項
    (因為會有多個項目使用該模塊,但是多個項目它有不同的登錄模塊)
    流程如下:

技術分享圖片

當我們接收到html請求或者數據請求的時候,先判斷是否需要身份認證(spring security來做的)如果是否的話就直接返回了,如果是的話則需要跳轉到我們自定義的Controller上面去(目前我們的做法是跳轉到了sign.html頁面上)在該方法內判斷是html請求還是數據請求。

Controller

@RestController
public class BrowserSecurityController {
    private Logger logger = LoggerFactory.getLogger(BrowserSecurityConfig.class);

    //拿到引發跳轉的請求(HttpSessionRequestCache把當前的請求緩存到Session中)
    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    private SecurityProperties securityProperties;

    //當需要身份認證時,跳轉到這裏
    @RequestMapping("/authentication/require")
    @ResponseStatus(code=HttpStatus.UNAUTHORIZED) //不是html請求時,返回401狀態碼
    public SimpleResponse requireAuthentication(HttpServletRequest request,HttpServletResponse response) throws IOException {
        //之前緩存的請求(可以拿到引發跳轉的請求)
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            logger.info("引發跳轉的請求是:"+targetUrl);
            //是否以.html結尾,如果是則跳轉到登錄頁面
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                //這個url,我們需要做成可配置的url(因為我們不可能每次都跳轉到我們自己的寫的固定登錄頁面,需要根據每個項目的不同)
                //這個時候就需要用到**Properties 配置文件類來做靈活性配置
                redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());
            }
            //如果不是html請求,則返回401狀態碼以及錯誤信息
        }
        return new SimpleResponse("訪問的服務需要身份認證,請引導用戶到登錄頁");
    }
}

SecurityProperties自定義的登錄頁配置屬性類,為了可配置化

#另外的項目的登錄請求的頁面
core.security.browser.loginPage = /demo-sign.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>demo-sign</title>
</head>
<body>
    <h2>demo-sign自定義登錄頁</h2>
</body>
</html>

而我們不想單純的要一個簡單的配置是,而是可管理的配置類,因為後面會有其他的配置,例如驗證碼的配置,OAuth的配置,等 這個時候需要同一個的配置類入口(SecurityProperties

技術分享圖片

//讀取配置文件內的信息
@ConfigurationProperties(prefix="core.security")
public class SecurityProperties {
    private BrowserProperties browser = new BrowserProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }
    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

public class BrowserProperties {
    //標準的登錄頁面,如果其他項目沒有配置則使用默認的登錄配置
    private String loginPage = "/sign.html";

    public String getLoginPage() {
        return loginPage;
    }
    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}

//為了使core.security生效則需要一個@Configuration類
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}

//最後要在權限配置類**BrowserSecurityConfig**中 配置放行的url
  private final static String loginPage = "/authentication/require";
    @Autowired
    private SecurityProperties securityProperties;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()  
        .loginPage(loginPage)
        .loginProcessingUrl("/authentication/form")
        .and()
        .authorizeRequests()
        .antMatchers(loginPage).permitAll()
        //自定義的登錄頁面權限放開
        .antMatchers(securityProperties.getBrowser().getLoginPage()).permitAll()
        .anyRequest().authenticated()
        .and()
        .csrf().disable();
    }

第一種情況:訪問請求:http://localhost:8080/user/1
技術分享圖片

第二種情況:訪問請求:http://localhost:8080/index.html
技術分享圖片

第三種情況:關閉配置,繼續訪問請求:http://localhost:8080/index.html

#core.security.browser.loginPage = /demo-sign.html

技術分享圖片

這個功能就是我們目前想要的,可以針對不同的請求對於沒有權限時的攔截以及調整判斷。

spring-security 個性化用戶認證流程——自定義登錄頁面(可配置)