Skip to content

Commit 2838db7

Browse files
Refactor: Authorization Code Flow
Use customized error messages. Utilize error code constants.
1 parent 34c2c9a commit 2838db7

File tree

12 files changed

+238
-246
lines changed

12 files changed

+238
-246
lines changed

client/src/main/java/com/patternhelloworld/securityhelper/oauth2/client/config/securityimpl/message/CustomSecurityUserExceptionMessage.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ public enum CustomSecurityUserExceptionMessage implements ExceptionMessageInterf
2626
AUTHENTICATION_WRONG_CLIENT_ID_SECRET("1Client information is not verified."),
2727

2828
// GRANT TYPE
29-
AUTHENTICATION_WRONG_GRANT_TYPE("1Wrong Grant Type detected.");
29+
AUTHENTICATION_WRONG_GRANT_TYPE("1Wrong Grant Type detected."),
30+
31+
// OAuth2 : Authorization Code
32+
AUTHENTICATION_AUTHORIZATION_CODE_REQUEST_WRONG_METHOD("1Wrong Authorization Code request."),
33+
AUTHENTICATION_CLIENT_ID_MISSING("1Client ID is missing."),
34+
AUTHENTICATION_REDIRECT_URI_MISSING("1Redirect URI is missing."),
35+
AUTHENTICATION_STATE_MISSING("1State is missing."),
36+
AUTHENTICATION_REGISTERED_CLIENT_NOT_FOUND("1Registered client is missing or invalid"),
37+
AUTHENTICATION_AUTHORIZATION_CODE_MISSING("1Authorization Code is missing.");
3038

3139
private String message;
3240

client/src/main/java/com/patternhelloworld/securityhelper/oauth2/client/config/securityimpl/response/CustomWebAuthenticationFailureHandlerImpl.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.patternhelloworld.securityhelper.oauth2.client.config.securityimpl.response;
22

3-
43
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.message.ISecurityUserExceptionMessageService;
54
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.response.error.exception.EasyPlusOauth2AuthenticationException;
5+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.util.ErrorCodeConstants;
66
import jakarta.servlet.ServletException;
77
import jakarta.servlet.http.HttpServletRequest;
88
import jakarta.servlet.http.HttpServletResponse;
@@ -36,21 +36,22 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo
3636

3737
String errorMessage = "An unexpected error occurred.";
3838
List<String> errorDetails = new ArrayList<>();
39+
40+
EasyPlusOauth2AuthenticationException oauth2Exception;
3941
// Extract error messages if the exception is of type EasyPlusOauth2AuthenticationException
4042
if (exception instanceof EasyPlusOauth2AuthenticationException) {
41-
EasyPlusOauth2AuthenticationException oauth2Exception = (EasyPlusOauth2AuthenticationException) exception;
43+
oauth2Exception = (EasyPlusOauth2AuthenticationException) exception;
4244
errorMessage = oauth2Exception.getErrorMessages().getUserMessage();
43-
}
4445

45-
if(errorMessage.equals("Authorization code missing in GET request")){
46-
request.getRequestDispatcher("/login").forward(request, response);
47-
}else {
46+
if(oauth2Exception.getError().getErrorCode().equals(ErrorCodeConstants.REDIRECT_TO_LOGIN)){
47+
request.getRequestDispatcher("/login").forward(request, response);
48+
return;
49+
}
50+
}
51+
request.setAttribute("errorMessage", errorMessage);
52+
request.setAttribute("errorDetails", errorDetails);
4853

49-
// Redirect to /error with query parameters
50-
request.setAttribute("errorMessage", errorMessage);
51-
request.setAttribute("errorDetails", errorDetails);
54+
request.getRequestDispatcher("/error").forward(request, response);
5255

53-
request.getRequestDispatcher("/error").forward(request, response);
54-
}
5556
}
5657
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package io.github.patternhelloworld.securityhelper.oauth2.api.config.security.converter.auth.endpoint;
22

33
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.dao.EasyPlusAuthorizationConsentRepository;
4+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.message.DefaultSecurityUserExceptionMessage;
5+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.message.ISecurityUserExceptionMessageService;
6+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.response.error.dto.EasyPlusErrorMessages;
47
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.response.error.exception.EasyPlusOauth2AuthenticationException;
58
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.serivce.persistence.authorization.OAuth2AuthorizationServiceImpl;
6-
import io.github.patternhelloworld.securityhelper.oauth2.api.config.util.RequestOAuth2Distiller;
9+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.util.EasyPlusOAuth2EndpointUtils;
10+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.util.ErrorCodeConstants;
711
import jakarta.servlet.http.HttpServletRequest;
812
import lombok.RequiredArgsConstructor;
9-
import org.springframework.lang.Nullable;
13+
import org.springframework.security.authentication.AnonymousAuthenticationToken;
1014
import org.springframework.security.core.Authentication;
15+
import org.springframework.security.core.authority.AuthorityUtils;
1116
import org.springframework.security.core.context.SecurityContextHolder;
1217
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
1318
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
19+
import org.springframework.security.oauth2.core.oidc.OidcScopes;
1420
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
1521
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
1622
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
1723
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
1824
import org.springframework.security.web.authentication.AuthenticationConverter;
25+
import org.springframework.security.web.util.matcher.AndRequestMatcher;
26+
import org.springframework.security.web.util.matcher.RequestMatcher;
1927
import org.springframework.util.MultiValueMap;
2028
import org.springframework.util.StringUtils;
2129

@@ -28,98 +36,116 @@ public final class AuthorizationCodeAuthorizationRequestConverter implements Aut
2836
private final EasyPlusAuthorizationConsentRepository easyPlusAuthorizationConsentRepository;
2937
private final OAuth2AuthorizationServiceImpl oAuth2AuthorizationService;
3038

31-
public void setClientAuthenticationContext(String clientId) {
32-
RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);
33-
if (registeredClient == null) {
34-
throw new IllegalArgumentException("Invalid client ID");
35-
}
39+
private final ISecurityUserExceptionMessageService iSecurityUserExceptionMessageService;
3640

37-
OAuth2ClientAuthenticationToken clientAuthenticationToken = new OAuth2ClientAuthenticationToken(
38-
registeredClient,
39-
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
40-
null
41-
);
4241

43-
SecurityContextHolder.getContext().setAuthentication(clientAuthenticationToken);
44-
}
42+
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
43+
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
44+
45+
private static final RequestMatcher OIDC_REQUEST_MATCHER = createOidcRequestMatcher();
4546

47+
/*
48+
* Why is the validation check done here?
49+
* - Because if an OAuth2AuthenticationException is thrown in the CustomizedProvider,
50+
* Spring retries the process by replacing the CustomizedProvider with the OAuth2AuthorizationCodeRequestAuthenticationProvider.
51+
*
52+
* Where is OAuth2AuthorizationCodeRequestAuthenticationToken implemented?
53+
* - It is handled by "EasyPlusGrantAuthenticationToken" when calling "/oauth2/token"
54+
*/
4655
@Override
47-
@Nullable
4856
public Authentication convert(HttpServletRequest request) {
49-
if ("POST".equalsIgnoreCase(request.getMethod())) {
50-
// TODO: Authorization Consent
51-
} else if ("GET".equalsIgnoreCase(request.getMethod())) {
52-
MultiValueMap<String, String> parameters = RequestOAuth2Distiller.getAuthorizationCodeSecurityAdditionalParameters(request);
5357

58+
if (!"GET".equals(request.getMethod()) && !OIDC_REQUEST_MATCHER.matches(request)) {
59+
throw new EasyPlusOauth2AuthenticationException(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_AUTHORIZATION_CODE_REQUEST_WRONG_METHOD));
60+
}
5461

62+
MultiValueMap<String, String> parameters = EasyPlusOAuth2EndpointUtils.getWebParameters(request);
5563

56-
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
57-
if (!StringUtils.hasText(clientId)) {
58-
throw new EasyPlusOauth2AuthenticationException("client_id missing");
59-
}
60-
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
61-
if (!StringUtils.hasText(redirectUri)) {
62-
throw new EasyPlusOauth2AuthenticationException("redirect_uri missing");
63-
}
64+
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
65+
if (!StringUtils.hasText(clientId)) {
66+
throw new EasyPlusOauth2AuthenticationException(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_CLIENT_ID_MISSING));
67+
}
68+
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
69+
if (!StringUtils.hasText(redirectUri)) {
70+
throw new EasyPlusOauth2AuthenticationException(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_REDIRECT_URI_MISSING));
71+
}
6472

65-
// setClientAuthenticationContext from the client_id param
66-
setClientAuthenticationContext(clientId);
67-
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
73+
Map<String, Object> additionalParameters = new HashMap<>();
6874

75+
parameters.forEach((key, value) -> {
76+
additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0]));
77+
});
6978

70-
RegisteredClient registeredClient = ((OAuth2ClientAuthenticationToken) clientPrincipal).getRegisteredClient();
7179

72-
// Check if the registered client is null
73-
if (registeredClient == null) {
74-
throw new EasyPlusOauth2AuthenticationException("Registered client is missing or invalid");
75-
}
76-
// Check if the redirectUri is not in the registered redirect URIs
77-
if (!registeredClient.getRedirectUris().contains(redirectUri)) {
78-
throw new EasyPlusOauth2AuthenticationException("Invalid redirect_uri: " + redirectUri);
79-
}
80+
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
81+
if (!StringUtils.hasText(state)) {
82+
throw new EasyPlusOauth2AuthenticationException(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_STATE_MISSING));
83+
}
8084

85+
Set<String> requestedScopes = new HashSet<>(parameters.getOrDefault(OAuth2ParameterNames.SCOPE, Collections.emptyList()));
8186

82-
Set<String> requestedScopes = new HashSet<>(parameters.getOrDefault(OAuth2ParameterNames.SCOPE, Collections.emptyList()));
83-
// Scopes from the request
84-
Set<String> registeredScopes = registeredClient.getScopes(); // Scopes from the RegisteredClient
87+
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
88+
if (principal == null) {
89+
setClientAuthenticationContext(clientId);
90+
principal = SecurityContextHolder.getContext().getAuthentication();
91+
}
8592

86-
if (!registeredScopes.containsAll(requestedScopes)) {
87-
throw new EasyPlusOauth2AuthenticationException("Invalid scopes: " + requestedScopes + ". Allowed scopes: " + registeredScopes);
88-
}
93+
RegisteredClient registeredClient = ((OAuth2ClientAuthenticationToken) principal).getRegisteredClient();
8994

90-
Map<String, Object> additionalParameters = new HashMap<>();
95+
if (registeredClient == null) {
96+
throw new EasyPlusOauth2AuthenticationException(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_REGISTERED_CLIENT_NOT_FOUND));
97+
}
9198

92-
parameters.forEach((key, value) -> {
93-
additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0]));
94-
});
99+
if (!registeredClient.getRedirectUris().contains(redirectUri)) {
100+
throw new EasyPlusOauth2AuthenticationException(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_INVALID_REDIRECT_URI));
101+
}
95102

96-
String code = parameters.getFirst(OAuth2ParameterNames.CODE);
97-
if (!StringUtils.hasText(code)) {
98-
throw new EasyPlusOauth2AuthenticationException("Authorization code missing in GET request");
99-
}
103+
Set<String> registeredScopes = registeredClient.getScopes(); // Scopes from the RegisteredClient
100104

101-
return new OAuth2AuthorizationCodeAuthenticationToken(
102-
code,
103-
clientPrincipal,
104-
redirectUri,
105-
additionalParameters
106-
);
105+
if (!registeredScopes.containsAll(requestedScopes)) {
106+
throw new EasyPlusOauth2AuthenticationException(EasyPlusErrorMessages.builder().userMessage(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_INVALID_REDIRECT_URI))
107+
.message("Invalid scopes: " + requestedScopes + ". Allowed scopes: " + registeredScopes).build());
108+
}
107109

108-
} else {
109-
throw new IllegalStateException("Unsupported HTTP method: " + request.getMethod());
110+
String code = parameters.getFirst(OAuth2ParameterNames.CODE);
111+
if (!StringUtils.hasText(code)) {
112+
throw new EasyPlusOauth2AuthenticationException(EasyPlusErrorMessages.builder().userMessage(iSecurityUserExceptionMessageService.getUserMessage(DefaultSecurityUserExceptionMessage.AUTHENTICATION_AUTHORIZATION_CODE_MISSING))
113+
.errorCode(ErrorCodeConstants.REDIRECT_TO_LOGIN).build());
110114
}
111115

112-
return null;
113-
// TODO: Authorization Consent
114-
/* return new OAuth2AuthorizationCodeRequestAuthenticationToken(
115-
parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI),
116-
clientId,
117-
clientPrincipal,
116+
return new OAuth2AuthorizationCodeAuthenticationToken(
117+
code,
118+
principal,
118119
redirectUri,
119-
state,
120-
scopes,
121120
additionalParameters
122-
);*/
121+
);
123122
}
123+
124+
private static RequestMatcher createOidcRequestMatcher() {
125+
RequestMatcher postMethodMatcher = (request) -> "POST".equals(request.getMethod());
126+
RequestMatcher responseTypeParameterMatcher = (
127+
request) -> request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null;
128+
RequestMatcher openidScopeMatcher = (request) -> {
129+
String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
130+
return StringUtils.hasText(scope) && scope.contains(OidcScopes.OPENID);
131+
};
132+
return new AndRequestMatcher(postMethodMatcher, responseTypeParameterMatcher, openidScopeMatcher);
133+
}
134+
135+
public void setClientAuthenticationContext(String clientId) {
136+
RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);
137+
if (registeredClient == null) {
138+
throw new IllegalArgumentException("Invalid client ID");
139+
}
140+
141+
OAuth2ClientAuthenticationToken clientAuthenticationToken = new OAuth2ClientAuthenticationToken(
142+
registeredClient,
143+
ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
144+
null
145+
);
146+
147+
SecurityContextHolder.getContext().setAuthentication(clientAuthenticationToken);
148+
}
149+
124150
}
125151

Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.github.patternhelloworld.securityhelper.oauth2.api.config.security.converter.auth.endpoint;
22

3-
import io.github.patternhelloworld.securityhelper.oauth2.api.config.util.RequestOAuth2Distiller;
43
import io.github.patternhelloworld.securityhelper.oauth2.api.config.security.token.EasyPlusGrantAuthenticationToken;
4+
import io.github.patternhelloworld.securityhelper.oauth2.api.config.util.EasyPlusOAuth2EndpointUtils;
55
import jakarta.servlet.http.HttpServletRequest;
66
import lombok.RequiredArgsConstructor;
77
import org.springframework.security.core.Authentication;
@@ -16,22 +16,20 @@
1616
public final class PasswordAccessTokenRequestConverter implements AuthenticationConverter {
1717
/*
1818
* `
19-
* CustomGrantAuthenticationToken <- OAuth2ClientAuthenticationToken
19+
* EasyPlusGrantAuthenticationToken <- OAuth2ClientAuthenticationToken
2020
*
2121
* */
2222
@Override
2323
public Authentication convert(HttpServletRequest request) {
2424

2525
OAuth2ClientAuthenticationToken oAuth2ClientAuthenticationToken = (OAuth2ClientAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
2626

27-
Map<String, Object> additionalParameters = RequestOAuth2Distiller.getTokenUsingSecurityAdditionalParameters(request);
27+
Map<String, Object> additionalParameters = EasyPlusOAuth2EndpointUtils.getApiParameters(request);
2828

29+
// additionalParameters.put(Principal.class.getName(), easyPlusGrantAuthenticationToken);
2930

30-
EasyPlusGrantAuthenticationToken easyPlusGrantAuthenticationToken = new EasyPlusGrantAuthenticationToken(new AuthorizationGrantType((String) additionalParameters.get("grant_type")),
31+
return new EasyPlusGrantAuthenticationToken(new AuthorizationGrantType((String) additionalParameters.get("grant_type")),
3132
oAuth2ClientAuthenticationToken, additionalParameters);
32-
// additionalParameters.put(Principal.class.getName(), easyPlusGrantAuthenticationToken);
33-
34-
return easyPlusGrantAuthenticationToken;
3533
}
3634

3735
}

lib/src/main/java/io/github/patternhelloworld/securityhelper/oauth2/api/config/security/message/DefaultSecurityUserExceptionMessage.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,15 @@ public enum DefaultSecurityUserExceptionMessage implements ExceptionMessageInter
2525
AUTHENTICATION_WRONG_CLIENT_ID_SECRET("Client information is not verified."),
2626

2727
// GRANT TYPE
28-
AUTHENTICATION_WRONG_GRANT_TYPE("Wrong Grant Type detected.");
28+
AUTHENTICATION_WRONG_GRANT_TYPE("Wrong Grant Type detected."),
29+
30+
// OAuth2 : Authorization Code
31+
AUTHENTICATION_AUTHORIZATION_CODE_REQUEST_WRONG_METHOD("Wrong Authorization Code request."),
32+
AUTHENTICATION_CLIENT_ID_MISSING("Client ID is missing."),
33+
AUTHENTICATION_REDIRECT_URI_MISSING("Redirect URI is missing."),
34+
AUTHENTICATION_STATE_MISSING("State is missing."),
35+
AUTHENTICATION_REGISTERED_CLIENT_NOT_FOUND("Registered client is missing or invalid"),
36+
AUTHENTICATION_AUTHORIZATION_CODE_MISSING("Authorization Code is missing.");
2937

3038
private String message;
3139

0 commit comments

Comments
 (0)