Instantly share code, notes, and snippets.
Created
October 15, 2022 19:05
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save g2px1/fdd5a681878b8555ae3830dcc370bf29 to your computer and use it in GitHub Desktop.
JpaOAuth2AuthorizationService for Spring Authorization Server
This file contains 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
/* | |
* 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. | |
*/ | |
import java.time.Instant; | |
import javax.persistence.Column; | |
import javax.persistence.Entity; | |
import javax.persistence.Id; | |
/** | |
* @author Steve Riesenberg | |
*/ | |
@Entity | |
public class Authorization { | |
@Id | |
private String id; | |
private String registeredClientId; | |
private String principalName; | |
private String authorizationGrantType; | |
@Column(length = 4000) | |
private String attributes; | |
@Column(length = 500) | |
private String state; | |
@Column(length = 4000) | |
private String authorizationCode; | |
private Instant authorizationCodeIssuedAt; | |
private Instant authorizationCodeExpiresAt; | |
private String authorizationCodeMetadata; | |
@Column(length = 4000) | |
private String accessToken; | |
private Instant accessTokenIssuedAt; | |
private Instant accessTokenExpiresAt; | |
@Column(length = 2000) | |
private String accessTokenMetadata; | |
@Column(length = 1000) | |
private String accessTokenScopes; | |
@Column(length = 4000) | |
private String refreshToken; | |
private Instant refreshTokenIssuedAt; | |
private Instant refreshTokenExpiresAt; | |
@Column(length = 2000) | |
private String refreshTokenMetadata; | |
@Column(length = 4000) | |
private String idToken; | |
private Instant idTokenIssuedAt; | |
private Instant idTokenExpiresAt; | |
@Column(length = 2000) | |
private String idTokenMetadata; | |
@Column(length = 2000) | |
private String idTokenClaims; | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public String getRegisteredClientId() { | |
return registeredClientId; | |
} | |
public void setRegisteredClientId(String registeredClientId) { | |
this.registeredClientId = registeredClientId; | |
} | |
public String getPrincipalName() { | |
return principalName; | |
} | |
public void setPrincipalName(String principalName) { | |
this.principalName = principalName; | |
} | |
public String getAuthorizationGrantType() { | |
return authorizationGrantType; | |
} | |
public void setAuthorizationGrantType(String authorizationGrantType) { | |
this.authorizationGrantType = authorizationGrantType; | |
} | |
public String getAttributes() { | |
return attributes; | |
} | |
public void setAttributes(String attributes) { | |
this.attributes = attributes; | |
} | |
public String getState() { | |
return state; | |
} | |
public void setState(String state) { | |
this.state = state; | |
} | |
public String getAuthorizationCode() { | |
return authorizationCode; | |
} | |
public void setAuthorizationCode(String authorizationCode) { | |
this.authorizationCode = authorizationCode; | |
} | |
public Instant getAuthorizationCodeIssuedAt() { | |
return authorizationCodeIssuedAt; | |
} | |
public void setAuthorizationCodeIssuedAt(Instant authorizationCodeIssuedAt) { | |
this.authorizationCodeIssuedAt = authorizationCodeIssuedAt; | |
} | |
public Instant getAuthorizationCodeExpiresAt() { | |
return authorizationCodeExpiresAt; | |
} | |
public void setAuthorizationCodeExpiresAt(Instant authorizationCodeExpiresAt) { | |
this.authorizationCodeExpiresAt = authorizationCodeExpiresAt; | |
} | |
public String getAuthorizationCodeMetadata() { | |
return authorizationCodeMetadata; | |
} | |
public void setAuthorizationCodeMetadata(String authorizationCodeMetadata) { | |
this.authorizationCodeMetadata = authorizationCodeMetadata; | |
} | |
public String getAccessToken() { | |
return accessToken; | |
} | |
public void setAccessToken(String accessToken) { | |
this.accessToken = accessToken; | |
} | |
public Instant getAccessTokenIssuedAt() { | |
return accessTokenIssuedAt; | |
} | |
public void setAccessTokenIssuedAt(Instant accessTokenIssuedAt) { | |
this.accessTokenIssuedAt = accessTokenIssuedAt; | |
} | |
public Instant getAccessTokenExpiresAt() { | |
return accessTokenExpiresAt; | |
} | |
public void setAccessTokenExpiresAt(Instant accessTokenExpiresAt) { | |
this.accessTokenExpiresAt = accessTokenExpiresAt; | |
} | |
public String getAccessTokenMetadata() { | |
return accessTokenMetadata; | |
} | |
public void setAccessTokenMetadata(String accessTokenMetadata) { | |
this.accessTokenMetadata = accessTokenMetadata; | |
} | |
public String getAccessTokenScopes() { | |
return accessTokenScopes; | |
} | |
public void setAccessTokenScopes(String accessTokenScopes) { | |
this.accessTokenScopes = accessTokenScopes; | |
} | |
public String getRefreshToken() { | |
return refreshToken; | |
} | |
public void setRefreshToken(String refreshToken) { | |
this.refreshToken = refreshToken; | |
} | |
public Instant getRefreshTokenIssuedAt() { | |
return refreshTokenIssuedAt; | |
} | |
public void setRefreshTokenIssuedAt(Instant refreshTokenIssuedAt) { | |
this.refreshTokenIssuedAt = refreshTokenIssuedAt; | |
} | |
public Instant getRefreshTokenExpiresAt() { | |
return refreshTokenExpiresAt; | |
} | |
public void setRefreshTokenExpiresAt(Instant refreshTokenExpiresAt) { | |
this.refreshTokenExpiresAt = refreshTokenExpiresAt; | |
} | |
public String getRefreshTokenMetadata() { | |
return refreshTokenMetadata; | |
} | |
public void setRefreshTokenMetadata(String refreshTokenMetadata) { | |
this.refreshTokenMetadata = refreshTokenMetadata; | |
} | |
public String getIdToken() { | |
return idToken; | |
} | |
public void setIdToken(String idToken) { | |
this.idToken = idToken; | |
} | |
public Instant getIdTokenIssuedAt() { | |
return idTokenIssuedAt; | |
} | |
public void setIdTokenIssuedAt(Instant idTokenIssuedAt) { | |
this.idTokenIssuedAt = idTokenIssuedAt; | |
} | |
public Instant getIdTokenExpiresAt() { | |
return idTokenExpiresAt; | |
} | |
public void setIdTokenExpiresAt(Instant idTokenExpiresAt) { | |
this.idTokenExpiresAt = idTokenExpiresAt; | |
} | |
public String getIdTokenMetadata() { | |
return idTokenMetadata; | |
} | |
public void setIdTokenMetadata(String idTokenMetadata) { | |
this.idTokenMetadata = idTokenMetadata; | |
} | |
public String getIdTokenClaims() { | |
return idTokenClaims; | |
} | |
public void setIdTokenClaims(String idTokenClaims) { | |
this.idTokenClaims = idTokenClaims; | |
} | |
} |
This file contains 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
/* | |
* 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. | |
*/ | |
import java.util.Optional; | |
import org.apache.tomcat.util.http.parser.Authorization; | |
import org.springframework.data.jpa.repository.JpaRepository; | |
import org.springframework.data.jpa.repository.Query; | |
import org.springframework.data.repository.query.Param; | |
import org.springframework.stereotype.Repository; | |
/** | |
* @author Steve Riesenberg | |
*/ | |
@Repository | |
public interface AuthorizationRepository extends JpaRepository<Authorization, String> { | |
Optional<Authorization> findByState(String state); | |
Optional<Authorization> findByAuthorizationCode(String authorizationCode); | |
Optional<Authorization> findByAccessToken(String accessToken); | |
Optional<Authorization> findByRefreshToken(String refreshToken); | |
@Query("select a from Authorization a where a.state = :token" + | |
" or a.authorizationCode = :token" + | |
" or a.accessToken = :token" + | |
" or a.refreshToken = :token" | |
) | |
Optional<sample.jpa.Authorization> findByStateOrAuthorizationCodeOrAccessTokenOrRefreshToken(@Param("token") String token); | |
} |
This file contains 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
/* | |
* 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. | |
*/ | |
import java.time.Instant; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Optional; | |
import java.util.function.Consumer; | |
import com.fasterxml.jackson.core.type.TypeReference; | |
import com.fasterxml.jackson.databind.Module; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import org.apache.tomcat.util.http.parser.Authorization; | |
import sample.jpa.AuthorizationRepository; | |
import org.springframework.dao.DataRetrievalFailureException; | |
import org.springframework.security.jackson2.SecurityJackson2Modules; | |
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |
import org.springframework.security.oauth2.core.OAuth2AccessToken; | |
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; | |
import org.springframework.security.oauth2.core.OAuth2RefreshToken; | |
import org.springframework.security.oauth2.core.OAuth2Token; | |
import org.springframework.security.oauth2.core.OAuth2TokenType; | |
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |
import org.springframework.security.oauth2.core.oidc.OidcIdToken; | |
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; | |
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; | |
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; | |
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; | |
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module; | |
import org.springframework.stereotype.Component; | |
import org.springframework.util.Assert; | |
import org.springframework.util.StringUtils; | |
/** | |
* @author Steve Riesenberg | |
*/ | |
@Component | |
public class JpaOAuth2AuthorizationService implements OAuth2AuthorizationService { | |
private final AuthorizationRepository authorizationRepository; | |
private final RegisteredClientRepository registeredClientRepository; | |
private final ObjectMapper objectMapper = new ObjectMapper(); | |
public JpaOAuth2AuthorizationService(AuthorizationRepository authorizationRepository, RegisteredClientRepository registeredClientRepository) { | |
Assert.notNull(authorizationRepository, "authorizationRepository cannot be null"); | |
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); | |
this.authorizationRepository = authorizationRepository; | |
this.registeredClientRepository = registeredClientRepository; | |
ClassLoader classLoader = JpaOAuth2AuthorizationService.class.getClassLoader(); | |
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader); | |
this.objectMapper.registerModules(securityModules); | |
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module()); | |
} | |
@Override | |
public void save(OAuth2Authorization authorization) { | |
Assert.notNull(authorization, "authorization cannot be null"); | |
this.authorizationRepository.save(toEntity(authorization)); | |
} | |
@Override | |
public void remove(OAuth2Authorization authorization) { | |
Assert.notNull(authorization, "authorization cannot be null"); | |
this.authorizationRepository.deleteById(authorization.getId()); | |
} | |
@Override | |
public OAuth2Authorization findById(String id) { | |
Assert.hasText(id, "id cannot be empty"); | |
return this.authorizationRepository.findById(id).map(this::toObject).orElse(null); | |
} | |
@Override | |
public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) { | |
Assert.hasText(token, "token cannot be empty"); | |
Optional<Authorization> result; | |
if (tokenType == null) { | |
result = this.authorizationRepository.findByStateOrAuthorizationCodeOrAccessTokenOrRefreshToken(token); | |
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) { | |
result = this.authorizationRepository.findByState(token); | |
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) { | |
result = this.authorizationRepository.findByAuthorizationCode(token); | |
} else if (OAuth2ParameterNames.ACCESS_TOKEN.equals(tokenType.getValue())) { | |
result = this.authorizationRepository.findByAccessToken(token); | |
} else if (OAuth2ParameterNames.REFRESH_TOKEN.equals(tokenType.getValue())) { | |
result = this.authorizationRepository.findByRefreshToken(token); | |
} else { | |
result = Optional.empty(); | |
} | |
return result.map(this::toObject).orElse(null); | |
} | |
private OAuth2Authorization toObject(Authorization entity) { | |
RegisteredClient registeredClient = this.registeredClientRepository.findById(entity.getRegisteredClientId()); | |
if (registeredClient == null) { | |
throw new DataRetrievalFailureException( | |
"The RegisteredClient with id '" + entity.getRegisteredClientId() + "' was not found in the RegisteredClientRepository."); | |
} | |
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient) | |
.id(entity.getId()) | |
.principalName(entity.getPrincipalName()) | |
.authorizationGrantType(resolveAuthorizationGrantType(entity.getAuthorizationGrantType())) | |
.attributes(attributes -> attributes.putAll(parseMap(entity.getAttributes()))); | |
if (entity.getState() != null) { | |
builder.attribute(OAuth2ParameterNames.STATE, entity.getState()); | |
} | |
if (entity.getAuthorizationCode() != null) { | |
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode( | |
entity.getAuthorizationCode(), | |
entity.getAuthorizationCodeIssuedAt(), | |
entity.getAuthorizationCodeExpiresAt()); | |
builder.token(authorizationCode, metadata -> metadata.putAll(parseMap(entity.getAuthorizationCodeMetadata()))); | |
} | |
if (entity.getAccessToken() != null) { | |
OAuth2AccessToken accessToken = new OAuth2AccessToken( | |
OAuth2AccessToken.TokenType.BEARER, | |
entity.getAccessToken(), | |
entity.getAccessTokenIssuedAt(), | |
entity.getAccessTokenExpiresAt()); | |
builder.token(accessToken, metadata -> metadata.putAll(parseMap(entity.getAccessTokenMetadata()))); | |
} | |
if (entity.getRefreshToken() != null) { | |
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken( | |
entity.getRefreshToken(), | |
entity.getRefreshTokenIssuedAt(), | |
entity.getRefreshTokenExpiresAt()); | |
builder.token(refreshToken, metadata -> metadata.putAll(parseMap(entity.getRefreshTokenMetadata()))); | |
} | |
if (entity.getIdToken() != null) { | |
OidcIdToken idToken = new OidcIdToken( | |
entity.getIdToken(), | |
entity.getIdTokenIssuedAt(), | |
entity.getIdTokenExpiresAt(), | |
parseMap(entity.getIdTokenClaims())); | |
builder.token(idToken, metadata -> metadata.putAll(parseMap(entity.getIdTokenMetadata()))); | |
} | |
return builder.build(); | |
} | |
private Authorization toEntity(OAuth2Authorization authorization) { | |
Authorization entity = new Authorization(); | |
entity.setId(authorization.getId()); | |
entity.setRegisteredClientId(authorization.getRegisteredClientId()); | |
entity.setPrincipalName(authorization.getPrincipalName()); | |
entity.setAuthorizationGrantType(authorization.getAuthorizationGrantType().getValue()); | |
entity.setAttributes(writeMap(authorization.getAttributes())); | |
entity.setState(authorization.getAttribute(OAuth2ParameterNames.STATE)); | |
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = | |
authorization.getToken(OAuth2AuthorizationCode.class); | |
setTokenValues( | |
authorizationCode, | |
entity::setAuthorizationCode, | |
entity::setAuthorizationCodeIssuedAt, | |
entity::setAuthorizationCodeExpiresAt, | |
entity::setAuthorizationCodeMetadata | |
); | |
OAuth2Authorization.Token<OAuth2AccessToken> accessToken = | |
authorization.getToken(OAuth2AccessToken.class); | |
setTokenValues( | |
accessToken, | |
entity::setAccessToken, | |
entity::setAccessTokenIssuedAt, | |
entity::setAccessTokenExpiresAt, | |
entity::setAccessTokenMetadata | |
); | |
if (accessToken != null && accessToken.getToken().getScopes() != null) { | |
entity.setAccessTokenScopes(StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), ",")); | |
} | |
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = | |
authorization.getToken(OAuth2RefreshToken.class); | |
setTokenValues( | |
refreshToken, | |
entity::setRefreshToken, | |
entity::setRefreshTokenIssuedAt, | |
entity::setRefreshTokenExpiresAt, | |
entity::setRefreshTokenMetadata | |
); | |
OAuth2Authorization.Token<OidcIdToken> oidcIdToken = | |
authorization.getToken(OidcIdToken.class); | |
setTokenValues( | |
oidcIdToken, | |
entity::setIdToken, | |
entity::setIdTokenIssuedAt, | |
entity::setIdTokenExpiresAt, | |
entity::setIdTokenMetadata | |
); | |
if (oidcIdToken != null) { | |
entity.setIdTokenClaims(writeMap(oidcIdToken.getClaims())); | |
} | |
return entity; | |
} | |
private void setTokenValues( | |
OAuth2Authorization.Token<?> token, | |
Consumer<String> tokenValueConsumer, | |
Consumer<Instant> issuedAtConsumer, | |
Consumer<Instant> expiresAtConsumer, | |
Consumer<String> metadataConsumer) { | |
if (token != null) { | |
OAuth2Token oAuth2Token = token.getToken(); | |
tokenValueConsumer.accept(oAuth2Token.getTokenValue()); | |
issuedAtConsumer.accept(oAuth2Token.getIssuedAt()); | |
expiresAtConsumer.accept(oAuth2Token.getExpiresAt()); | |
metadataConsumer.accept(writeMap(token.getMetadata())); | |
} | |
} | |
private Map<String, Object> parseMap(String data) { | |
try { | |
return this.objectMapper.readValue(data, new TypeReference<>() { | |
}); | |
} catch (Exception ex) { | |
throw new IllegalArgumentException(ex.getMessage(), ex); | |
} | |
} | |
private String writeMap(Map<String, Object> metadata) { | |
try { | |
return this.objectMapper.writeValueAsString(metadata); | |
} catch (Exception ex) { | |
throw new IllegalArgumentException(ex.getMessage(), ex); | |
} | |
} | |
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) { | |
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) { | |
return AuthorizationGrantType.AUTHORIZATION_CODE; | |
} else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equals(authorizationGrantType)) { | |
return AuthorizationGrantType.CLIENT_CREDENTIALS; | |
} else if (AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(authorizationGrantType)) { | |
return AuthorizationGrantType.REFRESH_TOKEN; | |
} | |
return new AuthorizationGrantType(authorizationGrantType); // Custom authorization grant type | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment