View Javadoc
1   /*
2    * Copyright 2024-2026 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.boot.autoconfigure.security.authentication;
18  
19  import static java.util.Objects.isNull;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.bremersee.spring.boot.autoconfigure.security.authentication.AuthenticationProperties.JwtConverterProperties;
24  import org.bremersee.spring.security.core.mapping.CaseTransformation;
25  import org.bremersee.spring.security.core.mapping.authority.NormalizedGrantedAuthoritiesMapper;
26  import org.bremersee.spring.security.core.mapping.group.GroupsMapper;
27  import org.bremersee.spring.security.core.mapping.group.NormalizedGroupsMapper;
28  import org.bremersee.spring.security.oauth2.server.resource.authentication.JsonPathJwtConverter;
29  import org.bremersee.spring.security.oauth2.server.resource.authentication.JsonPathJwtProperties;
30  import org.springframework.beans.factory.ObjectProvider;
31  import org.springframework.boot.autoconfigure.AutoConfiguration;
32  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
33  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
34  import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
35  import org.springframework.boot.context.event.ApplicationReadyEvent;
36  import org.springframework.boot.context.properties.EnableConfigurationProperties;
37  import org.springframework.boot.security.autoconfigure.web.reactive.ReactiveWebSecurityAutoConfiguration;
38  import org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration;
39  import org.springframework.context.annotation.Bean;
40  import org.springframework.context.event.EventListener;
41  import org.springframework.core.convert.converter.Converter;
42  import org.springframework.security.authentication.AbstractAuthenticationToken;
43  import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
44  import org.springframework.security.oauth2.jwt.Jwt;
45  import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
46  import org.springframework.util.ClassUtils;
47  
48  /**
49   * The jwt converter autoconfiguration.
50   *
51   * @author Christian Bremer
52   */
53  @ConditionalOnClass(name = {
54      "org.bremersee.spring.security.oauth2.server.resource.authentication.JsonPathJwtConverter",
55      "org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter",
56      "org.springframework.boot.security.autoconfigure.web.reactive.ReactiveWebSecurityAutoConfiguration",
57      "org.springframework.boot.security.autoconfigure.web.servlet.ServletWebSecurityAutoConfiguration"
58  })
59  @AutoConfiguration(
60      before = {
61          ServletWebSecurityAutoConfiguration.class,
62          ReactiveWebSecurityAutoConfiguration.class
63      })
64  @ConditionalOnProperty(
65      prefix = "spring.security.oauth2.resourceserver.jwt",
66      name = "jwk-set-uri")
67  @EnableConfigurationProperties(AuthenticationProperties.class)
68  public class JwtConverterAutoConfiguration {
69  
70    private static final Log log = LogFactory.getLog(JwtConverterAutoConfiguration.class);
71  
72    private final JwtConverterProperties properties;
73  
74    /**
75     * Instantiates a new Jwt converter autoconfiguration.
76     *
77     * @param properties the properties
78     */
79    public JwtConverterAutoConfiguration(AuthenticationProperties properties) {
80      this.properties = properties.getJwtConverter();
81    }
82  
83    /**
84     * Init.
85     */
86    @EventListener(ApplicationReadyEvent.class)
87    public void init() {
88      log.info(String.format("""
89              
90              *********************************************************************************
91              * %s
92              * properties = %s
93              *********************************************************************************""",
94          ClassUtils.getUserClass(getClass()).getSimpleName(),
95          properties));
96    }
97  
98    /**
99     * Creates jwt converter.
100    *
101    * @param rolesMapperProvider the mapper of roles
102    * @param groupsMapperProvider the mapper of groups
103    * @return the converter
104    */
105   @ConditionalOnMissingBean({JwtAuthenticationConverter.class})
106   @Bean
107   public Converter<Jwt, AbstractAuthenticationToken> jwtConverter(
108       ObjectProvider<GrantedAuthoritiesMapper> rolesMapperProvider,
109       ObjectProvider<GroupsMapper> groupsMapperProvider) {
110 
111     log.info("Creating new jwt authentication converter.");
112 
113     GrantedAuthoritiesMapper grantedAuthoritiesMapper = rolesMapperProvider
114         .getIfAvailable(() -> new NormalizedGrantedAuthoritiesMapper(
115             properties.getDefaultRoles(),
116             properties.toRoleMappings(),
117             properties.getRolePrefix(),
118             getCaseTransformation(properties.getRoleCaseTransformation()),
119             properties.toRoleStringReplacements()));
120 
121     GroupsMapper groupsMapper = groupsMapperProvider
122         .getIfAvailable(() -> new NormalizedGroupsMapper(
123             properties.getDefaultGroups(),
124             properties.toGroupMappings(),
125             properties.getGroupPrefix(),
126             getCaseTransformation(properties.getGroupCaseTransformation()),
127             properties.toGroupStringReplacements()));
128 
129     JsonPathJwtProperties jsonPathJwtProperties = JsonPathJwtProperties.builder()
130         .nameJsonPath(properties.getNameJsonPath())
131         .firstNameJsonPath(properties.getFirstNameJsonPath())
132         .lastNameJsonPath(properties.getLastNameJsonPath())
133         .emailJsonPath(properties.getEmailJsonPath())
134         .rolesJsonPath(properties.getRolesJsonPath())
135         .rolesValueList(properties.isRolesValueList())
136         .rolesValueSeparator(properties.getRolesValueSeparator())
137         .authoritiesMapper(grantedAuthoritiesMapper)
138         .groupsJsonPath(properties.getGroupsJsonPath())
139         .groupsValueList(properties.isGroupsValueList())
140         .groupsValueSeparator(properties.getGroupsValueSeparator())
141         .groupsMapper(groupsMapper)
142         .build();
143 
144     return new JsonPathJwtConverter(jsonPathJwtProperties);
145   }
146 
147   private CaseTransformation getCaseTransformation(
148       AuthenticationProperties.CaseTransformation source) {
149     return isNull(source) ? CaseTransformation.NONE : CaseTransformation.valueOf(source.name());
150   }
151 }