JsonPathJwtConverter.java
/*
* Copyright 2020 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
*
* http://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.bremersee.spring.security.oauth2.server.resource.authentication;
import static java.util.Objects.requireNonNullElseGet;
import static org.springframework.util.ObjectUtils.isEmpty;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.bremersee.spring.security.core.NormalizedUser;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.oauth2.jwt.Jwt;
/**
* The json path jwt converter.
*
* @author Christian Bremer
*/
@Getter(AccessLevel.PROTECTED)
@ToString(callSuper = true)
@EqualsAndHashCode
public class JsonPathJwtConverter implements Converter<Jwt, AbstractAuthenticationToken> {
/**
* The json path to the username.
*/
private final String nameJsonPath;
/**
* The json path to the first name.
*/
private final String firstNameJsonPath;
/**
* The json path to the last name.
*/
private final String lastNameJsonPath;
/**
* The json path to the email.
*/
private final String emailJsonPath;
/**
* The json path to the roles.
*/
private final String rolesJsonPath;
/**
* Specifies whether the roles are represented as a json array or as a list separated by
* {@link #getRolesValueSeparator()}.
*/
private final boolean rolesValueList;
/**
* The roles separator to use if {@link #isRolesValueList()} is set to {@code false}.
*/
private final String rolesValueSeparator;
/**
* The authorities mapper.
*/
private final GrantedAuthoritiesMapper authoritiesMapper;
/**
* Instantiates a new Json path jwt converter.
*
* @param nameJsonPath the name json path
* @param firstNameJsonPath the first name json path
* @param lastNameJsonPath the last name json path
* @param emailJsonPath the email json path
* @param rolesJsonPath the roles json path
* @param rolesValueList the roles value list
* @param rolesValueSeparator the roles value separator
* @param authoritiesMapper the authorities mapper
*/
public JsonPathJwtConverter(
String nameJsonPath,
String firstNameJsonPath,
String lastNameJsonPath,
String emailJsonPath,
String rolesJsonPath,
boolean rolesValueList,
String rolesValueSeparator,
GrantedAuthoritiesMapper authoritiesMapper) {
this.nameJsonPath = nameJsonPath;
this.firstNameJsonPath = firstNameJsonPath;
this.lastNameJsonPath = lastNameJsonPath;
this.emailJsonPath = emailJsonPath;
this.rolesJsonPath = rolesJsonPath;
this.rolesValueList = rolesValueList;
this.rolesValueSeparator = rolesValueSeparator;
this.authoritiesMapper = requireNonNullElseGet(authoritiesMapper, SimpleAuthorityMapper::new);
}
@NonNull
@Override
public NormalizedJwtAuthenticationToken convert(@NonNull Jwt source) {
JsonPathJwtParser parser = new JsonPathJwtParser(source);
return new NormalizedJwtAuthenticationToken(
source,
new NormalizedUser(
getUsername(source, parser),
getFirstName(parser),
getLastName(parser),
getEmail(parser)),
getGrantedAuthorities(parser));
}
/**
* Gets granted authorities.
*
* @param parser the parser
* @return the granted authorities
*/
protected Collection<? extends GrantedAuthority> getGrantedAuthorities(JsonPathJwtParser parser) {
Stream<String> values = isRolesValueList()
? getAuthoritiesFromList(parser)
: getAuthoritiesFromValue(parser);
Set<GrantedAuthority> authorities = values.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
return authoritiesMapper.mapAuthorities(authorities);
}
/**
* Gets authorities from list.
*
* @param parser the parser
* @return the authorities from list
*/
protected Stream<String> getAuthoritiesFromList(JsonPathJwtParser parser) {
//noinspection unchecked
return Stream.ofNullable(getRolesJsonPath())
.map(path -> parser.read(path, List.class))
.filter(Objects::nonNull)
.map(list -> (List<String>) list)
.flatMap(Collection::stream);
}
/**
* Gets authorities from value.
*
* @param parser the parser
* @return the authorities from value
*/
protected Stream<String> getAuthoritiesFromValue(JsonPathJwtParser parser) {
return Stream.ofNullable(getRolesJsonPath())
.filter(path -> !isEmpty(getRolesValueSeparator()))
.map(path -> parser.read(path, String.class))
.filter(Objects::nonNull)
.map(value -> value.split(Pattern.quote(getRolesValueSeparator())))
.flatMap(Arrays::stream)
.map(String::valueOf);
}
/**
* Gets username.
*
* @param source the source
* @param parser the parser
* @return the username
*/
protected String getUsername(Jwt source, JsonPathJwtParser parser) {
return Optional.ofNullable(getNameJsonPath())
.filter(jsonPath -> !jsonPath.isBlank())
.map(jsonPath -> parser.read(jsonPath, String.class))
.orElseGet(source::getSubject);
}
/**
* Gets first name.
*
* @param parser the parser
* @return the first name
*/
protected String getFirstName(JsonPathJwtParser parser) {
return parser.read(getFirstNameJsonPath(), String.class);
}
/**
* Gets last name.
*
* @param parser the parser
* @return the last name
*/
protected String getLastName(JsonPathJwtParser parser) {
return parser.read(getLastNameJsonPath(), String.class);
}
/**
* Gets email.
*
* @param parser the parser
* @return the email
*/
protected String getEmail(JsonPathJwtParser parser) {
return parser.read(getEmailJsonPath(), String.class);
}
}