javascript
stateless_Spring Stateless State Security第3部分:JWT +社会认证
stateless
我的Stateless Spring Security系列文章的第三部分也是最后一部分是關于將基于JWT令牌的身份驗證與spring-social-security混合在一起的。 這篇文章直接建立在它的基礎上,并且主要集中在已更改的部分上。 想法是使用基于OAuth 2的“使用Facebook登錄”功能來替換基于用戶名/密碼的登錄,但是此后仍使用相同的基于令牌的身份驗證。
登錄流程
客戶端
用戶單擊“使用Facebook登錄”按鈕,這是指向“ / auth / facebook”的簡單鏈接,SocialAuthenticationFilter注意到缺少其他查詢參數(shù),并觸發(fā)了將您的網(wǎng)站用戶重定向到Facebook的重定向。 他們使用用戶名/密碼登錄,然后重定向回“ / auth / facebook”,但這一次指定了“?code =…&state =…”參數(shù)。 (如果用戶以前登錄過facebook并設置了cookie,facebook甚至會立即重定向回該用戶,而根本不向用戶顯示任何facebook屏幕。)有趣的是,您可以按照瀏覽器網(wǎng)絡日志中的說明進行操作。所有操作均使用純HTTP 302重定向完成。 (HTTP響應中的“ Location”標頭用于告訴瀏覽器下一步要去哪里)
服務器端
從facebook重定向到“ / auth / facebook?code =…&state =…”之后,SocialAuthenticationFilter現(xiàn)在將看到適當?shù)膮?shù),并將觸發(fā)兩個服務器調(diào)用Facebook。 第一個是獲取已登錄用戶的訪問令牌,第二個是通過使用訪問令牌獲取用戶詳細信息來測試整個過程是否成功。 完成所有這些操作后,就可以認為用戶已登錄,并且可以使用另一個302重定向(到“ /”)將他重定向回到應用程序的根目錄。
關于Spring社交的一些話
Spring Social是用于處理社交網(wǎng)絡的完整框架,其范圍遠遠超出了僅登錄場景。 除了不同的社交網(wǎng)絡適配器之外,還有一個名為Spring Social Security的小型集成庫,該庫以與Spring Security更好地集成的方式實現(xiàn)了社交身份驗證用例。 它帶有一個映射到“ / auth”的SocialAuthenticationFilter,這就是我們將要使用的。
因此,設置社交身份驗證需要使用簡潔的Spring Social Security庫配置Spring Social本身以及Spring Security 。
Spring社交
配置它基本上涉及擴展SocialConfigurerAdapter。 首先,您告訴它要支持哪些社交網(wǎng)絡:
將facebook添加為提供者
@Override public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {cfConfig.addConnectionFactory(new FacebookConnectionFactory(env.getProperty("facebook.appKey"),env.getProperty("facebook.appSecret"))); }它還需要知道如何獲取當前用戶的用戶ID:
檢索UserId
@Override public UserIdSource getUserIdSource() {//retrieve the UserId from the UserAuthentication in security contextreturn new UserAuthenticationUserIdSource(); }最后,它需要一個UsersConnectionRepository。 基本上負責用戶及其與社交網(wǎng)絡的連接之間的關系。 Spring Social帶有自己的兩個實現(xiàn)(jdbc或內(nèi)存中)。 我選擇自己動手,因為我想重用基于Spring Data JPA的UserDetailsS??ervice。
自定義UsersConnectionRepository
@Override public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {SimpleUsersConnectionRepository usersConnectionRepository =new SimpleUsersConnectionRepository(userService, connectionFactoryLocator);// if no local user record exists yet for a facebook's user id// automatically create a User and add it to the databaseusersConnectionRepository.setConnectionSignUp(autoSignUpHandler);return usersConnectionRepository; }Spring安全
如上一篇博客文章所述,對其進行配置基本上涉及擴展WebSecurityConfigurerAdapter。 除了配置和公開AuthenticationManager和UserDetailsS??ervice之類的常用內(nèi)容外,它現(xiàn)在還需要配置和插入SocialAuthenticationFilter。 由于SpringSocialConfigurer完成了大部分工作,因此這基本上只涉及很少的代碼。 它可能很簡單:
@Override protected void configure(HttpSecurity http) throws Exception {// apply the configuration from the socialConfigurer // (adds the SocialAuthenticationFilter)http.apply(new SpringSocialConfigurer()); }考慮到我想插入基于令牌的身份驗證,我自己的succesHandler和userIdSource; 我必須進行一些配置更改:
@Autowired private SocialAuthenticationSuccessHandler successHandler; @Autowired private StatelessAuthenticationFilter jwtFilter; @Autowired private UserIdSource userIdSource;@Override protected void configure(HttpSecurity http) throws Exception {// Set a custom successHandler on the SocialAuthenticationFilter (saf) final SpringSocialConfigurer sc = new SpringSocialConfigurer(); sc.addObjectPostProcessor(new ObjectPostProcessor<...>() {@Overridepublic <...> O postProcess(O saf) {saf.setAuthenticationSuccessHandler(successHandler);return saf;} });http....// add custom authentication filter for stateless JWT based authentication .addFilterBefore(jwtFilter, AbstractPreAuthenticatedProcessingFilter.class)// apply the configuration from the SocialConfigurer .apply(sc.userIdSource(userIdSource)); }如果您愿意,還可以繼承SpringSocialConfigurer的子類,并為自定義的successHandler提供更優(yōu)雅的設置器…
過去的樣板(在這里贊譽您)
現(xiàn)在是時候關注一些更有趣的地方了。
建立與Facebook的初始成功連接后,立即觸發(fā)自定義ConnectionSignUp:
@Override @Transactional public String execute(final Connection<?> connection) {//add new users to the db with its default rolesfinal User user = new User();final String firstName = connection.fetchUserProfile().getFirstName();user.setUsername(generateUniqueUserName(firstName));user.setProviderId(connection.getKey().getProviderId());user.setProviderUserId(connection.getKey().getProviderUserId());user.setAccessToken(connection.createData().getAccessToken());grantRoles(user);userRepository.save(user);return user.getUserId(); }如您所見,我的版本只是將用戶的連接數(shù)據(jù)持久化為單個JPA對象。 故意僅支持用戶與Facebook上的身份之間的一對一關系。
請注意,我最終從用戶生成的實際令牌中排除了連接屬性。 就像我之前排除了密碼字段(該字段不再是User對象的一部分)一樣:
@JsonIgnore private String accessToken;走這條路線確實意味著對facebook API的任何調(diào)用都需要數(shù)據(jù)庫查詢其他連接字段。 稍后將對此進行更多討論。
在用戶通過身份驗證之后,立即觸發(fā)自定義AuthenticationSuccessHandler:
@Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) {// Lookup the complete User object from the databasefinal User user = userService.loadUserByUsername(auth.getName());// Add UserAuthentication to the responsefinal UserAuthentication ua = new UserAuthentication(user);tokenAuthenticationService.addAuthentication(response, ua);super.onAuthenticationSuccess(request, response, auth); }這看起來很像以前博客文章中的代碼,但是我不得不在TokenAuthenticationService中進行一些更改。 由于客戶端是在重定向后加載的,因此要在此之前在客戶端保留令牌,必須將其作為cookie發(fā)送給客戶端:
public void addAuthentication(HttpServletResponse response, UserAuthentication authentication) {final User user = authentication.getDetails();user.setExpires(System.currentTimeMillis() + TEN_DAYS);final String token = tokenHandler.createTokenForUser(user);// Put the token into a cookie because the client can't capture response// headers of redirects / full page reloads. // (this response triggers a redirect back to "/")response.addCookie(createCookieForToken(token)); }最終成為最終重定向響應的一部分,如下所示:
成功登錄后,最終重定向到客戶端
成功登錄后,最終重定向到客戶端
最后也是最好的部分是所有代碼結合在一起形成一個非常漂亮的API。 由于Spring Social已經(jīng)負責創(chuàng)建用戶特定的請求范圍的ConnectionRepository,因此可以通過將以下bean代碼添加到SocialConfigurerAdapter來創(chuàng)建其特定于連接的API:
@Bean @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES) public Facebook facebook(ConnectionRepository repo) { Connection<Facebook> connection = repo.findPrimaryConnection(Facebook.class);return connection != null ? connection.getApi() : null; }此用戶特定的facebook bean可以在控制器中使用,如下所示:
@Autowired Facebook facebook;@RequestMapping(value = "/api/facebook/details", method = RequestMethod.GET) public FacebookProfile getSocialDetails() {return facebook.userOperations().getUserProfile(); }客戶端實施
如前所述,令牌現(xiàn)在作為Cookie傳遞給客戶端。 但是,就像以前一樣,服務器端仍然只接受發(fā)送到特殊HTTP標頭中的令牌。 承認這是相當隨意的,您可以讓它簡單地接受cookie。 我寧愿不要這樣做,因為它可以防止CSRF攻擊。 (因為無法指示瀏覽器將正確的身份驗證令牌自動添加到請求中。)
因此,在獲取當前用戶詳細信息之前,前端的init方法現(xiàn)在首先嘗試將cookie移至本地存儲:
$scope.init = function () {var authCookie = $cookies['AUTH-TOKEN'];if (authCookie) {TokenStorage.store(authCookie);delete $cookies['AUTH-TOKEN'];}$http.get('/api/user/current').success(function (user) {if (user.username) {$rootScope.authenticated = true;$scope.username = user.username;// For display purposes only$scope.token = JSON.parse(atob(TokenStorage.retrieve().split('.')[0]));}}); };自定義HTTP標頭的放置在與上次相同的http攔截器中進行處理。
實際的“使用Facebook登錄”按鈕只是觸發(fā)整個重定向狂潮的鏈接:
<a href="/auth/facebook"><button>Login with Facebook</button></a>為了檢查實際的Facebook API是否有效,我添加了另一個按鈕,用于在登錄后顯示來自facebook的用戶詳細信息。
最后的話(建議)
將我的自定義版本的JWT與社交身份驗證集成在一起是一個很大的旅程。 有些部分不那么瑣碎。 就像在將數(shù)據(jù)庫調(diào)用卸載到JWT令牌之間找到一個很好的平衡。 最終,我選擇不與客戶端共享Facebook的訪問令牌,因為只有在使用Facebook的API時才需要它。 這意味著對Facebook的任何查詢都需要數(shù)據(jù)庫調(diào)用來獲取令牌。 實際上,這意味著對任何具有@Autowired Facebook服務的控制器的任何REST API調(diào)用都會導致獲取請求令牌的過程非常熱烈,這是請求范圍的Bean創(chuàng)建的一部分。 但是,通過使用專用控制器進行Facebook調(diào)用可以輕松緩解這種情況,但這絕對是需要注意的。
如果您打算實際使用此代碼并進行Facebook API調(diào)用,請確保您的JWT令牌在facebook令牌之前過期(當前有效期為60天)。 最好在檢測到故障時實施強制重新登錄,因為任何重新登錄都會自動將新獲取的facebook令牌存儲在數(shù)據(jù)庫中。
您可以在github上找到完整的工作示例。 也可以在此處找到有關如何運行它的詳細信息。 我已經(jīng)包含了Maven和Gradle構建文件。
翻譯自: https://www.javacodegeeks.com/2015/01/stateless-spring-security-part-3-jwt-social-authentication.html
stateless
總結
以上是生活随笔為你收集整理的stateless_Spring Stateless State Security第3部分:JWT +社会认证的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓免流量软件(安卓免流量)
- 下一篇: 为什么非阻塞io性能更好_提高性能:流的