簡體   English   中英

如何在Grails和現有的oauth2提供程序中使用Spring Security實現基於表單的登錄

[英]How to implement form-based login with spring security in grails and existing oauth2 provider

大家好,我有一個新手grails /春季安全問題。 我們使用spring-security-oauth2-provider在grails 3項目中設置了oauth2,似乎可以保護REST API。

但是后來我們開始使用GSP在同一項目中添加Web前端,並走到了十字路口。 通常,oauth2通過對端點進行身份驗證並接收令牌來工作,在后續請求中,它可以在HTTP標頭中使用它來繼續訪問受保護的資源。 但是我的Web前端有一個登錄頁面。 因此,最初我們考慮將Web前端視為客戶端之一(我們的iOS應用程序有1個客戶端,Android應用程序有1個客戶端,所以為什么我們的Web應用程序也沒有1個客戶端)。 但是從我們的控制器代碼(用於登錄)向我們的oauth2提供者端點發出HTTP請求似乎很奇怪,因為它在同一個項目中。 以及我的Web前端需要做出的大多數后續請求,我們希望直接訪問基礎服務和域對象,因此添加額外的躍點似乎適得其反。

因此,我們選擇的是,當我使用我的login.gsp登錄時,在控制器代碼中,我繞過了oauth2登錄並僅通過使用authenticationManager.authenticate()和我根據用戶名構造的UsernamePasswordAuthenticationToken進行直接的彈簧安全性認證和密碼字段傳遞到我的表單中,然后在響應上調用SecurityContextHolder.getContext()。setAuthentication()。 這樣的一半解決了我的問題,因為從這篇文章中得知,這樣做只會在當前線程上設置SecurityContextHolder,並且由於SecurityContextHolder在過濾器鏈的末尾被清除,因此后續請求將不被認證。 因此,我要做的是,如果此身份驗證通過(即未引發任何異常),那么我將用戶對象放入HTTP會話以及所有后續請求中,嘗試將其作為“說明”身份驗證的方式來檢索它。 但這似乎既骯臟又骯臟。 並導致我的用戶對象未附加到數據庫會話(導致延遲初始化異常)。

我沒有找到類似的帖子其他類似的建議, 這使整個SecurityContext中的HTTP會話,但似乎並沒有說明如何使用SecurityContext的在后續請求。

我想我的最終問題是,我們會以錯誤的方式走下去嗎? 有沒有更好,更清潔的方法來完成我想做的事情? 我想我們不能成為第一個嘗試這樣做的人。

我在回答自己的問題感到有些難過。 但是我想看看我的解決方案是否是解決此問題的正確方法。

因此,我們最終要做的是使用過濾器。 我們知道Interceptor應該是Grails 3的使用方式,但是在到達那里之前,我們只想擁有一個帶有Filters的工作版本。

在我們的AuthController.signIn方法中,我們經歷了UsernamePasswordAuthenticationToken身份驗證過程,並且實際上將SecurityContext推送到了HTTP會話中。 順便說一句,有趣的是,我后來發現這實際上已經為我完成了(使用“ SPRING_SECURITY_CONTEXT”鍵),而無需我顯式執行session.setAttribute。 我的理由是,由於僅使用UsernamePasswordAuthenticationToken針對此請求對SecurityContext進行了身份驗證,因此我需要將其保留在HTTP會話中,以便可以為后續請求重新加載它。 因此,在我作為第一個順序放置的過濾器中,我檢查了HTTP會話中的SecurityContext,並將其“身份驗證”復制到當前的SecurityContext中。 請注意,如果在HTTP會話中未找到任何內容,我們還已經有一個過濾器,該過濾器順序放置在最后,以將用戶重定向到登錄頁面。

通過此更改,我們現在可以訪問Controller中的springSecurityService來訪問當前登錄的用戶(之前不能)。 我們獲得的其他好處包括能夠在GSP頁面中使用sec:ifAllGranted。 但是有些事情仍然不可行-我們似乎仍然無法使用@Secured(“#oauth2.isUser()”)批注來保護我們的Controller方法,總是命中401。也許我們使用UsernamePasswordAuthenticationToken進行身份驗證仍然不是我們需要“完整”身份驗證嗎?

我通過將OAuth2訪問令牌而不是SecurityContext放入HTTP會話中進行了進一步嘗試,並在我的過濾器中添加了帶有值“ Bearer” + accessToken的“ Authorization” HTTP標頭。 這是行不通的,初步日志記錄表明,在一個HTTP請求(頁面視圖)中,我多次進入過濾器,這是它第一次成功將OAuth2訪問令牌放入請求的HTTP標頭中。

因此,總而言之,我想找出的是,上面的方法是否正確,使用過濾器來保持用戶在會話中的連接? 還是我應該采取其他方法? 另外,如何使用@Secured注釋保護我的控制器?

考慮到我已經給出的答案,我不知道SO將如何對待該答案。 las,他真的很在乎-我是首先提出這個問題的人。

通過查看過的所有文檔,例如Grails Security-參考文檔Spring Security Core插件-參考文檔Grails Spring Security Core插件自定義身份驗證以及如何自定義grails的登錄頁面,盡管我們使用的是Spring Security OAuth2插件,還獲得了Spring Security Core插件提供的所有功能。 這意味着開箱即用地支持基於表單的登錄,並且還支持自定義的登錄表單。

並且使用自定義登錄表單,而不是讓login.gsp調用我們自己的AuthController的signIn方法,我們仍然可以使用$ {request.contextPath} /使用LoginController(與Spring Security插件一起提供)的“ authenticate”方法。登錄/驗證。

因此,我實際上嘗試了一下,當然,首先要禁用我們的攔截器,然后最終導致重定向循環,導致瀏覽器崩潰。 我認為我們需要做一些沒有的配置(使用chainMap或過濾器),這就是為什么它仍然不起作用的原因。

幫助的一件事是在grails-app / conf / logback.groovy中添加以下兩行

logger 'org.springframework.security', DEBUG, ['STDOUT'], false
logger 'grails.plugin.springsecurity', DEBUG, ['STDOUT'], false

這給了我很多日志信息來幫助調試。 由於某種原因,我意識到它以某種方式在我的請求中添加了一個匿名令牌,因為未對某些內容進行身份驗證。

花了一些時間進行調試之后,我終於找到了答案,那就是application.groovy。 我遇到的問題是,由於默認情況下所有應用程序都具有Spring Security filterChain,該鏈的最后一行為/ **匹配JOINED_FILTERS。 而且我們在安裝Spring Security OAuth2 Provider時添加的行具有/ **匹配JOINED_FILTERS減去一堆其他過濾器。 該行實際上是正確的行,但是因為我們從未刪除原始的/ **行,所以優先順序是在此行之前。 因此,我要做的就是刪除與** JOINED_FILTERS匹配的/ **的第一行,現在可以登錄了。

最后,filterChain中的行是:

[pattern: '/rest/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter'],
// We want all the other resources to be Web-based
[pattern: '/web/**',  filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-oauth2BasicAuthenticationFilter,-oauth2ExceptionTranslationFilter'],
// This is just a catch-all that defaults to the same logic as before, it also would match things like /oauth/** or /auth/**
[pattern: '/**',      filters: 'JOINED_FILTERS']

此外,我們還有一些其他配置,用於覆蓋指向我們自己的AuthController的覆蓋:

grails.plugin.springsecurity.auth.loginFormUrl = '/auth/login' // This is our own Login Form
grails.plugin.springsecurity.failureHandler.defaultFailureUrl = '/auth/login' // Exception message shown in controller
grails.plugin.springsecurity.adh.errorPage = '/auth/denied' // Copied from LoginController

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM