Skip to content

Commit 3582367

Browse files
feature : io.github.patternknife.securityhelper.oauth2.no-app-token-same-access-token
1 parent ed38308 commit 3582367

File tree

7 files changed

+60
-18
lines changed

7 files changed

+60
-18
lines changed

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# Spring Security Oauth2 Password JPA Implementation
22

3-
> One OAuth2 ROPC POC built to grow with Spring Boot and ORM
3+
> App-Token based OAuth2 ROPC POC built to grow with Spring Boot and ORM
44
55
## Quick Start
66
```xml
77
<dependency>
88
<groupId>io.github.patternknife.securityhelper.oauth2.api</groupId>
99
<artifactId>spring-security-oauth2-password-jpa-implementation</artifactId>
10-
<version>3.0.1</version>
10+
<version>3.1.0</version>
1111
</dependency>
1212
```
1313
For v2, using the database tables from Spring Security 5 (only the database tables; follow the dependencies as above):
@@ -52,6 +52,23 @@ For v2, using the database tables from Spring Security 5 (only the database tabl
5252

5353
* Authentication management based on a combination of username, client ID, and App-Token
5454
* What is an App-Token? An App-Token is a new access token generated each time the same account logs in. If the token values are the same, the same access token is shared.
55+
56+
| App-Token Status | Access Token Behavior |
57+
|------------------------|----------------------------|
58+
| same for the same user | Access-Token is shared |
59+
| different for the same user | Access-Token is NOT shared |
60+
61+
* Set this in your ``application.properties``.
62+
* App-Token Behavior Based on `io.github.patternknife.securityhelper.oauth2.no-app-token-same-access-token`
63+
64+
| `no-app-token-same-access-token` Value | App-Token Status | Access Token Sharing Behavior |
65+
|------------------------------------------------------------|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------|
66+
| `true` | App-Token is `null` for the same user | Same user with a `null` App-Token shares the same access token across multiple logins. |
67+
| `false` | App-Token is `null` for the same user | Even if the App-Token is `null`, the same user will receive a new access token for each login. |
68+
| `-` | App-Token is shared for the same user | Access tokens will not be shared. A new access token is generated for each unique App-Token, even for the same user.|
69+
| `-` | App-Token is NOT shared for the same user | Each unique App-Token generates a new access token for the same user. |
70+
71+
5572
* Separated UserDetails implementation for Admin and Customer roles as an example. (This can be extended as desired by implementing ``UserDetailsServiceFactory``)
5673
* For versions greater than or equal to v3, including the latest version (Spring Security 6), provide MySQL DDL, which consists of ``oauth2_authorization`` and ``oauth2_registered_client``.
5774
* For v2 (Spring Security 5), provide MySQL DDL, which consists of ``oauth_access_token, oauth_refresh_token and oauth_client_details``, which are tables in Security 5. As I meant to migrate current security system to Security 6 back then, I hadn't changed them to the ``oauth2_authorization`` table indicated in https://github.com/spring-projects/spring-authorization-server.

client/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
77
<modelVersion>4.0.0</modelVersion>
88
<groupId>com.patternknife.securityhelper.oauth2.client</groupId>
99
<artifactId>spring-security-oauth2-password-jpa-implementation-client</artifactId>
10-
<version>3.0.1</version>
10+
<version>3.1.0</version>
1111
<packaging>jar</packaging>
1212

1313
<properties>
@@ -48,7 +48,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
4848
<dependency>
4949
<groupId>io.github.patternknife.securityhelper.oauth2.api</groupId>
5050
<artifactId>spring-security-oauth2-password-jpa-implementation</artifactId>
51-
<version>3.0.1</version>
51+
<version>3.1.0</version>
5252
</dependency>
5353

5454
<!-- DB -->

client/src/main/resources/application.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ spring.servlet.multipart.maxRequestSize=10MB
6767

6868
management.endpoints.web.exposure.include=*
6969
app.timezone=Asia/Seoul
70+
71+
io.github.patternknife.securityhelper.oauth2.no-app-token-same-access-token=false

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
88

99
<groupId>io.github.patternknife.securityhelper.oauth2.api</groupId>
1010
<artifactId>spring-security-oauth2-password-jpa-implementation</artifactId>
11-
<version>3.0.1</version>
11+
<version>3.1.0</version>
1212
<name>spring-security-oauth2-password-jpa-implementation</name>
1313
<description>The implementation of Spring Security 6 Spring Authorization Server for stateful OAuth2 Password Grant</description>
1414
<packaging>jar</packaging>
@@ -362,7 +362,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
362362
<!-- <plugin>
363363
<groupId>org.apache.maven.plugins</groupId>
364364
<artifactId>maven-gpg-plugin</artifactId>
365-
<version>3.0.1</version>
365+
<version>3.1.0</version>
366366
<executions>
367367
<execution>
368368
<id>sign-artifacts</id>

src/main/java/io/github/patternknife/securityhelper/oauth2/api/config/security/dao/KnifeAuthorizationRepository.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ Optional<KnifeAuthorization> findValidAuthorizationByPrincipalNameAndClientIdAnd
9999
@Param("accessTokenAppToken") String accessTokenAppToken
100100
);
101101

102+
@Query("SELECT o FROM KnifeAuthorization o WHERE o.principalName = :principalName AND o.registeredClientId = :registeredClientId AND " +
103+
"(o.accessTokenAppToken = :accessTokenAppToken OR (o.accessTokenAppToken IS NULL AND :accessTokenAppToken IS NULL)) " +
104+
"AND o.accessTokenExpiresAt > CURRENT_TIMESTAMP")
105+
Optional<KnifeAuthorization> findValidAuthorizationByPrincipalNameAndClientIdAndNullableAppToken(
106+
@Param("principalName") String principalName,
107+
@Param("registeredClientId") String registeredClientId,
108+
@Param("accessTokenAppToken") String accessTokenAppToken
109+
);
110+
102111

103112

104113
Optional<List<KnifeAuthorization>> findListByPrincipalNameAndRegisteredClientIdAndAccessTokenAppToken(String principalName, String registeredClientId, String accessTokenAppToken);

src/main/java/io/github/patternknife/securityhelper/oauth2/api/config/security/serivce/persistence/authorization/OAuth2AuthorizationServiceImpl.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@
1111
import jakarta.validation.constraints.NotEmpty;
1212
import lombok.RequiredArgsConstructor;
1313
import org.apache.commons.lang3.StringUtils;
14+
import org.springframework.beans.factory.annotation.Value;
1415
import org.springframework.context.annotation.Configuration;
15-
import org.springframework.security.oauth2.core.AuthorizationGrantType;
16+
1617
import org.springframework.security.oauth2.core.OAuth2AccessToken;
1718
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
1819
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
1920
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
2021
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
2122

22-
import java.time.Duration;
23+
2324
import java.time.Instant;
2425
import java.time.LocalDateTime;
2526
import java.time.ZoneId;
26-
import java.util.List;
27+
2728
import java.util.Optional;
2829
import java.util.function.Consumer;
2930
import java.util.function.Supplier;
@@ -35,6 +36,7 @@ public class OAuth2AuthorizationServiceImpl implements OAuth2AuthorizationServic
3536
private final KnifeAuthorizationRepository knifeAuthorizationRepository;
3637
private final SecurityPointCut securityPointCut;
3738

39+
3840
/*
3941
* 1. C for Create
4042
* */
@@ -155,12 +157,24 @@ public OAuth2Authorization findByToken(@NotEmpty String tokenValue, @Nullable OA
155157
.orElse(null);
156158

157159
}
160+
161+
162+
@Value("${io.github.patternknife.securityhelper.oauth2.no-app-token-same-access-token:true}")
163+
private boolean noAppTokenSameAccessToken;
158164
/*
159165
* [IMPORTANT] KEY = Username (principalName) + ClientId + AppToken
160166
* Same ( org.springframework.security.core.userdetails : userName + spring-authorization-server : principalName )
161167
* */
162168
public @Nullable OAuth2Authorization findByUserNameAndClientIdAndAppToken(@NotEmpty String userName, @NotEmpty String clientId, @Nullable String appToken) {
163-
return findOAuth2AuthorizationByAccessTokenValueSafely(() -> knifeAuthorizationRepository.findValidAuthorizationByPrincipalNameAndClientIdAndAppToken(userName, clientId, appToken).map(KnifeAuthorization::getAttributes),
169+
if (noAppTokenSameAccessToken) {
170+
return findAuthorization(() -> knifeAuthorizationRepository.findValidAuthorizationByPrincipalNameAndClientIdAndNullableAppToken(userName, clientId, appToken), userName, clientId, appToken);
171+
} else {
172+
return findAuthorization(() -> knifeAuthorizationRepository.findValidAuthorizationByPrincipalNameAndClientIdAndAppToken(userName, clientId, appToken), userName, clientId, appToken);
173+
}
174+
}
175+
176+
private @Nullable OAuth2Authorization findAuthorization(Supplier<Optional<KnifeAuthorization>> authorizationSupplier, String userName, String clientId, @Nullable String appToken) {
177+
return findOAuth2AuthorizationByAccessTokenValueSafely(() -> authorizationSupplier.get().map(KnifeAuthorization::getAttributes),
164178
e -> {
165179
knifeAuthorizationRepository.findListByPrincipalNameAndRegisteredClientIdAndAccessTokenAppToken(userName, clientId, appToken).ifPresent(knifeAuthorizationRepository::deleteAll);
166180
knifeAuthorizationRepository.deleteByPrincipalNameAndRegisteredClientIdAndAccessTokenAppToken(userName, clientId, appToken);

src/main/java/io/github/patternknife/securityhelper/oauth2/api/config/security/serivce/persistence/client/RegisteredClientRepositoryImpl.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -120,28 +120,28 @@ public void save(RegisteredClient registeredClient) {
120120
}
121121

122122

123-
private RegisteredClient mapToRegisteredClient(KnifeClient detail) {
124-
Set<String> scopesSet = Arrays.stream(detail.getScopes().split(","))
123+
private RegisteredClient mapToRegisteredClient(KnifeClient knifeClient) {
124+
Set<String> scopesSet = Arrays.stream(knifeClient.getScopes().split(","))
125125
.map(String::trim)
126126
.collect(Collectors.toSet());
127127

128-
Set<AuthorizationGrantType> grantTypesSet = Arrays.stream(detail.getAuthorizationGrantTypes().split(","))
128+
Set<AuthorizationGrantType> grantTypesSet = Arrays.stream(knifeClient.getAuthorizationGrantTypes().split(","))
129129
.map(String::trim)
130130
.map(AuthorizationGrantType::new)
131131
.collect(Collectors.toSet());
132132

133133
// Assuming getTokenSettings() returns a map-like structure for token settings.
134-
Map<String, Object> tokenSettings = parseMap(detail.getTokenSettings());
134+
Map<String, Object> tokenSettings = parseMap(knifeClient.getTokenSettings());
135135

136136
// Extract token time-to-live values from tokenSettings (assuming they are stored as strings or numbers)
137137
Duration accessTokenTimeToLive = Duration.ofSeconds(Long.parseLong(tokenSettings.get("access_token_time_to_live").toString()));
138138
Duration refreshTokenTimeToLive = Duration.ofSeconds(Long.parseLong(tokenSettings.get("refresh_token_time_to_live").toString()));
139139

140140

141-
return RegisteredClient.withId(UUID.randomUUID().toString())
142-
.clientId(detail.getClientId())
143-
.clientSecret(detail.getClientSecret())
144-
.clientName(detail.getClientId())
141+
return RegisteredClient.withId(knifeClient.getId())
142+
.clientId(knifeClient.getClientId())
143+
.clientSecret(knifeClient.getClientSecret())
144+
.clientName(knifeClient.getClientId())
145145
.clientAuthenticationMethods(authenticationMethods ->
146146
authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) // Adjust based on your entity
147147
.authorizationGrantTypes(grantTypes -> grantTypes.addAll(grantTypesSet))

0 commit comments

Comments
 (0)