Skip to content

Commit 0d82607

Browse files
Spring Reactive OAuth Login starts working
1 parent b780a44 commit 0d82607

File tree

12 files changed

+173
-87
lines changed

12 files changed

+173
-87
lines changed

spring-lemon-commons-reactive/src/main/java/com/naturalprogrammer/spring/lemon/commonsreactive/util/LecrUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
import org.apache.commons.logging.Log;
1010
import org.apache.commons.logging.LogFactory;
11+
import org.springframework.http.HttpCookie;
1112
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
13+
import org.springframework.web.server.ServerWebExchange;
1214

1315
import com.github.fge.jsonpatch.JsonPatchException;
1416
import com.naturalprogrammer.spring.lemon.commons.security.UserDto;
@@ -33,6 +35,10 @@ public void postConstruct() {
3335
NOT_FOUND_MONO = Mono.error(LexUtils.NOT_FOUND_EXCEPTION);
3436
}
3537

38+
public static Optional<HttpCookie> fetchCookie(ServerWebExchange exchange, String cookieName) {
39+
return Optional.ofNullable(exchange.getRequest().getCookies().getFirst(cookieName));
40+
}
41+
3642
/**
3743
* Gets the current-user
3844
*/

spring-lemon-commons/src/main/java/com/naturalprogrammer/spring/lemon/commons/AbstractLemonService.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.springframework.boot.context.event.ApplicationReadyEvent;
1111
import org.springframework.context.event.EventListener;
1212
import org.springframework.security.crypto.password.PasswordEncoder;
13+
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
1314

1415
import com.naturalprogrammer.spring.lemon.commons.LemonProperties.Admin;
1516
import com.naturalprogrammer.spring.lemon.commons.domain.LemonUser;
@@ -176,5 +177,40 @@ public void mailForgotPasswordLink(U user, String forgotPasswordLink) {
176177
LexUtils.getMessage("com.naturalprogrammer.spring.forgotPasswordEmail",
177178
forgotPasswordLink)));
178179
}
180+
181+
/**
182+
* Extracts the email id from user attributes received from OAuth2 provider, e.g. Google
183+
*
184+
*/
185+
public String getOAuth2Email(String registrationId, Map<String, Object> attributes) {
186+
187+
return (String) attributes.get(StandardClaimNames.EMAIL);
188+
}
189+
190+
191+
/**
192+
* Extracts additional fields, e.g. name from user attributes received from OAuth2 provider, e.g. Google
193+
* Override this if you introduce more user fields, e.g. name
194+
*/
195+
public void fillAdditionalFields(String clientId, U user, Map<String, Object> attributes) {
196+
197+
}
198+
199+
200+
/**
201+
* Checks if the account at the OAuth2 provider is verified
202+
*/
203+
public boolean getOAuth2AccountVerified(String registrationId, Map<String, Object> attributes) {
204+
205+
Object verified = attributes.get(StandardClaimNames.EMAIL_VERIFIED);
206+
if (verified == null)
207+
verified = attributes.get("verified");
208+
209+
try {
210+
return (boolean) verified;
211+
} catch (Throwable t) {
212+
return false;
213+
}
214+
}
179215

180216
}

spring-lemon-commons/src/main/java/com/naturalprogrammer/spring/lemon/commons/util/LecUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ public class LecUtils {
4040

4141
private static final Log log = LogFactory.getLog(LecUtils.class);
4242

43+
public static final String AUTHORIZATION_REQUEST_COOKIE_NAME = "lemon_oauth2_authorization_request";
44+
public static final String LEMON_REDIRECT_URI_COOKIE_PARAM_NAME = "lemon_redirect_uri";
45+
4346
// Computed authorities
4447
public static final String GOOD_ADMIN = "GOOD_ADMIN";
4548
public static final String GOOD_USER = "GOOD_USER";

spring-lemon-jpa/src/main/java/com/naturalprogrammer/spring/lemon/LemonService.java

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -556,42 +556,6 @@ public void changeEmail(ID userId, @Valid @NotBlank String changeEmailCode) {
556556
}
557557

558558

559-
/**
560-
* Extracts the email id from user attributes received from OAuth2 provider, e.g. Google
561-
*
562-
*/
563-
public String getOAuth2Email(String registrationId, Map<String, Object> attributes) {
564-
565-
return (String) attributes.get(StandardClaimNames.EMAIL);
566-
}
567-
568-
569-
/**
570-
* Extracts additional fields, e.g. name from user attributes received from OAuth2 provider, e.g. Google
571-
* Override this if you introduce more user fields, e.g. name
572-
*/
573-
public void fillAdditionalFields(String clientId, U user, Map<String, Object> attributes) {
574-
575-
}
576-
577-
578-
/**
579-
* Checks if the account at the OAuth2 provider is verified
580-
*/
581-
public boolean getOAuth2AccountVerified(String registrationId, Map<String, Object> attributes) {
582-
583-
Object verified = attributes.get(StandardClaimNames.EMAIL_VERIFIED);
584-
if (verified == null)
585-
verified = attributes.get("verified");
586-
587-
try {
588-
return (boolean) verified;
589-
} catch (Throwable t) {
590-
return false;
591-
}
592-
}
593-
594-
595559
/**
596560
* Fetches a new token - for session scrolling etc.
597561
* @return

spring-lemon-jpa/src/main/java/com/naturalprogrammer/spring/lemon/security/HttpCookieOAuth2AuthorizationRequestRepository.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919
*/
2020
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
2121

22-
public static final String AUTHORIZATION_REQUEST_COOKIE_NAME = "lemon_oauth2_authorization_request";
23-
public static final String LEMON_REDIRECT_URI_COOKIE_PARAM_NAME = "lemon_redirect_uri";
24-
2522
private int cookieExpirySecs;
2623

2724
public HttpCookieOAuth2AuthorizationRequestRepository(LemonProperties properties) {
@@ -37,7 +34,7 @@ public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest re
3734

3835
Assert.notNull(request, "request cannot be null");
3936

40-
return LecwUtils.fetchCookie(request, AUTHORIZATION_REQUEST_COOKIE_NAME)
37+
return LecwUtils.fetchCookie(request, LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME)
4138
.map(this::deserialize)
4239
.orElse(null);
4340
}
@@ -54,20 +51,20 @@ public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationReq
5451

5552
if (authorizationRequest == null) {
5653

57-
deleteCookies(request, response, AUTHORIZATION_REQUEST_COOKIE_NAME, LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
54+
deleteCookies(request, response, LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME, LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
5855
return;
5956
}
6057

61-
Cookie cookie = new Cookie(AUTHORIZATION_REQUEST_COOKIE_NAME, LecUtils.serialize(authorizationRequest));
58+
Cookie cookie = new Cookie(LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME, LecUtils.serialize(authorizationRequest));
6259
cookie.setPath("/");
6360
cookie.setHttpOnly(true);
6461
cookie.setMaxAge(cookieExpirySecs);
6562
response.addCookie(cookie);
6663

67-
String lemonRedirectUri = request.getParameter(LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
64+
String lemonRedirectUri = request.getParameter(LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
6865
if (StringUtils.isNotBlank(lemonRedirectUri)) {
6966

70-
cookie = new Cookie(LEMON_REDIRECT_URI_COOKIE_PARAM_NAME, lemonRedirectUri);
67+
cookie = new Cookie(LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME, lemonRedirectUri);
7168
cookie.setPath("/");
7269
cookie.setHttpOnly(true);
7370
cookie.setMaxAge(cookieExpirySecs);
@@ -79,7 +76,7 @@ public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationReq
7976
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) {
8077

8178
OAuth2AuthorizationRequest originalRequest = loadAuthorizationRequest(request);
82-
deleteCookies(request, response, AUTHORIZATION_REQUEST_COOKIE_NAME);
79+
deleteCookies(request, response, LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME);
8380
return originalRequest;
8481
}
8582

spring-lemon-jpa/src/main/java/com/naturalprogrammer/spring/lemon/security/OAuth2AuthenticationFailureHandler.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import org.springframework.security.core.AuthenticationException;
1010
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
1111

12+
import com.naturalprogrammer.spring.lemon.commons.util.LecUtils;
13+
1214
/**
1315
* OAuth2 Authentication failure handler for removing oauth2 related cookies
1416
*
@@ -23,8 +25,8 @@ public void onAuthenticationFailure(HttpServletRequest request,
2325
throws IOException, ServletException {
2426

2527
HttpCookieOAuth2AuthorizationRequestRepository.deleteCookies(request, response,
26-
HttpCookieOAuth2AuthorizationRequestRepository.AUTHORIZATION_REQUEST_COOKIE_NAME,
27-
HttpCookieOAuth2AuthorizationRequestRepository.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
28+
LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME,
29+
LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
2830

2931
super.onAuthenticationFailure(request, response, exception);
3032
}

spring-lemon-jpa/src/main/java/com/naturalprogrammer/spring/lemon/security/OAuth2AuthenticationSuccessHandler.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.naturalprogrammer.spring.lemon.security;
22

3-
import java.io.Serializable;
4-
53
import javax.servlet.http.Cookie;
64
import javax.servlet.http.HttpServletRequest;
75
import javax.servlet.http.HttpServletResponse;
@@ -13,6 +11,7 @@
1311
import com.naturalprogrammer.spring.lemon.commons.LemonProperties;
1412
import com.naturalprogrammer.spring.lemon.commons.security.BlueTokenService;
1513
import com.naturalprogrammer.spring.lemon.commons.security.UserDto;
14+
import com.naturalprogrammer.spring.lemon.commons.util.LecUtils;
1615
import com.naturalprogrammer.spring.lemon.commonsweb.util.LecwUtils;
1716

1817
import lombok.AllArgsConstructor;
@@ -44,13 +43,13 @@ protected String determineTargetUrl(HttpServletRequest request,
4443
(long) properties.getJwt().getShortLivedMillis());
4544

4645
String targetUrl = LecwUtils.fetchCookie(request,
47-
HttpCookieOAuth2AuthorizationRequestRepository.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME)
46+
LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME)
4847
.map(Cookie::getValue)
4948
.orElse(properties.getOauth2AuthenticationSuccessUrl());
5049

5150
HttpCookieOAuth2AuthorizationRequestRepository.deleteCookies(request, response,
52-
HttpCookieOAuth2AuthorizationRequestRepository.AUTHORIZATION_REQUEST_COOKIE_NAME,
53-
HttpCookieOAuth2AuthorizationRequestRepository.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
51+
LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME,
52+
LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
5453

5554
return targetUrl + shortLivedAuthToken;
5655
}

spring-lemon-reactive/src/main/java/com/naturalprogrammer/spring/lemonreactive/LemonReactiveAutoConfiguration.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.springframework.context.annotation.Bean;
1111
import org.springframework.context.annotation.Configuration;
1212
import org.springframework.security.core.userdetails.UserDetailsService;
13+
import org.springframework.security.crypto.password.PasswordEncoder;
1314

1415
import com.naturalprogrammer.spring.lemon.commons.LemonProperties;
1516
import com.naturalprogrammer.spring.lemon.commons.domain.IdConverter;
@@ -45,14 +46,23 @@ IdConverter<ID> idConverter(LemonReactiveService<?,ID> lemonService) {
4546
@Bean
4647
@ConditionalOnMissingBean(ReactiveOAuth2AuthenticationSuccessHandler.class)
4748
public <U extends AbstractMongoUser<ID>, ID extends Serializable>
48-
ReactiveOAuth2AuthenticationSuccessHandler reactiveOAuth2AuthenticationSuccessHandler(
49+
ReactiveOAuth2AuthenticationSuccessHandler<U, ID> reactiveOAuth2AuthenticationSuccessHandler(
4950
BlueTokenService blueTokenService,
50-
LemonProperties properties) {
51+
AbstractMongoUserRepository<U, ID> userRepository,
52+
LemonReactiveUserDetailsService<U, ID> userDetailsService,
53+
LemonReactiveService<U, ID> lemonService,
54+
PasswordEncoder passwordEncoder,
55+
LemonProperties properties
56+
) {
5157

5258
log.info("Configuring ReactiveOAuth2AuthenticationSuccessHandler ...");
5359

54-
return new ReactiveOAuth2AuthenticationSuccessHandler(
60+
return new ReactiveOAuth2AuthenticationSuccessHandler<U,ID>(
5561
blueTokenService,
62+
userRepository,
63+
userDetailsService,
64+
lemonService,
65+
passwordEncoder,
5666
properties);
5767
}
5868

@@ -62,7 +72,7 @@ ReactiveOAuth2AuthenticationSuccessHandler reactiveOAuth2AuthenticationSuccessHa
6272
LemonReactiveSecurityConfig<U,ID> lemonReactiveSecurityConfig(
6373
BlueTokenService blueTokenService,
6474
LemonReactiveUserDetailsService<U, ID> userDetailsService,
65-
ReactiveOAuth2AuthenticationSuccessHandler reactiveOAuth2AuthenticationSuccessHandler,
75+
ReactiveOAuth2AuthenticationSuccessHandler<U,ID> reactiveOAuth2AuthenticationSuccessHandler,
6676
LemonProperties properties) {
6777

6878
log.info("Configuring LemonReactiveSecurityConfig ...");

spring-lemon-reactive/src/main/java/com/naturalprogrammer/spring/lemonreactive/security/LemonReactiveSecurityConfig.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import org.apache.commons.logging.Log;
66
import org.apache.commons.logging.LogFactory;
77
import org.springframework.security.config.web.server.ServerHttpSecurity;
8+
import org.springframework.security.core.AuthenticationException;
89
import org.springframework.security.core.userdetails.UsernameNotFoundException;
10+
import org.springframework.security.web.server.WebFilterExchange;
911
import org.springframework.security.web.server.authentication.WebFilterChainServerAuthenticationSuccessHandler;
1012

1113
import com.naturalprogrammer.spring.lemon.commons.LemonProperties;
1214
import com.naturalprogrammer.spring.lemon.commons.security.BlueTokenService;
1315
import com.naturalprogrammer.spring.lemon.commons.security.UserDto;
16+
import com.naturalprogrammer.spring.lemon.commons.util.LecUtils;
1417
import com.naturalprogrammer.spring.lemon.commonsreactive.security.LemonCommonsReactiveSecurityConfig;
1518
import com.naturalprogrammer.spring.lemonreactive.domain.AbstractMongoUser;
1619
import com.naturalprogrammer.spring.lemonreactive.util.LerUtils;
@@ -24,11 +27,11 @@ public class LemonReactiveSecurityConfig<U extends AbstractMongoUser<ID>, ID ext
2427

2528
protected LemonReactiveUserDetailsService<U, ID> userDetailsService;
2629
private LemonProperties properties;
27-
private ReactiveOAuth2AuthenticationSuccessHandler reactiveOAuth2AuthenticationSuccessHandler;
30+
private ReactiveOAuth2AuthenticationSuccessHandler<U,ID> reactiveOAuth2AuthenticationSuccessHandler;
2831

2932
public LemonReactiveSecurityConfig(BlueTokenService blueTokenService,
3033
LemonReactiveUserDetailsService<U, ID> userDetailsService,
31-
ReactiveOAuth2AuthenticationSuccessHandler reactiveOAuth2AuthenticationSuccessHandler,
34+
ReactiveOAuth2AuthenticationSuccessHandler<U,ID> reactiveOAuth2AuthenticationSuccessHandler,
3235
LemonProperties properties) {
3336

3437
super(blueTokenService);
@@ -68,7 +71,7 @@ protected void oauth2Login(ServerHttpSecurity http) {
6871
http.oauth2Login()
6972
.authorizedClientRepository(new ReactiveCookieServerOAuth2AuthorizedClientRepository(properties))
7073
.authenticationSuccessHandler(reactiveOAuth2AuthenticationSuccessHandler)
71-
.authenticationManager(authenticationManager);
74+
.authenticationFailureHandler(this::onAuthenticationFailure);
7275
}
7376

7477
@Override
@@ -84,4 +87,13 @@ protected Mono<UserDto> fetchUserDto(JWTClaimsSet claims) {
8487
})
8588
.map(AbstractMongoUser::toUserDto);
8689
}
90+
91+
protected Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
92+
93+
ReactiveCookieServerOAuth2AuthorizedClientRepository.deleteCookies(webFilterExchange.getExchange(),
94+
LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME,
95+
LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
96+
97+
return Mono.error(exception);
98+
}
8799
}

spring-lemon-reactive/src/main/java/com/naturalprogrammer/spring/lemonreactive/security/ReactiveCookieServerOAuth2AuthorizedClientRepository.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@
1515

1616
import com.naturalprogrammer.spring.lemon.commons.LemonProperties;
1717
import com.naturalprogrammer.spring.lemon.commons.util.LecUtils;
18-
import com.naturalprogrammer.spring.lemonreactive.util.LerUtils;
18+
import com.naturalprogrammer.spring.lemon.commonsreactive.util.LecrUtils;
1919

2020
import reactor.core.publisher.Mono;
2121

2222
public class ReactiveCookieServerOAuth2AuthorizedClientRepository implements ServerOAuth2AuthorizedClientRepository {
2323

24-
public static final String AUTHORIZATION_REQUEST_COOKIE_NAME = "lemon_oauth2_authorization_request";
25-
public static final String LEMON_REDIRECT_URI_COOKIE_PARAM_NAME = "lemon_redirect_uri";
26-
2724
private int cookieExpirySecs;
2825

2926
public ReactiveCookieServerOAuth2AuthorizedClientRepository(LemonProperties properties) {
@@ -35,7 +32,7 @@ public ReactiveCookieServerOAuth2AuthorizedClientRepository(LemonProperties prop
3532
public Mono<OAuth2AuthorizedClient> loadAuthorizedClient(String clientRegistrationId,
3633
Authentication principal, ServerWebExchange exchange) {
3734

38-
return LerUtils.fetchCookie(exchange, AUTHORIZATION_REQUEST_COOKIE_NAME)
35+
return LecrUtils.fetchCookie(exchange, LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME)
3936
.map(this::deserialize)
4037
.orElse(Mono.empty());
4138
}
@@ -49,12 +46,12 @@ public Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient,
4946
Assert.notNull(exchange, "exchange cannot be null");
5047
if (authorizedClient == null) {
5148

52-
deleteCookies(exchange, AUTHORIZATION_REQUEST_COOKIE_NAME, LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
49+
deleteCookies(exchange, LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME, LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
5350
return Mono.empty();
5451
}
5552

5653
ResponseCookie cookie = ResponseCookie
57-
.from(AUTHORIZATION_REQUEST_COOKIE_NAME, LecUtils.serialize(authorizedClient))
54+
.from(LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME, LecUtils.serialize(authorizedClient))
5855
.path("/")
5956
.httpOnly(true)
6057
.maxAge(cookieExpirySecs)
@@ -63,12 +60,12 @@ public Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient,
6360
response.addCookie(cookie);
6461

6562
String lemonRedirectUri = exchange.getRequest()
66-
.getQueryParams().getFirst(LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
63+
.getQueryParams().getFirst(LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME);
6764

6865
if (StringUtils.isNotBlank(lemonRedirectUri)) {
6966

7067
cookie = ResponseCookie
71-
.from(LEMON_REDIRECT_URI_COOKIE_PARAM_NAME, lemonRedirectUri)
68+
.from(LecUtils.LEMON_REDIRECT_URI_COOKIE_PARAM_NAME, lemonRedirectUri)
7269
.path("/")
7370
.httpOnly(true)
7471
.maxAge(cookieExpirySecs)
@@ -84,11 +81,11 @@ public Mono<Void> saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient,
8481
public Mono<Void> removeAuthorizedClient(String clientRegistrationId, Authentication principal,
8582
ServerWebExchange exchange) {
8683

87-
deleteCookies(exchange, AUTHORIZATION_REQUEST_COOKIE_NAME);
84+
deleteCookies(exchange, LecUtils.AUTHORIZATION_REQUEST_COOKIE_NAME);
8885
return Mono.empty();
8986
}
9087

91-
private void deleteCookies(ServerWebExchange exchange, String ...cookiesToDelete) {
88+
public static void deleteCookies(ServerWebExchange exchange, String ...cookiesToDelete) {
9289

9390
MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
9491
MultiValueMap<String, ResponseCookie> responseCookies = exchange.getResponse().getCookies();

0 commit comments

Comments
 (0)