Skip to content

Commit cf7ecc1

Browse files
xuxiaowei-com-cnSteve Riesenberg
authored and
Steve Riesenberg
committed
JDBC device_code authorization
Issue gh-1156
1 parent 8e04da7 commit cf7ecc1

File tree

5 files changed

+156
-13
lines changed

5 files changed

+156
-13
lines changed

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java

+75-9
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,7 @@
4747
import org.springframework.jdbc.support.lob.LobHandler;
4848
import org.springframework.lang.Nullable;
4949
import org.springframework.security.jackson2.SecurityJackson2Modules;
50-
import org.springframework.security.oauth2.core.AuthorizationGrantType;
51-
import org.springframework.security.oauth2.core.OAuth2AccessToken;
52-
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
53-
import org.springframework.security.oauth2.core.OAuth2Token;
50+
import org.springframework.security.oauth2.core.*;
5451
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
5552
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
5653
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
@@ -106,20 +103,31 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
106103
+ "refresh_token_value,"
107104
+ "refresh_token_issued_at,"
108105
+ "refresh_token_expires_at,"
109-
+ "refresh_token_metadata";
106+
+ "refresh_token_metadata,"
107+
+ "user_code_value,"
108+
+ "user_code_issued_at,"
109+
+ "user_code_expires_at,"
110+
+ "user_code_metadata,"
111+
+ "device_code_value,"
112+
+ "device_code_issued_at,"
113+
+ "device_code_expires_at,"
114+
+ "device_code_metadata";
110115
// @formatter:on
111116

112117
private static final String TABLE_NAME = "oauth2_authorization";
113118

114119
private static final String PK_FILTER = "id = ?";
115-
private static final String UNKNOWN_TOKEN_TYPE_FILTER = "state = ? OR authorization_code_value = ? OR " +
116-
"access_token_value = ? OR oidc_id_token_value = ? OR refresh_token_value = ?";
120+
private static final String UNKNOWN_TOKEN_TYPE_FILTER = "state = ? OR authorization_code_value = ? OR "
121+
+ "access_token_value = ? OR oidc_id_token_value = ? OR refresh_token_value = ? OR "
122+
+ "user_code_value = ? OR device_code_value = ?";
117123

118124
private static final String STATE_FILTER = "state = ?";
119125
private static final String AUTHORIZATION_CODE_FILTER = "authorization_code_value = ?";
120126
private static final String ACCESS_TOKEN_FILTER = "access_token_value = ?";
121127
private static final String ID_TOKEN_FILTER = "oidc_id_token_value = ?";
122128
private static final String REFRESH_TOKEN_FILTER = "refresh_token_value = ?";
129+
private static final String USER_CODE_FILTER = "user_code_value = ?";
130+
private static final String DEVICE_CODE_FILTER = "device_code_value = ?";
123131

124132
// @formatter:off
125133
private static final String LOAD_AUTHORIZATION_SQL = "SELECT " + COLUMN_NAMES
@@ -129,7 +137,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
129137

130138
// @formatter:off
131139
private static final String SAVE_AUTHORIZATION_SQL = "INSERT INTO " + TABLE_NAME
132-
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
140+
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
133141
// @formatter:on
134142

135143
// @formatter:off
@@ -138,7 +146,9 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
138146
+ " authorization_code_value = ?, authorization_code_issued_at = ?, authorization_code_expires_at = ?, authorization_code_metadata = ?,"
139147
+ " access_token_value = ?, access_token_issued_at = ?, access_token_expires_at = ?, access_token_metadata = ?, access_token_type = ?, access_token_scopes = ?,"
140148
+ " oidc_id_token_value = ?, oidc_id_token_issued_at = ?, oidc_id_token_expires_at = ?, oidc_id_token_metadata = ?,"
141-
+ " refresh_token_value = ?, refresh_token_issued_at = ?, refresh_token_expires_at = ?, refresh_token_metadata = ?"
149+
+ " refresh_token_value = ?, refresh_token_issued_at = ?, refresh_token_expires_at = ?, refresh_token_metadata = ?,"
150+
+ " user_code_value = ?, user_code_issued_at = ?, user_code_expires_at = ?, user_code_metadata = ?,"
151+
+ " device_code_value = ?, device_code_issued_at = ?, device_code_expires_at = ?, device_code_metadata = ?"
142152
+ " WHERE " + PK_FILTER;
143153
// @formatter:on
144154

@@ -244,6 +254,8 @@ public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType t
244254
parameters.add(mapToSqlParameter("access_token_value", token));
245255
parameters.add(mapToSqlParameter("oidc_id_token_value", token));
246256
parameters.add(mapToSqlParameter("refresh_token_value", token));
257+
parameters.add(mapToSqlParameter("user_code_value", token));
258+
parameters.add(mapToSqlParameter("device_code_value", token));
247259
return findBy(UNKNOWN_TOKEN_TYPE_FILTER, parameters);
248260
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
249261
parameters.add(new SqlParameterValue(Types.VARCHAR, token));
@@ -260,6 +272,12 @@ public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType t
260272
} else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
261273
parameters.add(mapToSqlParameter("refresh_token_value", token));
262274
return findBy(REFRESH_TOKEN_FILTER, parameters);
275+
} else if (OAuth2TokenType.USER_CODE.equals(tokenType)) {
276+
parameters.add(mapToSqlParameter("user_code_value", token));
277+
return findBy(USER_CODE_FILTER, parameters);
278+
} else if (OAuth2TokenType.DEVICE_CODE.equals(tokenType)) {
279+
parameters.add(mapToSqlParameter("device_code_value", token));
280+
return findBy(DEVICE_CODE_FILTER, parameters);
263281
}
264282
return null;
265283
}
@@ -425,6 +443,35 @@ public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException
425443
refreshTokenValue, tokenIssuedAt, tokenExpiresAt);
426444
builder.token(refreshToken, (metadata) -> metadata.putAll(refreshTokenMetadata));
427445
}
446+
447+
String userCodeValue = getLobValue(rs, "user_code_value");
448+
if (StringUtils.hasText(userCodeValue)) {
449+
tokenIssuedAt = rs.getTimestamp("user_code_issued_at").toInstant();
450+
tokenExpiresAt = null;
451+
Timestamp userCodeExpiresAt = rs.getTimestamp("user_code_expires_at");
452+
if (userCodeExpiresAt != null) {
453+
tokenExpiresAt = userCodeExpiresAt.toInstant();
454+
}
455+
Map<String, Object> userCodeMetadata = parseMap(getLobValue(rs, "user_code_metadata"));
456+
457+
OAuth2UserCode userCode = new OAuth2UserCode(userCodeValue, tokenIssuedAt, tokenExpiresAt);
458+
builder.token(userCode, (metadata) -> metadata.putAll(userCodeMetadata));
459+
}
460+
461+
String deviceCodeValue = getLobValue(rs, "device_code_value");
462+
if (StringUtils.hasText(deviceCodeValue)) {
463+
tokenIssuedAt = rs.getTimestamp("device_code_issued_at").toInstant();
464+
tokenExpiresAt = null;
465+
Timestamp deviceCodeExpiresAt = rs.getTimestamp("device_code_expires_at");
466+
if (deviceCodeExpiresAt != null) {
467+
tokenExpiresAt = deviceCodeExpiresAt.toInstant();
468+
}
469+
Map<String, Object> deviceCodeMetadata = parseMap(getLobValue(rs, "device_code_metadata"));
470+
471+
OAuth2DeviceCode deviceCode = new OAuth2DeviceCode(deviceCodeValue, tokenIssuedAt, tokenExpiresAt);
472+
builder.token(deviceCode, (metadata) -> metadata.putAll(deviceCodeMetadata));
473+
}
474+
428475
return builder.build();
429476
}
430477

@@ -545,6 +592,17 @@ public List<SqlParameterValue> apply(OAuth2Authorization authorization) {
545592
List<SqlParameterValue> refreshTokenSqlParameters = toSqlParameterList(
546593
"refresh_token_value", "refresh_token_metadata", refreshToken);
547594
parameters.addAll(refreshTokenSqlParameters);
595+
596+
OAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);
597+
List<SqlParameterValue> userCodeSqlParameters = toSqlParameterList(
598+
"user_code_value", "user_code_metadata", userCode);
599+
parameters.addAll(userCodeSqlParameters);
600+
601+
OAuth2Authorization.Token<OAuth2DeviceCode> deviceCode = authorization.getToken(OAuth2DeviceCode.class);
602+
List<SqlParameterValue> deviceCodeSqlParameters = toSqlParameterList(
603+
"device_code_value", "device_code_metadata", deviceCode);
604+
parameters.addAll(deviceCodeSqlParameters);
605+
548606
return parameters;
549607
}
550608

@@ -670,6 +728,14 @@ private static void initColumnMetadata(JdbcOperations jdbcOperations) {
670728
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
671729
columnMetadata = getColumnMetadata(jdbcOperations, "refresh_token_metadata", Types.BLOB);
672730
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
731+
columnMetadata = getColumnMetadata(jdbcOperations, "user_code_value", Types.BLOB);
732+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
733+
columnMetadata = getColumnMetadata(jdbcOperations, "user_code_metadata", Types.BLOB);
734+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
735+
columnMetadata = getColumnMetadata(jdbcOperations, "device_code_value", Types.BLOB);
736+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
737+
columnMetadata = getColumnMetadata(jdbcOperations, "device_code_metadata", Types.BLOB);
738+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
673739
}
674740

675741
private static ColumnMetadata getColumnMetadata(JdbcOperations jdbcOperations, String columnName, int defaultDataType) {

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2Authorization.java

+45-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@
2727
import java.util.function.Consumer;
2828

2929
import org.springframework.lang.Nullable;
30-
import org.springframework.security.oauth2.core.AuthorizationGrantType;
31-
import org.springframework.security.oauth2.core.OAuth2AccessToken;
32-
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
33-
import org.springframework.security.oauth2.core.OAuth2Token;
30+
import org.springframework.security.oauth2.core.*;
3431
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
3532
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
3633
import org.springframework.util.Assert;
@@ -50,6 +47,8 @@
5047
* @see OAuth2Token
5148
* @see OAuth2AccessToken
5249
* @see OAuth2RefreshToken
50+
* @see OAuth2UserCode
51+
* @see OAuth2DeviceCode
5352
*/
5453
public class OAuth2Authorization implements Serializable {
5554
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
@@ -129,6 +128,28 @@ public Token<OAuth2RefreshToken> getRefreshToken() {
129128
return getToken(OAuth2RefreshToken.class);
130129
}
131130

131+
/**
132+
* Returns the {@link Token} of type {@link OAuth2UserCode}.
133+
*
134+
* @return the {@link Token} of type {@link OAuth2UserCode}, or {@code null} if not
135+
* available
136+
*/
137+
@Nullable
138+
public Token<OAuth2UserCode> getUserCode() {
139+
return getToken(OAuth2UserCode.class);
140+
}
141+
142+
/**
143+
* Returns the {@link Token} of type {@link OAuth2DeviceCode}.
144+
*
145+
* @return the {@link Token} of type {@link OAuth2DeviceCode}, or {@code null} if not
146+
* available
147+
*/
148+
@Nullable
149+
public Token<OAuth2DeviceCode> getDeviceCode() {
150+
return getToken(OAuth2DeviceCode.class);
151+
}
152+
132153
/**
133154
* Returns the {@link Token} of type {@code tokenType}.
134155
*
@@ -460,6 +481,26 @@ public Builder refreshToken(OAuth2RefreshToken refreshToken) {
460481
return token(refreshToken);
461482
}
462483

484+
/**
485+
* Sets the {@link OAuth2UserCode user token}.
486+
*
487+
* @param userCode the {@link OAuth2UserCode}
488+
* @return the {@link Builder}
489+
*/
490+
public Builder userCode(OAuth2UserCode userCode) {
491+
return token(userCode);
492+
}
493+
494+
/**
495+
* Sets the {@link OAuth2DeviceCode device token}.
496+
*
497+
* @param deviceCode the {@link OAuth2DeviceCode}
498+
* @return the {@link Builder}
499+
*/
500+
public Builder deviceCode(OAuth2DeviceCode deviceCode) {
501+
return token(deviceCode);
502+
}
503+
463504
/**
464505
* Sets the {@link OAuth2Token token}.
465506
*

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2TokenType.java

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public final class OAuth2TokenType implements Serializable {
3131
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
3232
public static final OAuth2TokenType ACCESS_TOKEN = new OAuth2TokenType("access_token");
3333
public static final OAuth2TokenType REFRESH_TOKEN = new OAuth2TokenType("refresh_token");
34+
public static final OAuth2TokenType USER_CODE = new OAuth2TokenType("user_code");
35+
public static final OAuth2TokenType DEVICE_CODE = new OAuth2TokenType("device_code");
3436
private final String value;
3537

3638
/**

oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql

+8
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,13 @@ CREATE TABLE oauth2_authorization (
2929
refresh_token_issued_at timestamp DEFAULT NULL,
3030
refresh_token_expires_at timestamp DEFAULT NULL,
3131
refresh_token_metadata blob DEFAULT NULL,
32+
user_code_value blob DEFAULT NULL,
33+
user_code_issued_at timestamp DEFAULT NULL,
34+
user_code_expires_at timestamp DEFAULT NULL,
35+
user_code_metadata blob DEFAULT NULL,
36+
device_code_value blob DEFAULT NULL,
37+
device_code_issued_at timestamp DEFAULT NULL,
38+
device_code_expires_at timestamp DEFAULT NULL,
39+
device_code_metadata blob DEFAULT NULL,
3240
PRIMARY KEY (id)
3341
);

samples/device-grant-authorizationserver/src/main/java/sample/config/SecurityConfig.java

+26
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
import org.springframework.context.annotation.Bean;
3131
import org.springframework.context.annotation.Configuration;
3232
import org.springframework.core.annotation.Order;
33+
import org.springframework.jdbc.core.JdbcTemplate;
34+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
35+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
36+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
3337
import org.springframework.security.config.Customizer;
3438
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3539
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -41,6 +45,8 @@
4145
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
4246
import org.springframework.security.oauth2.core.oidc.OidcScopes;
4347
import org.springframework.security.oauth2.jwt.JwtDecoder;
48+
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
49+
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
4450
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
4551
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
4652
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -131,6 +137,12 @@ public RegisteredClientRepository registeredClientRepository() {
131137
return new InMemoryRegisteredClientRepository(registeredClient);
132138
}
133139

140+
@Bean
141+
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
142+
RegisteredClientRepository registeredClientRepository) {
143+
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
144+
}
145+
134146
@Bean
135147
public JWKSource<SecurityContext> jwkSource() {
136148
KeyPair keyPair = generateRsaKey();
@@ -167,4 +179,18 @@ public AuthorizationServerSettings authorizationServerSettings() {
167179
return AuthorizationServerSettings.builder().build();
168180
}
169181

182+
@Bean
183+
public EmbeddedDatabase embeddedDatabase() {
184+
// @formatter:off
185+
return new EmbeddedDatabaseBuilder()
186+
.generateUniqueName(true)
187+
.setType(EmbeddedDatabaseType.H2)
188+
.setScriptEncoding("UTF-8")
189+
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
190+
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
191+
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
192+
.build();
193+
// @formatter:on
194+
}
195+
170196
}

0 commit comments

Comments
 (0)