View Javadoc
1   /*
2    * Copyright 2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.spring.security.oauth2.server.resource.authentication;
18  
19  import static java.util.Objects.requireNonNullElseGet;
20  import static org.springframework.util.ObjectUtils.isEmpty;
21  
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.List;
25  import java.util.Objects;
26  import java.util.Optional;
27  import java.util.Set;
28  import java.util.regex.Pattern;
29  import java.util.stream.Collectors;
30  import java.util.stream.Stream;
31  import lombok.AccessLevel;
32  import lombok.EqualsAndHashCode;
33  import lombok.Getter;
34  import lombok.ToString;
35  import org.bremersee.spring.security.core.NormalizedUser;
36  import org.springframework.core.convert.converter.Converter;
37  import org.springframework.lang.NonNull;
38  import org.springframework.security.authentication.AbstractAuthenticationToken;
39  import org.springframework.security.core.GrantedAuthority;
40  import org.springframework.security.core.authority.SimpleGrantedAuthority;
41  import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
42  import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
43  import org.springframework.security.oauth2.jwt.Jwt;
44  
45  /**
46   * The json path jwt converter.
47   *
48   * @author Christian Bremer
49   */
50  @Getter(AccessLevel.PROTECTED)
51  @ToString(callSuper = true)
52  @EqualsAndHashCode
53  public class JsonPathJwtConverter implements Converter<Jwt, AbstractAuthenticationToken> {
54  
55    /**
56     * The json path to the username.
57     */
58    private final String nameJsonPath;
59  
60    /**
61     * The json path to the first name.
62     */
63    private final String firstNameJsonPath;
64  
65    /**
66     * The json path to the last name.
67     */
68    private final String lastNameJsonPath;
69  
70    /**
71     * The json path to the email.
72     */
73    private final String emailJsonPath;
74  
75    /**
76     * The json path to the roles.
77     */
78    private final String rolesJsonPath;
79  
80    /**
81     * Specifies whether the roles are represented as a json array or as a list separated by
82     * {@link #getRolesValueSeparator()}.
83     */
84    private final boolean rolesValueList;
85  
86    /**
87     * The roles separator to use if {@link #isRolesValueList()} is set to {@code false}.
88     */
89    private final String rolesValueSeparator;
90  
91    /**
92     * The authorities mapper.
93     */
94    private final GrantedAuthoritiesMapper authoritiesMapper;
95  
96    /**
97     * Instantiates a new Json path jwt converter.
98     *
99     * @param nameJsonPath the name json path
100    * @param firstNameJsonPath the first name json path
101    * @param lastNameJsonPath the last name json path
102    * @param emailJsonPath the email json path
103    * @param rolesJsonPath the roles json path
104    * @param rolesValueList the roles value list
105    * @param rolesValueSeparator the roles value separator
106    * @param authoritiesMapper the authorities mapper
107    */
108   public JsonPathJwtConverter(
109       String nameJsonPath,
110       String firstNameJsonPath,
111       String lastNameJsonPath,
112       String emailJsonPath,
113       String rolesJsonPath,
114       boolean rolesValueList,
115       String rolesValueSeparator,
116       GrantedAuthoritiesMapper authoritiesMapper) {
117 
118     this.nameJsonPath = nameJsonPath;
119     this.firstNameJsonPath = firstNameJsonPath;
120     this.lastNameJsonPath = lastNameJsonPath;
121     this.emailJsonPath = emailJsonPath;
122     this.rolesJsonPath = rolesJsonPath;
123     this.rolesValueList = rolesValueList;
124     this.rolesValueSeparator = rolesValueSeparator;
125     this.authoritiesMapper = requireNonNullElseGet(authoritiesMapper, SimpleAuthorityMapper::new);
126   }
127 
128   @NonNull
129   @Override
130   public NormalizedJwtAuthenticationToken convert(@NonNull Jwt source) {
131     JsonPathJwtParser parser = new JsonPathJwtParser(source);
132     return new NormalizedJwtAuthenticationToken(
133         source,
134         new NormalizedUser(
135             getUsername(source, parser),
136             getFirstName(parser),
137             getLastName(parser),
138             getEmail(parser)),
139         getGrantedAuthorities(parser));
140   }
141 
142   /**
143    * Gets granted authorities.
144    *
145    * @param parser the parser
146    * @return the granted authorities
147    */
148   protected Collection<? extends GrantedAuthority> getGrantedAuthorities(JsonPathJwtParser parser) {
149     Stream<String> values = isRolesValueList()
150         ? getAuthoritiesFromList(parser)
151         : getAuthoritiesFromValue(parser);
152     Set<GrantedAuthority> authorities = values.map(SimpleGrantedAuthority::new)
153         .collect(Collectors.toSet());
154     return authoritiesMapper.mapAuthorities(authorities);
155   }
156 
157   /**
158    * Gets authorities from list.
159    *
160    * @param parser the parser
161    * @return the authorities from list
162    */
163   protected Stream<String> getAuthoritiesFromList(JsonPathJwtParser parser) {
164     //noinspection unchecked
165     return Stream.ofNullable(getRolesJsonPath())
166         .map(path -> parser.read(path, List.class))
167         .filter(Objects::nonNull)
168         .map(list -> (List<String>) list)
169         .flatMap(Collection::stream);
170   }
171 
172   /**
173    * Gets authorities from value.
174    *
175    * @param parser the parser
176    * @return the authorities from value
177    */
178   protected Stream<String> getAuthoritiesFromValue(JsonPathJwtParser parser) {
179     return Stream.ofNullable(getRolesJsonPath())
180         .filter(path -> !isEmpty(getRolesValueSeparator()))
181         .map(path -> parser.read(path, String.class))
182         .filter(Objects::nonNull)
183         .map(value -> value.split(Pattern.quote(getRolesValueSeparator())))
184         .flatMap(Arrays::stream)
185         .map(String::valueOf);
186   }
187 
188   /**
189    * Gets username.
190    *
191    * @param source the source
192    * @param parser the parser
193    * @return the username
194    */
195   protected String getUsername(Jwt source, JsonPathJwtParser parser) {
196     return Optional.ofNullable(getNameJsonPath())
197         .filter(jsonPath -> !jsonPath.isBlank())
198         .map(jsonPath -> parser.read(jsonPath, String.class))
199         .orElseGet(source::getSubject);
200   }
201 
202   /**
203    * Gets first name.
204    *
205    * @param parser the parser
206    * @return the first name
207    */
208   protected String getFirstName(JsonPathJwtParser parser) {
209     return parser.read(getFirstNameJsonPath(), String.class);
210   }
211 
212   /**
213    * Gets last name.
214    *
215    * @param parser the parser
216    * @return the last name
217    */
218   protected String getLastName(JsonPathJwtParser parser) {
219     return parser.read(getLastNameJsonPath(), String.class);
220   }
221 
222   /**
223    * Gets email.
224    *
225    * @param parser the parser
226    * @return the email
227    */
228   protected String getEmail(JsonPathJwtParser parser) {
229     return parser.read(getEmailJsonPath(), String.class);
230   }
231 
232 }