-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Provide JDBC implementation of OAuth2AuthorizationConsentService #314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
249 changes: 249 additions & 0 deletions
249
...framework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationConsentService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
/* | ||
* Copyright 2020-2021 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.springframework.security.oauth2.server.authorization; | ||
|
||
import java.sql.ResultSet; | ||
import java.sql.SQLException; | ||
import java.sql.Types; | ||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.function.Function; | ||
|
||
import org.springframework.dao.DataRetrievalFailureException; | ||
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; | ||
import org.springframework.jdbc.core.JdbcOperations; | ||
import org.springframework.jdbc.core.PreparedStatementSetter; | ||
import org.springframework.jdbc.core.RowMapper; | ||
import org.springframework.jdbc.core.SqlParameterValue; | ||
import org.springframework.lang.Nullable; | ||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; | ||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; | ||
import org.springframework.util.Assert; | ||
import org.springframework.util.StringUtils; | ||
|
||
/** | ||
* A JDBC implementation of an {@link OAuth2AuthorizationConsentService} that uses a | ||
* <p> | ||
* {@link JdbcOperations} for {@link OAuth2AuthorizationConsent} persistence. | ||
* | ||
* <p> | ||
* <b>NOTE:</b> This {@code OAuth2AuthorizationConsentService} depends on the table definition | ||
* described in | ||
* "classpath:org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql" and | ||
* therefore MUST be defined in the database schema. | ||
* | ||
* @author Ovidiu Popa | ||
* @see OAuth2AuthorizationConsentService | ||
* @see OAuth2AuthorizationConsent | ||
* @see JdbcOperations | ||
* @see RowMapper | ||
* @since 0.1.2 | ||
*/ | ||
public final class JdbcOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { | ||
|
||
// @formatter:off | ||
private static final String COLUMN_NAMES = "registered_client_id, " | ||
+ "principal_name, " | ||
+ "authorities"; | ||
// @formatter:on | ||
|
||
private static final String TABLE_NAME = "oauth2_authorization_consent"; | ||
|
||
private static final String PK_FILTER = "registered_client_id = ? AND principal_name = ?"; | ||
|
||
// @formatter:off | ||
private static final String LOAD_AUTHORIZATION_CONSENT_SQL = "SELECT " + COLUMN_NAMES | ||
+ " FROM " + TABLE_NAME | ||
+ " WHERE " + PK_FILTER; | ||
// @formatter:on | ||
|
||
// @formatter:off | ||
private static final String SAVE_AUTHORIZATION_CONSENT_SQL = "INSERT INTO " + TABLE_NAME | ||
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)"; | ||
// @formatter:on | ||
|
||
// @formatter:off | ||
private static final String UPDATE_AUTHORIZATION_CONSENT_SQL = "UPDATE " + TABLE_NAME | ||
+ " SET authorities = ?" | ||
+ " WHERE " + PK_FILTER; | ||
// @formatter:on | ||
|
||
private static final String REMOVE_AUTHORIZATION_CONSENT_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER; | ||
|
||
private final JdbcOperations jdbcOperations; | ||
private RowMapper<OAuth2AuthorizationConsent> authorizationConsentRowMapper; | ||
private Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> authorizationConsentParametersMapper; | ||
|
||
/** | ||
* Constructs a {@code JdbcOAuth2AuthorizationConsentService} using the provided parameters. | ||
* | ||
* @param jdbcOperations the JDBC operations | ||
* @param registeredClientRepository the registered client repository | ||
*/ | ||
public JdbcOAuth2AuthorizationConsentService(JdbcOperations jdbcOperations, | ||
RegisteredClientRepository registeredClientRepository) { | ||
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null"); | ||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); | ||
this.jdbcOperations = jdbcOperations; | ||
this.authorizationConsentRowMapper = new OAuth2AuthorizationConsentRowMapper(registeredClientRepository); | ||
this.authorizationConsentParametersMapper = new OAuth2AuthorizationConsentParametersMapper(); | ||
} | ||
|
||
@Override | ||
public void save(OAuth2AuthorizationConsent authorizationConsent) { | ||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); | ||
|
||
OAuth2AuthorizationConsent existingAuthorizationConsent = | ||
findById(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); | ||
|
||
if (existingAuthorizationConsent == null) { | ||
insertAuthorizationConsent(authorizationConsent); | ||
} else { | ||
updateAuthorizationConsent(authorizationConsent); | ||
} | ||
} | ||
|
||
private void updateAuthorizationConsent(OAuth2AuthorizationConsent authorizationConsent) { | ||
List<SqlParameterValue> parameters = this.authorizationConsentParametersMapper.apply(authorizationConsent); | ||
SqlParameterValue registeredClientId = parameters.remove(0); | ||
SqlParameterValue principalName = parameters.remove(0); | ||
parameters.add(registeredClientId); | ||
parameters.add(principalName); | ||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); | ||
this.jdbcOperations.update(UPDATE_AUTHORIZATION_CONSENT_SQL, pss); | ||
} | ||
|
||
private void insertAuthorizationConsent(OAuth2AuthorizationConsent authorizationConsent) { | ||
List<SqlParameterValue> parameters = this.authorizationConsentParametersMapper.apply(authorizationConsent); | ||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray()); | ||
this.jdbcOperations.update(SAVE_AUTHORIZATION_CONSENT_SQL, pss); | ||
} | ||
|
||
@Override | ||
public void remove(OAuth2AuthorizationConsent authorizationConsent) { | ||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); | ||
SqlParameterValue[] parameters = new SqlParameterValue[]{ | ||
new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId()), | ||
new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName()) | ||
}; | ||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); | ||
this.jdbcOperations.update(REMOVE_AUTHORIZATION_CONSENT_SQL, pss); | ||
} | ||
|
||
@Override | ||
@Nullable | ||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { | ||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); | ||
Assert.hasText(principalName, "principalName cannot be empty"); | ||
SqlParameterValue[] parameters = new SqlParameterValue[]{ | ||
new SqlParameterValue(Types.VARCHAR, registeredClientId), | ||
new SqlParameterValue(Types.VARCHAR, principalName)}; | ||
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters); | ||
List<OAuth2AuthorizationConsent> result = this.jdbcOperations.query(LOAD_AUTHORIZATION_CONSENT_SQL, pss, | ||
this.authorizationConsentRowMapper); | ||
return !result.isEmpty() ? result.get(0) : null; | ||
} | ||
|
||
/** | ||
* Sets the {@link RowMapper} used for mapping the current row in | ||
* {@code java.sql.ResultSet} to {@link OAuth2AuthorizationConsent}. The default is | ||
* {@link OAuth2AuthorizationConsentRowMapper}. | ||
* | ||
* @param authorizationConsentRowMapper the {@link RowMapper} used for mapping the current | ||
* row in {@code ResultSet} to {@link OAuth2AuthorizationConsent} | ||
*/ | ||
public void setAuthorizationConsentRowMapper(RowMapper<OAuth2AuthorizationConsent> authorizationConsentRowMapper) { | ||
Assert.notNull(authorizationConsentRowMapper, "authorizationConsentRowMapper cannot be null"); | ||
this.authorizationConsentRowMapper = authorizationConsentRowMapper; | ||
} | ||
|
||
/** | ||
* Sets the {@code Function} used for mapping {@link OAuth2AuthorizationConsent} to | ||
* a {@code List} of {@link SqlParameterValue}. The default is | ||
* {@link OAuth2AuthorizationConsentParametersMapper}. | ||
* | ||
* @param authorizationConsentParametersMapper the {@code Function} used for mapping | ||
* {@link OAuth2AuthorizationConsent} to a {@code List} of {@link SqlParameterValue} | ||
*/ | ||
public void setAuthorizationConsentParametersMapper( | ||
Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> authorizationConsentParametersMapper) { | ||
Assert.notNull(authorizationConsentParametersMapper, "authorizationConsentParametersMapper cannot be null"); | ||
this.authorizationConsentParametersMapper = authorizationConsentParametersMapper; | ||
} | ||
|
||
/** | ||
* The default {@link RowMapper} that maps the current row in | ||
* {@code ResultSet} to {@link OAuth2AuthorizationConsent}. | ||
*/ | ||
public static class OAuth2AuthorizationConsentRowMapper implements RowMapper<OAuth2AuthorizationConsent> { | ||
|
||
private final RegisteredClientRepository registeredClientRepository; | ||
|
||
public OAuth2AuthorizationConsentRowMapper(RegisteredClientRepository registeredClientRepository) { | ||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); | ||
this.registeredClientRepository = registeredClientRepository; | ||
} | ||
|
||
@Override | ||
public OAuth2AuthorizationConsent mapRow(ResultSet rs, int rowNum) throws SQLException { | ||
String registeredClientId = rs.getString("registered_client_id"); | ||
|
||
RegisteredClient registeredClient = this.registeredClientRepository | ||
.findById(registeredClientId); | ||
if (registeredClient == null) { | ||
throw new DataRetrievalFailureException( | ||
"The RegisteredClient with id '" + registeredClientId + "' it was not found in the RegisteredClientRepository."); | ||
} | ||
|
||
String principalName = rs.getString("principal_name"); | ||
|
||
OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(registeredClientId, principalName); | ||
String authorizationConsentAuthorities = rs.getString("authorities"); | ||
if (authorizationConsentAuthorities != null) { | ||
for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsentAuthorities)) { | ||
builder.authority(new SimpleGrantedAuthority(authority)); | ||
} | ||
} | ||
return builder.build(); | ||
} | ||
} | ||
|
||
/** | ||
* The default {@code Function} that maps {@link OAuth2AuthorizationConsent} to a | ||
* {@code List} of {@link SqlParameterValue}. | ||
*/ | ||
public static class OAuth2AuthorizationConsentParametersMapper implements Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> { | ||
|
||
@Override | ||
public List<SqlParameterValue> apply(OAuth2AuthorizationConsent authorizationConsent) { | ||
List<SqlParameterValue> parameters = new ArrayList<>(); | ||
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId())); | ||
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName())); | ||
|
||
Set<String> authorities = new HashSet<>(); | ||
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) { | ||
authorities.add(authority.getAuthority()); | ||
} | ||
parameters.add(new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToDelimitedString(authorities, ","))); | ||
sjohnr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return parameters; | ||
} | ||
} | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
...ingframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
CREATE TABLE oauth2_authorization_consent ( | ||
registered_client_id varchar(100) NOT NULL, | ||
principal_name varchar(200) NOT NULL, | ||
authorities varchar(1000) NOT NULL, | ||
PRIMARY KEY (registered_client_id, principal_name) | ||
); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.