I found a solution to upload a file with Feign but my configuration creates another problem. I have several Feign clients for differents services used from a client sevice.
Now when I submit a simple form I get this error :
feign.codec.EncodeException: class java.util.ArrayList is not a type
supported by this encoder
Before I had this configuration that worked perfectly but not anymore, I don't know why.
#Component
#RestController
#FeignClient(name = "zuul-server"
,configuration = {ProxyImages.MultipartSupportConfig.class})
#RibbonClient(name = "oauth2-server")
public interface ProxyImages {
class MultipartSupportConfig {
#Bean
#Primary
#Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
#RequestMapping(value = {"oauth2-server/auth/user/avatar"}, consumes = {"multipart/form-data"})
ResponseEntity<String> saveUserAvatar(#RequestPart(name = "file") MultipartFile file);$
}
I've no idea why the MultipartSupportConfig class is not taken into account any more.
So I added a #configuration annotation to the MultipartSupportConfig class
#Configuration
class MultipartSupportConfig
and it works again, but now it seems that this encoder is always used and I'd like to use it only for that proxy. How can I do that ?
Here is the dependencies I use
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.M8</spring-cloud.version>
<joinfaces.version>3.2.4</joinfaces.version>
</properties>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.3.0</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Thanks.
Problem fixed.
#RestController
#FeignClient(name = "zuul-server")
public interface ProxyImages {
#Configuration
class MultipartSupportConfig {
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
#Primary
#Scope("prototype")
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
#RequestMapping(value = {"oauth2-server/auth/user/avatar"}, consumes = {"multipart/form-data"})
ResponseEntity<String> saveUserAvatar(#RequestPart(name = "file") MultipartFile file);
}
Related
I have created an authorization server using the new spring authorization server module. I am able to get the token successfully but when I try to use the token against a protected endpoint with hasAuthority() I get forbidden 403 error. Below my pom.xml file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.erycoking</groupId>
<artifactId>auth-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth-service</name>
<description>Auth Service</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.0</spring-cloud.version>
<jhipster-dependencies.version>7.0.1</jhipster-dependencies.version>
<liquibase.version>4.6.1</liquibase.version>
</properties>
<dependencies>
<dependency>
<groupId>tech.jhipster</groupId>
<artifactId>jhipster-framework</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<!-- Jackson Configurations -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hppc</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
<version>0.26.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<!-- Inherited version from Spring Boot can't be used because of regressions -->
<version>${liquibase.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>${liquibase.version}</version>
</plugin>
</plugins>
</build>
</project>
Below is my Authorization Server config
#Configuration(proxyBeanMethods = false)
public class AuthServerConfig {
private final DataSource dataSource;
private final AuthProperties authProps;
private final TokenSettings tokenSettings;
public AuthServerConfig(DataSource dataSource, AuthProperties authProps, TokenSettings tokenSettings) {
this.dataSource = dataSource;
this.authProps = authProps;
this.tokenSettings = tokenSettings;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
clientRepository.save(webClient());
return clientRepository;
}
private RegisteredClient webClient() {
return RegisteredClient.withId("98a9104c-a9c7-4d7c-ad03-ec61bcfeab36")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(authProps.getClientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/authorized")
.scope("create").scope("read").scope("write").scope("update").scope("delete")
.tokenSettings(tokenSettings)
.build();
}
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
#Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer(authProps.getIssuerUri())
.build();
}
}
And this is my Security Config
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {
private final UserDetailsService userDetailsService;
public SecurityConfig(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/management/**").permitAll()
.antMatchers("/h2-console/**").permitAll()
//this does not work
.antMatchers(HttpMethod.POST, "/auth/user").hasAuthority(AuthoritiesConstants.ADMIN)
//this does not work
.antMatchers(HttpMethod.GET, "/auth/user").hasAuthority("SCOPE_read")
.anyRequest().authenticated()
.and()
.csrf().disable()
.headers().frameOptions().disable()
.and()
.formLogin(withDefaults())
.userDetailsService(userDetailsService);
return http.build();
}
#Bean
public PasswordEncoder delegatingPasswordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder());
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder("bcrypt", encoders);
passwordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());
return passwordEncoder;
}
#Bean
public TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofMinutes(1))
.refreshTokenTimeToLive(Duration.ofHours(24))
.build();
}
}
Here is my user detail service
#Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
return userRepository.findOneWithRolesByEmailIgnoreCase(login)
.map(user -> createSpringSecurityUser(login, user))
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {
if (!user.isActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
user.getRoles().forEach(e -> {
grantedAuthorities.add(new SimpleGrantedAuthority(e.getName()));
e.getPermissions().forEach(p -> grantedAuthorities.add(new SimpleGrantedAuthority(p.getName())));
});
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), grantedAuthorities);
}
}
when using the token to make a request to an endpoint that requires authentication alone it succeeds but when I try using it on an endpoint that requires a role it fails.
What could be the issue?
Based on a brief discussion in comments to clarify your setup, it seems that you are configuring your authorization server as a regular secured application with the intention of using scopes as authorities. However, this is an incorrect usage of the authorization server.
Note: I have not seen any reference to an OAuth 2.0 client or resource server in your description, so I'm assuming you are trying to hit endpoints directly on the authorization server. If that's not the case, let me know.
There are three applications involved in an OAuth 2.0 protected setup:
Authorization server
Resource server
Client
Your configuration is only for #1 (as far as I can tell). The authorization server contains two filter chains and additionally a configuration for a single oauth client. The two filter chains do the following:
Secure endpoints provided by the authorization server framework
Secure the login endpoint(s) the user will interact with prior to using the authorization endpoint (/oauth2/authorize) to obtain an authorization code, which the client will later use to obtain an access token
The scopes you have configured would allow a user (resource owner) to grant an oauth client the ability to make a protected call to a resource server using an access token. Only when the client makes a call to a resource server will your configured scopes be used. When the user directly interacts with an endpoint on the authorization server using a browser, the configuration for form login is in play, which as I mentioned in comments, uses roles from your database.
See the SpringOne 2021 repository and presentation to understand how to take an application from an unsecured application to a secured one, and then see how we turn it into a resource server, which uses scopes as authorities.
The presentation demonstrates all three applications, though the focus is on the resource server, which matches closely what you are trying to accomplish with scopes as authorities.
I have inherited a Spring Boot microservice which does not have a Service or API layer, it is behaving in a HATEOAS style.
This is not an optimal architecture and needs to be changed into MVC.
Currently all repository methods are accessed directly using the #RepositoryRestResource annotation.
The plan is to refactor this and add Controllers and a API layer (DTOs), however after adding a controller, swagger is not showing the Rest controllers
Also to note that when debugging the controller endpoint, it is not actually reached. It is being bypassed, which is another clue.
#CrossOrigin
#RestController
#RequestMapping("/fixing")
public class FixingController {
private final FixingRepository fixingRepository;
#Autowired
FixingController(final FixingRepository fixingRepository) {
this.fixingRepository = checkNotNull(fixingRepository, "Fixing Repository cannot be null");
}
/**
* Builds a list of Fixing strings from the database
* #return list
*/
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<String> getAllFixings() {
final List<String> fixingList = new ArrayList<>();
for (Fixing fixing : fixingRepository.findAll()) {
String name = fixing.getName();
fixingList.add(name);
}
return fixingList;
}
}
This is the spring swagger config
#Configuration
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api.*"))
.build();
}
}
The repository (note no #RepositoryRestResource annotation)
public interface FixingRepository extends JpaRepository<Fixing, Long> {
#Override
Fixing findOne(Long id);
#Override
List<Fixing> findAll();
}
When I rebuild and start the service, the controller is not shown. It only shows all the entities and their repository methods.
POM dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- included explicitly to avoid javadoc generation error
due to a conflict with a class used by #Transactional annotation -->
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.12</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.13.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-storage</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
</dependencies>
Any ideas what is causing this? There is nothing else I can see in the config which is preventing this from working
The issue is with your SwaggerConfig. You are only selecting a subset of your APIs (either the JPA repository sourced or your RestController sourced) via this :
.paths(PathSelectors.regex("/api.*"))
I replicated your scenario and I just commented the path selection out and I can see both type of APIs. Note that you can also use a custom predicate for selecting the paths:
#Configuration
#Import({SpringDataRestConfiguration.class})
public class SwaggerConfig {
#Autowired
#SuppressWarnings({"UnusedDeclaration"})
private ServletContext servletContext;
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.pathProvider(relativePath())
.select()
.apis(RequestHandlerSelectors.any())
// .paths(paths2())
.build();
}
// Select only a few
private Predicate<String> paths2() {
return and(
(regex("/fixing.*")),
(regex("/api.*")));
}
// Exclude these
private Predicate<String> paths() {
return and(
not(regex("/error.*")),
not(regex("/metrics.*")),
not(regex("/jolokia.*")),
not(regex("/health.*")),
not(regex("/env.*")),
not(regex("/metrics.*")),
not(regex("/info.*")),
not(regex("/mappings.*")),
not(regex("/trace.*")),
not(regex("/dump.*")),
not(regex("/heapdump.*")),
not(regex("/configprops.*")),
not(regex("/beans.*")),
not(regex("/autoconfig.*")),
not(regex("/logfile.*")),
not(regex("/shutdown.*")),
not(regex("/actuator.*")));
}
}
Sample Rest Controller:
#CrossOrigin
#RestController
#RequestMapping("/fixing")
public class FixingController {
/**
* Builds a list of Fixing strings from the database
* #return list
*/
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<String> getAllFixingsViaRestController() {
final List<String> fixingList = new ArrayList<>();
fixingList.add("foo");
fixingList.add("bar");
return fixingList;
}
}
Now my Swagger UI looks like this; you can see both the JPA Repository contributed REST APIs and the RestController contributed API (/fixing path):
Hello experts,
I'm currently learning Spring Boot and I want to use it with Spring Security ACL.
Following the documentation of Spring Security and a tutorial on Baeldung.com, I think I got an understanding about what is needed. I also looked into the DMS example of Spring. I stumbled across another example by searching for a solution.
Based on this information, I have build my application. For reference, you can find the current application on GitHub.
The current issue
When I start the application, I'm getting a java.lang.IllegalStateException: No ServletContext set thrown. As far as I understand, this is due to the fact that during Spring Boot's auto-configure magic, my #Configuration annotated classes are initialized before the ServletContext is initialized.
Link to full stack trace on pastebin.
What I have done so far
Based on my research (mostly on StackOverflow) and my current understanding of the issue, it should help to put the responsible Beans into an own #Configuration annotated class. Unfortunately, here I'm currently lost.
Related questions led me to this thinking (see this question, or this one.)
My environment
macOS 10.12.6
jdk1.8.0_144
IntelliJ IDEA 2017.3.3 (Ultimate Edition)
Spring Boot v1.5.9.RELEASE
Relevant project files
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.moritzrupp</groupId>
<artifactId>traderdiary</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Trader Diary</name>
<description>Trader Diary is an easy to use web application to create a journal
about your trades with great reporting on top of it.</description>
<licenses>
<license>
<name>GNU General Public License (GPL) v3.0</name>
<url>https://www.gnu.org/licenses/gpl-3.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<id>moritzrupp</id>
<name>Moritz Rupp</name>
<email>moritz.rupp#gmail.com</email>
<url>https://www.moritzrupp.de</url>
<timezone>DE</timezone>
</developer>
</developers>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.properties
# --------------------------------------------
# Datasource Properties
# --------------------------------------------
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.datasource.url=jdbc:h2:mem:trader-diary-h2-db
spring.datasource.platform=h2
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
TraderDiaryApplication.java
#SpringBootApplication
public class TraderDiaryApplication {
public static void main(String[] args) {
SpringApplication.run(TraderDiaryApplication.class, args);
}
}
DataSourceConfig.java
#Configuration
public class DataSourceConfig {
#Bean
public DataSource traderDiaryDataSource(DataSourceProperties
dataSourceProperties) {
return dataSourceProperties.initializeDataSourceBuilder().build();
}
}
WebSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("traderDiaryDataSource")
private DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
// I think it is not relevant for the issue, see GitHub repo
...
}
#Bean
public PasswordEncoder passwordEncoder() {
// I think it is not relevant for the issue, see GitHub repo
...
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(...) {
// I think it is not relevant for the issue, see GitHub repo
...
}
#Bean
public MethodSecurityExpressionHandler aclExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
AclPermissionCacheOptimizer permissionCacheOptimizer =
new AclPermissionCacheOptimizer(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator());
expressionHandler
.setPermissionCacheOptimizer(permissionCacheOptimizer);
return expressionHandler;
}
#Bean
public PermissionEvaluator permissionEvaluator() {
return new AclPermissionEvaluator(aclService());
}
#Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(dataSource, lookupStrategy(),
aclCache());
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(),
aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(),
permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new
ConsoleAuditLogger());
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
/* This is due to an earlier issue: DataSource required */
#Configuration
protected static class AclMethodSecurityConfig extends
GlobalMethodSecurityConfiguration {
#Autowired
#Qualifier("daoAuthenticationProvider")
private AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("aclExpressionHandler")
private MethodSecurityExpressionHandler aclExpressionHandler;
#Autowired
public void configureAuthManager(AuthenticationManagerBuilder
authenticationManagerBuilder) {
authenticationManagerBuilder
.authenticationProvider(authenticationProvider);
}
#Override
protected MethodSecurityExpressionHandler
createExpressionHandler() {
return aclExpressionHandler;
}
}
}
Thanks a lot for all input! If any further information is required, I will happily provide it.
Thanks and best regards
Moritz
Hey all,
I managed to resolve the issue myself. I did a step-by-step approach with commenting/uncommenting all the MethodSecurity related stuff.
I pin-pointed it down to the creation of the DefaultMethodSecurityExpressionHandler. This was causing the IllegalStateException: No ServletContext set.
Then, I have created a new Class MethodSecurityConfig.java and I put all the related code there. Now the application is starting again and I can continue with the developed.
#Rakesh: Thanks for your input!
MethodSecurityConfig.java
#Configuration
public class MethodSecurityConfig {
private DataSource dataSource;
#Autowired
#Qualifier("traderDiaryDataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
#Bean
public MethodSecurityExpressionHandler aclExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
AclPermissionCacheOptimizer permissionCacheOptimizer =
new AclPermissionCacheOptimizer(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator());
expressionHandler
.setPermissionCacheOptimizer(permissionCacheOptimizer);
return expressionHandler;
}
#Bean
public PermissionEvaluator permissionEvaluator() {
return new AclPermissionEvaluator(aclService());
}
#Bean
public JdbcMutableAclService aclService() {
return new JdbcMutableAclService(dataSource,
lookupStrategy(), aclCache());
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(),
aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(),
permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(
new ConsoleAuditLogger());
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
}
WebSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Configuration
protected static class AclMethodSecurityConfig
extends GlobalMethodSecurityConfiguration {
private MethodSecurityExpressionHandler aclExpressionHandler;
#Autowired
#Qualifier("aclExpressionHandler")
public void setAclExpressionHandler(
MethodSecurityExpressionHandler aclExpressionHandler) {
this.aclExpressionHandler = aclExpressionHandler;
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return aclExpressionHandler;
}
}
}
Just to follow up on your own answer and my previous comment where I was also lost with this one (for over 2 days!). In the end it was also overriding the configure(AuthenticationManagerBuilder auth) method in a subclass of GlobalMethodSecurityConfiguration which was also annotated with #EnableGlobalMethodSecurity.
As I am new to Spring I am unfortunately unable to provide details as to why this is the way but if I put the configuring of the AuthenticationManagerBuilder in a class that extends WebSecurityConfigurereAdapter (like I originally had) then I always end up getting a IllegalStateException: No ServletContext set error.
Here is the final code I used:
#Configuration
#EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return defaultMethodSecurityExpressionHandler();
}
#Override
protected void configure( final AuthenticationManagerBuilder authenticationManagerBuilder ) throws Exception {
authenticationManagerBuilder.authenticationProvider( authenticationProvider() );
}
#Bean
public JdbcDaoImpl jdbcDao() {
JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
jdbcDao.setDataSource( dataSource() );
return jdbcDao;
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService( jdbcDao() );
authenticationProvider.setPasswordEncoder( encoder() );
return authenticationProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder( 11 );
}
#Bean( name = "myDS")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName( "com.mysql.cj.jdbc.Driver" );
dataSource.setUrl( "jdbc:mysql://localhost:3306/java_spring_angular_rest_security_acl2" );
dataSource.setUsername( "root" );
dataSource.setPassword( "alphax" );
return dataSource;
}
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource(), aclCache(), aclAuthorizationStrategy(), permissionGrantingStrategy());
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
#Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
#Bean( name = "defaultMethodSecurityExpressionHandler")
public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());
expressionHandler.setPermissionEvaluator(permissionEvaluator);
expressionHandler.setPermissionCacheOptimizer( new AclPermissionCacheOptimizer( aclService() ) );
return expressionHandler;
}
#Bean( name = "myAclService")
public JdbcMutableAclService aclService() {
JdbcMutableAclService jdbcMutableAclService = new JdbcMutableAclService(dataSource(), lookupStrategy(), aclCache());
jdbcMutableAclService.setClassIdentityQuery( "SELECT ##IDENTITY" );
jdbcMutableAclService.setSidIdentityQuery( "SELECT ##IDENTITY" );
return jdbcMutableAclService;
}
}
and here is the pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fireduptech.spring.rest</groupId>
<artifactId>hero</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hero</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-acl -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Hope that helps someone :)
I've two microservices,
eureka-client-1 running on localhost:8081
eureka-client-2 running on localhost:8082
Both these are DiscoveryClients registered with 'eureka-server' running on localhost:8761.
In code snippet below, I'm trying to call eureka-client-2 from eureka-client-1. Instead of calling http://localhost:8082, i want to call http://eureka-client-2 but this throws java.net.UnknownHostException during Eureka service discovery.
After searching, I found that i need to use "Brixton" to get it done.
Is there a way to do it with Camden.SR3 ?
Please suggest.
#Component
public class HystrixDemoService {
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
public String getCustomerName() {
RestTemplate restTemplate = new RestTemplate();
URI uri = URI.create("http://eureka-client-2"); // fails here
return restTemplate.getForObject(uri, String.class);
}
public String getFallbackCustomerName() {
System.out.println("coming inside fallback method");
return "Resillient Customer";
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-pranay-eureka-client1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo-pranay-eureka-client1</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties for client 1, similar for client 2(just change in name i.e. eureka-client-2)
spring.application.name=eureka-client-1
server.port=8081
eureka:
client:
registerWithEureka: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
statusPageUrlPath: /info
healthCheckUrlPath: /health
application.properties for eureka server
spring.application.name=eureka-service
server.port=8761
eureka:
client:
registerWithEureka: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 10
statusPageUrlPath: /info
healthCheckUrlPath: /health
For me the only thing that I need to do to fix the issue was to add the spring annotation #LoadBalanced to
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
The solution is that you miss #LoadBalanced.
if you use Spring Boot or Sprint Cloud, I prefer to use the RestTemplateBuilder builder
#Bean
#LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
If you use spring only, I suggest you to new RestTemplate()
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
As commented earlier I believe you might be missing Ribbon configuration in eureka-client-1.
First, I would move:
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
to a configuration class.
Add Ribbon configuration to application.yml, something like:
the-eureka-client-2:
ribbon:
# Eureka vipAddress of the target service
DeploymentContextBasedVipAddresses: eureka-client-2
#listOfServers: localhost:${SERVER.PORT}
NIWSServerListClassName: com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
# Interval to refresh the server list from the source (ms)
ServerListRefreshInterval: 30000
Inject restTemplate in HystrixDemoService class instead of instantiating a new one for every request. RestTemplate is thread-safe:
#Component
public class HystrixDemoService {
#Autowired
public RestTemplate restTemplate;
...
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
public String getCustomerName() {
URI uri = URI.create("http://eureka-client-2");
return this.restTemplate.getForObject(uri, String.class);
}
where the-eureka-client-2 is a key that maps to the registered service with name: eureka-client-2
I blogged about Microservices Registration and Discovery using Spring Cloud Eureka, Ribbon and Feign which also includes source code of the Discovery server and two clients developed using Jersey 1 and Spring MVC.
Changes below worked for me.
#SpringBootApplication
public class DemoPranayEurekaClient1Application {
public static void main(String[] args) {
SpringApplication.run(DemoPranayEurekaClient1Application.class, args);
}
}
#EnableDiscoveryClient
#EnableCircuitBreaker
#RestController
class HystrixDemoApplication {
#Autowired
HystrixDemoService hystrixDemoService;
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
#RequestMapping("/")
public String name() {
String str = hystrixDemoService.getCustomerName();
return "I'm A talking to "+str;
}
}
Below is the line of code that is used for choosing instance of eureka-client-2...
ServiceInstance instance = loadBalancer.choose("eureka-client-2");
#Component
public class HystrixDemoService {
#Autowired
private LoadBalancerClient loadBalancer;
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
public String getCustomerName() {
RestTemplate restTemplate = new RestTemplate();
ServiceInstance instance = loadBalancer.choose("eureka-client-2");
URI uri = instance.getUri();
return restTemplate.getForObject(uri, String.class);
}
public String getFallbackCustomerName() {
System.out.println("coming inside fallback method");
return "Resillient Customer";
}
}
I use springfox-swagger2 for my Spring MVC REST API. Everything works good with swagger but my problem is I cannot add additional information to my swagger documentation.
Maven Dependency:
<!-- Swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
My Swagger config class:
#Configuration
#EnableWebMvc
#EnableSwagger2
#ComponentScan("path.to.controller.package")
public class SwaggerConfig {
#Bean
public Docket customImplementation() {
return new Docket(DocumentationType.SPRING_WEB).apiInfo(apiInfo());
}
#Bean
public UiConfiguration uiConfig() {
return UiConfiguration.DEFAULT;
}
private ApiInfo apiInfo() {
ApiInfo apiInfo = new ApiInfo("Service API", "Simple REST Service", "0.0.1",
"mail#mail.com", "mail#mail.com", " ", " ");
return apiInfo;
}
}
My controller class:
#RestController
#RequestMapping("/persons")
public class PersonController {
Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
#RequestMapping(value = "/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
#ApiOperation(value = "doStuff", response = Person.class)
#ApiImplicitParams({#ApiImplicitParam(name="Authorization", value="MY DESCRIPTION")})
public #ResponseBody Person getPerson(#PathVariable String id,
#RequestHeader(value = "Authorization") String authToken) throws Exception {
//do things and return
}
}
So, calling the swagger-ui the controller is shown, the method, everything except my additional infos defined in #ApiOperation and #ApiImplicitParams. Does anyone have an idea from where the problem can come from? The params are also not in the JSON file which is created from swagger.
Try to replace your customImplementation() method by:
#Bean
public Docket customImplementation() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.build()
.apiInfo(apiInfo());
}
Build the project, and then your additional infos should appear.
EDIT: I don't know if it makes any difference, but I am using these dependencies:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.1.2</version>
</dependency>