#Configuration class is not getting loaded during container startup - java

I am writing a RestTemplateErrorHandler.java class to handle RestTemplate exception.
Scenarion: I have a jar file in my class path which has the resttemplate exception handling functionality.
Jar name-
aws-common-config.jar
|- RestTemplateConfig.class
|- RestTemplateErrorHandler.class
RestTemplateConfig.class
#EnableConfigurationProperties({ HttpProperties.class, MksProperties.class })
#RequiredArgsConstructor
public class RestTemplateConfig {
private static final Logger LOG = LogManager
.getLogger(RestTemplateConfig.class);
private final HttpProperties httpProperties;
private final MksProperties mksProperties;
private final RestTemplateErrorHandler restTemplateHandler;
#Bean("NoSSLRestTemplate")
#ConditionalOnProperty(name = "aws.common.resttemplate.enabled", havingValue = "true", matchIfMissing = false)
#Primary
public RestTemplate configRestTemplate()
throws CertificateException, IOException, NoSuchAlgorithmException,
KeyStoreException, KeyManagementException {
RestTemplate restTemplate;
restTemplate = new RestTemplate(new OkHttp3ClientHttpRequestFactory(
getRestTemplateFromOkhttp3Template()));
restTemplate.setErrorHandler(restTemplateHandler);
return restTemplate;
}
private OkHttpClient getRestTemplateFromOkhttp3Template() {
//some code
}
#Bean("AWSRestTemplate")
#ConditionalOnProperty(name = "aws.common.resttemplate.awsenabled", havingValue = "true", matchIfMissing = false)
public RestTemplate configureRestTemplateforMTLS() throws Exception {
RestTemplate restTemplate = new RestTemplate();
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient());
requestFactory.setConnectTimeout(
httpProperties.getConnect().getTimeout());
requestFactory.setReadTimeout(
httpProperties.getConnect().getReadTimeout());
restTemplate.setRequestFactory(requestFactory);
restTemplate.setErrorHandler(restTemplateHandler);
} catch (Exception ex) {
LOG.error("Exception occurred while creating MTLS Rest Template ",
ex);
throw ex;
}
return restTemplate;
}
private CloseableHttpClient httpClient() throws Exception {
//some code
}
}
RestTemplateErrorHandler.java -
#Component
#Slf4j
public class RestTemplateErrorHandler implements ResponseErrorHandler {
private static final int BUFFER_SIZE = 200;
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return clientHttpResponse.getStatusCode() != HttpStatus.OK;
}
#Override
public void handleError(ClientHttpResponse clientHttpResponse)
throws IOException {
String errMessage = getErrMessage(clientHttpResponse);
HttpStatus status = clientHttpResponse.getStatusCode();
switch (status) {
case BAD_REQUEST:
throw new CustomException(errMessage,
ErrorResponse.INVALID_REQUEST);
case NOT_FOUND:
throw new CustomException(errMessage, ErrorResponse.NOT_FOUND);
case SERVICE_UNAVAILABLE:
throw new CustomException(errMessage, ErrorResponse.TIME_OUT);
case METHOD_NOT_ALLOWED:
case INTERNAL_SERVER_ERROR:
default:
throw new CustomException(errMessage,
ErrorResponse.INTERNAL_SERVER_ERROR);
}
}
private String getErrMessage(ClientHttpResponse clientHttpResponse) {
try {
return StreamUtils.copyToString(clientHttpResponse.getBody(),
StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("[RestTemplate]read body", e);
return "Error reading response body";
}
}
}
application.properties
aws.common:
appId: TTP
resttemplate:
enabled: true
awsenabled: true
As you could see here
#Override
public boolean hasError(ClientHttpResponse clientHttpResponse)
throws IOException {
return clientHttpResponse.getStatusCode() != HttpStatus.OK;
}
HttpStatus 201 Created is not being handled here.
To Handle this I have written both the java classes in my application.
Now I want to exclude the aws-common-config.jar file's RestTemplateConfig.class and want to load the one I have written in my application.
But everytime aws-common-config.jar file's RestTemplateConfig.class file gets loaded during the startup.
here is my configuration file:
#EnableConfigurationProperties({ HttpProperties.class, MksProperties.class })
#Configuration
#RequiredArgsConstructor
#Component
public class RestTemplateConfiguration {
//body code is same as jar file
}
My RestTemplateErrorHandler.class -
#Component
#Slf4j
public class RestTemplateErrorHandler implements ResponseErrorHandler {
//same code as Jar file
//have created my own CustomException.class and ErrorReponse.class and imported them
}
Here I am calling API using rest template:
#Qualifier("AWSRestTemplate")
#Autowired
private RestTemplate restTemplateAWS;
AWSRestTemplate.exchange(url, HttpMethod.POST, entity, Response.class);
Had tried with
#import
and
#ComponentScan(basePackages = {"com.efx.ews.es"}, excludeFilters = {#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = {RestTemplateConfig.class}
)
})
but nothing help.
Can Someone please help me here how can I exclude jar file config class and lode my RestTemplateConfiguration.class.
I can't modify the jar file code and can't remove the jar file dependency as some other features are getting used.

Related

I am getting swagger ui 403 error how can i fix it?

This is how my microservices classes are. I have two questions.
Firstly, when I run the microservice locally, the swagger document does not open automatically. When I enter the link in the form of host/v2/api-docs with my hand, it opens as json, but the ui part does not come. I can edit and view it with the swagger editor. I added dependency to pom.xml for the UI part, but it doesn't work, how to open the UI screen?
Secondly,
Except for host/v2/api-docs, when I type a link to a controller specifically, I get a 403 authorization error. This is the most important problem that I want to overcome, how can I do it? can you help me?
link/swagger-ui.html#!/signin
localhost:8000/swagger-ui.html#!/signin
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Jun 23 16:04:09 TRT 2022
There was an unexpected error (type=Forbidden, status=403).
Access Denied
My pom.xml is :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox.version}</version>
</dependency>
<dependency>
My SwaggerConfig class is:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build().apiInfo(metaData());
}
#Bean
public UiConfiguration uiConfiguration() {
return UiConfigurationBuilder.builder().deepLinking(true).validatorUrl(null).build();
}
private static final Contact DEFAULT_CONTACT = new Contact("Rosaline Fox,Anna Hurt", "http://www.google.com",
"rosaline.fox#gmail.com,anna.hurt#gmail.com");
private ApiInfo metaData() {
return new ApiInfoBuilder().title("Auth Service Controller API Title")
.description("Auth Service Controller API Description").version("1.0")
.license("Apache License Version 2.0").licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
.contact(DEFAULT_CONTACT).build();
}
}
My WebSecurityConfig class is:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String XI_PARTNER = "XIPartner";
private static final String XI_CONSULTANT = "XIConsultant";
private static final String SALES = "Sales";
private static final String STANDART = "Standart";
public static final String ADMIN = "Admin";
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Autowired
private FilterChainExceptionHandler filterChainExceptionHandler;
#Autowired
private HandlerExceptionResolver handlerExceptionResolver;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable CSRF (cross site request forgery)
http.csrf().disable();
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class);
http.exceptionHandling().accessDeniedHandler((req, res, e) -> handlerExceptionResolver.resolveException(req, res, null, e));
// Entry points
http.authorizeRequests()
.antMatchers("/**/signin/otp", "/**/signin/**", "/**/v2/api-docs/**", "/**/swagger-ui.html#/**").permitAll()
.antMatchers("/**/customers/create").hasAnyAuthority(SALES)
.antMatchers("/**/customers/update").hasAnyAuthority(SALES)
.antMatchers("/**/customers/all").hasAnyAuthority(SALES)
.antMatchers("/**/customers/deactivate").hasAnyAuthority(SALES)
.antMatchers("/**/customers/reactivate").hasAnyAuthority(SALES)
.antMatchers("/**/products/create").hasAnyAuthority(SALES)
.antMatchers("/**/products/update").hasAnyAuthority(SALES)
.antMatchers("/**/users/create").hasAnyAuthority(SALES)
.antMatchers("/**/users/update").hasAnyAuthority(SALES)
.antMatchers("/**/users/deactivate").hasAnyAuthority(SALES)
.antMatchers("/**/users/reactivate").hasAnyAuthority(SALES)
.antMatchers("/**/admin/user/all").hasAnyAuthority(ADMIN)
.antMatchers("/**/xicustomers/create").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/update").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/all").hasAnyAuthority(SALES)
.antMatchers("/**/partner/create").hasAnyAuthority(SALES)
.antMatchers("/**/xicustomers/list").hasAnyAuthority(XI_PARTNER,XI_CONSULTANT)
.antMatchers("/**/report/list/**").hasAnyAuthority(XI_CONSULTANT)
.antMatchers("/**/originator").hasAnyAuthority(STANDART)
.antMatchers("/**/blackhour/add").hasAnyAuthority(STANDART)
.antMatchers("/**/blackhour").hasAnyAuthority(STANDART)
.antMatchers("/**/access/**").anonymous()
.antMatchers("/**/pwd/forgot").anonymous()
.antMatchers("/**/maximo").anonymous()
.anyRequest().authenticated();
// Apply JWT
http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider));
// Optional, if you want to test the API from a browser
// http.httpBasic();
}
#Override
public void configure(WebSecurity web) throws Exception {
// Allow eureka client to be accessed without authentication
web.ignoring().antMatchers("/*/")//
.antMatchers("/eureka/**")//
.antMatchers(HttpMethod.OPTIONS, "/**"); // Request type options
// should be
// allowed.
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
public AuthenticationManager customAuthenticationManager() throws Exception {
return authenticationManager();
}
}
My Controller Class :
#ApiOperation(value = "Login Service", notes = "Login service with captcha verification.")
#PostMapping("/signin")
#ResponseBody
public ResponseEntity<LoginResponse> login(#RequestBody LoginRequest loginRequest) {
LoginResponse loginResponse = this.loginService.login(loginRequest);
return ResponseEntity.accepted().body(loginResponse);
}
My JwtTokenFilterConfigurer class :
public class JwtTokenFilterConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilterConfigurer(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void configure(HttpSecurity http) throws Exception {
JwtTokenFilter customFilter = new JwtTokenFilter(this.jwtTokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
My JwtTokenFilter class :
#Slf4j
#Component
public class JwtTokenFilter extends GenericFilterBean {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//HttpServletResponse response = (HttpServletResponse) res;
String requestURI = request.getRequestURI();
String token = getBearerToken((HttpServletRequest) req);
if (token != null && !requestURI.contains("/signin/otp")) {
TokenParams params = null;
try {
params = this.jwtTokenProvider.validateToken(token);
} catch (JwtException | IllegalArgumentException e) {
log.warn("Invalid Token: {}, Error: {}", params, e.getMessage());
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "INVALID JWT token");
//return;
throw new UnauthorizedException();
}
if (!params.getRoles().contains(WebSecurityConfig.ADMIN) && params.isForOtp() == true) {
log.warn("Invalid Token: {}, it is for OTP!", params);
//response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "INVALID JWT token");
//return;
throw new UnauthorizedException();
}
Authentication auth = this.jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
HeaderMapRequestWrapper wrappedRequest = new HeaderMapRequestWrapper(request);
wrappedRequest.addHeader("companyId", params.getCompanyId());
wrappedRequest.addHeader("user", params.getEmail());
filterChain.doFilter(wrappedRequest, res);
} else {
filterChain.doFilter(req, res);
}
}
private static final String AUTHORIZATION = "Authorization";
private String getBearerToken(HttpServletRequest req) {
String bearerToken = req.getHeader(AUTHORIZATION);
/*
* if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
* return bearerToken.substring(7, bearerToken.length()); }
*/
if (bearerToken != null) {
return bearerToken;
}
return null;
}
}

WebSecurityConfiguration antMatcher() not ignoring specific url for external API in integration test

I am while implementation of AWS Cognito security mechanism in my Spring Boot application. I met a problem with already existing integration test for external API after enabling security.
As a test result I am receiving an error:
2020-11-15 18:18:20.033 ERROR 12072 --- [ main]
.c.s.f.AwsCognitoJwtAuthenticationFilter : Invalid Action, no token
found MockHttpServletResponse:
Status = 401
Error message = null
Headers = [Access-Control-Allow-Origin:"*", Access-Control-Allow-Methods:"POST, GET, OPTIONS, PUT, DELETE",
Access-Control-Max-Age:"3600",
Access-Control-Allow-Credentials:"true",
Access-Control-Allow-Headers:"content-type,Authorization",
Content-Type:"application/json"]
Content type = application/json
Body = {"data":null,"exception":{"message":"JWT Handle exception","httpStatusCode":"INTERNAL_SERVER_ERROR","detail":null}}
My WebSecurityConfiguration looks like:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableTransactionManagement
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private CustomAuthenticationProvider authProvider;
private AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter;
private AccountControllerExceptionHandler exceptionHandler;
private static final String LOGIN_URL = "/auth/login";
private static final String LOGOUT_URL = "/auth/signOut";
#Autowired
public WebSecurityConfiguration(
CustomAuthenticationProvider authProvider,
AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter,
AccountControllerExceptionHandler exceptionHandler) {
this.authProvider = authProvider;
this.awsCognitoJwtAuthenticationFilter = awsCognitoJwtAuthenticationFilter;
this.exceptionHandler = exceptionHandler;
}
public WebSecurityConfiguration() {
super(true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authProvider).eraseCredentials(false);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) {
// TokenAuthenticationFilter will ignore the below paths
web.ignoring().antMatchers("/auth");
web.ignoring().antMatchers("/auth/**");
web.ignoring().antMatchers("/v2/api-docs");
web.ignoring().antMatchers(GET, "/nutrition/api/**");
web.ignoring().antMatchers(GET, "/**");
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterAfter(corsFilter(), ExceptionTranslationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
.accessDeniedHandler(new RestAccessDeniedHandler())
.and()
.anonymous()
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth")
.permitAll()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(
awsCognitoJwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin(formLogin -> formLogin.loginProcessingUrl(LOGIN_URL).failureHandler(exceptionHandler))
.logout(logout -> logout.permitAll().logoutUrl(LOGOUT_URL))
.csrf(AbstractHttpConfigurer::disable);
}
private CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader(ORIGIN);
config.addAllowedHeader(CONTENT_TYPE);
config.addAllowedHeader(ACCEPT);
config.addAllowedHeader(AUTHORIZATION);
config.addAllowedMethod(GET);
config.addAllowedMethod(PUT);
config.addAllowedMethod(POST);
config.addAllowedMethod(OPTIONS);
config.addAllowedMethod(DELETE);
config.addAllowedMethod(PATCH);
config.setMaxAge(3600L);
source.registerCorsConfiguration("/v2/api-docs", config);
source.registerCorsConfiguration("/**", config);
return new CorsFilter();
}
}
AwsCognitoJwtAuthenticationFilter
#Slf4j
public class AwsCognitoJwtAuthenticationFilter extends OncePerRequestFilter {
private static final String ERROR_OCCURRED_WHILE_PROCESSING_THE_TOKEN =
"Error occured while processing the token";
private static final String INVALID_TOKEN_MESSAGE = "Invalid Token";
private final AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor;
#Autowired private ApplicationContext appContext;
public AwsCognitoJwtAuthenticationFilter(AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor) {
this.awsCognitoIdTokenProcessor = awsCognitoIdTokenProcessor;
}
private void createExceptionResponse(
ServletRequest request, ServletResponse response, CognitoException exception)
throws IOException {
HttpServletRequest req = (HttpServletRequest) request;
ExceptionController exceptionController;
ObjectMapper objMapper = new ObjectMapper();
exceptionController = appContext.getBean(ExceptionController.class);
ResponseData<Object> responseData = exceptionController.handleJwtException(req, exception);
HttpServletResponse httpResponse = CorsHelper.addResponseHeaders(response);
final HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(httpResponse);
wrapper.setStatus(HttpStatus.UNAUTHORIZED.value());
wrapper.setContentType(APPLICATION_JSON_VALUE);
wrapper.getWriter().println(objMapper.writeValueAsString(responseData));
wrapper.getWriter().flush();
}
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authentication;
try {
authentication = awsCognitoIdTokenProcessor.getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (BadJOSEException e) {
SecurityContextHolder.clearContext();
log.error(e.getMessage());
createExceptionResponse(
request,
response,
new CognitoException(
INVALID_TOKEN_MESSAGE,
CognitoException.INVALID_TOKEN_EXCEPTION_CODE,
e.getMessage()));
return;
} catch (CognitoException e) {
SecurityContextHolder.clearContext();
log.error(e.getMessage());
createExceptionResponse(
request,
response,
new CognitoException(
e.getErrorMessage(),
CognitoException.INVALID_TOKEN_EXCEPTION_CODE,
e.getDetailErrorMessage()));
return;
} catch (Exception e) {
SecurityContextHolder.clearContext();
log.error(e.getMessage());
createExceptionResponse(
request,
response,
new CognitoException(
ERROR_OCCURRED_WHILE_PROCESSING_THE_TOKEN,
CognitoException.INVALID_TOKEN_EXCEPTION_CODE,
e.getMessage()));
return;
}
filterChain.doFilter(request, response);
}
}
AwsCognitoIdTokenProcessor
#AllArgsConstructor
#NoArgsConstructor
public class AwsCognitoIdTokenProcessor {
private static final String INVALID_TOKEN = "Invalid Token";
private static final String NO_TOKEN_FOUND = "Invalid Action, no token found";
private static final String ROLE_PREFIX = "ROLE_";
private static final String EMPTY_STRING = "";
private ConfigurableJWTProcessor<SecurityContext> configurableJWTProcessor;
private AWSConfig jwtConfiguration;
private String extractAndDecodeJwt(String token) {
String tokenResult = token;
if (token != null && token.startsWith("Bearer ")) {
tokenResult = token.substring("Bearer ".length());
}
return tokenResult;
}
#SuppressWarnings("unchecked")
public Authentication getAuthentication(HttpServletRequest request)
throws ParseException, BadJOSEException, JOSEException {
String idToken = request.getHeader(HTTP_HEADER);
if (idToken == null) {
throw new CognitoException(
NO_TOKEN_FOUND,
NO_TOKEN_PROVIDED_EXCEPTION,
"No token found in Http Authorization Header");
} else {
idToken = extractAndDecodeJwt(idToken);
JWTClaimsSet claimsSet;
claimsSet = configurableJWTProcessor.process(idToken, null);
if (!isIssuedCorrectly(claimsSet)) {
throw new CognitoException(
INVALID_TOKEN,
INVALID_TOKEN_EXCEPTION_CODE,
String.format(
"Issuer %s in JWT token doesn't match cognito idp %s",
claimsSet.getIssuer(), jwtConfiguration.getCognitoIdentityPoolUrl()));
}
if (!isIdToken(claimsSet)) {
throw new CognitoException(
INVALID_TOKEN, NOT_A_TOKEN_EXCEPTION, "JWT Token doesn't seem to be an ID Token");
}
String username = claimsSet.getClaims().get(USER_NAME_FIELD).toString();
List<String> groups = (List<String>) claimsSet.getClaims().get(COGNITO_GROUPS);
List<GrantedAuthority> grantedAuthorities =
convertList(
groups, group -> new SimpleGrantedAuthority(ROLE_PREFIX + group.toUpperCase()));
User user = new User(username, EMPTY_STRING, grantedAuthorities);
return new CognitoJwtAuthentication(user, claimsSet, grantedAuthorities);
}
}
private boolean isIssuedCorrectly(JWTClaimsSet claimsSet) {
return claimsSet.getIssuer().equals(jwtConfiguration.getCognitoIdentityPoolUrl());
}
private boolean isIdToken(JWTClaimsSet claimsSet) {
return claimsSet.getClaim("token_use").equals("id");
}
private static <T, U> List<U> convertList(List<T> from, Function<T, U> func) {
return from.stream().map(func).collect(Collectors.toList());
}
}
CognitoJwtAutoConfiguration
#Configuration
#Import(AWSConfig.class)
#ConditionalOnClass({AwsCognitoJwtAuthenticationFilter.class, AwsCognitoIdTokenProcessor.class})
public class CognitoJwtAutoConfiguration {
private final AWSConfig jwtConfiguration;
public CognitoJwtAutoConfiguration(AWSConfig jwtConfiguration) {
this.jwtConfiguration = jwtConfiguration;
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public CognitoJwtIdTokenCredentialsHolder awsCognitoCredentialsHolder() {
return new CognitoJwtIdTokenCredentialsHolder();
}
#Bean
public AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor() {
return new AwsCognitoIdTokenProcessor();
}
#Bean
public CognitoJwtAuthenticationProvider jwtAuthenticationProvider() {
return new CognitoJwtAuthenticationProvider();
}
#Bean
public AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter() {
return new AwsCognitoJwtAuthenticationFilter(awsCognitoIdTokenProcessor());
}
#SuppressWarnings({"rawtypes", "unchecked"})
#Bean
public ConfigurableJWTProcessor configurableJWTProcessor() throws MalformedURLException {
ResourceRetriever resourceRetriever =
new DefaultResourceRetriever(CONNECTION_TIMEOUT, READ_TIMEOUT);
// https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.
URL jwkSetURL = new URL(jwtConfiguration.getJwkUrl());
// Creates the JSON Web Key (JWK)
JWKSource keySource = new RemoteJWKSet(jwkSetURL, resourceRetriever);
ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
JWSKeySelector keySelector = new JWSVerificationKeySelector(RS256, keySource);
jwtProcessor.setJWSKeySelector(keySelector);
return jwtProcessor;
}
#Bean
public AWSCognitoIdentityProvider awsCognitoIdentityProvider() {
return AWSCognitoIdentityProviderClientBuilder.standard()
.withRegion(Regions.EU_CENTRAL_1)
.withCredentials(getCredentialsProvider())
.build();
}
#Bean
public AWSCredentialsProvider getCredentialsProvider() {
return new ClasspathPropertiesFileCredentialsProvider();
}
}
I want to exclude my controller URL from being considered as an endpoint which requires authorization.
Based on sight tested controller looks like:
#RestController
#RequestMapping("/nutrition/api/")
class NutritionixApiController {
private ProductFacadeImpl productFacadeImpl;
public NutritionixApiController(
ProductFacadeImpl productFacadeImpl) {
this.productFacadeImpl = productFacadeImpl;
}
#GetMapping("/productDetails")
public ResponseEntity<Set<RecipeIngredient>> productsDetails(#RequestParam String query) {
//logic here
}
}
I have tried to whitelist URL "/nutrition/api/**" in method configure(WebSecurity web)
aby adding:
web.ignoring().antMatchers(GET, "/nutrition/api/**");
or
web.ignoring().antMatchers(GET, "/**");
but without desirable effect. I am a little bit confused about why ignoring.antMatchers() not working so I will be grateful for suggestions on how to fix the above problem.
EDIT
I came back to the topic but with the same result. In WebSecurityConfiguration I commented out #EnableGlobalMethodSecurity(prePostEnabled = true) to try configuration without prePostEnabled = true but without desirable effect. I have the same problem with endpoint /auth which is ignored in the configuration.
I patterned after tutorial which is working and available here click
but I refactored my code a little to get rid of field injection with #Autowired but without doing radical changes and logic under the hood.
Moreover class CustomAuthenticationProvider looks like:
#Component
#RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final CognitoAuthenticationService cognitoService;
#SuppressWarnings("unchecked")
#Override
public Authentication authenticate(Authentication authentication) {
AuthenticationRequest authenticationRequest;
if (authentication != null) {
authenticationRequest = new AuthenticationRequest();
Map<String, String> credentials = (Map<String, String>) authentication.getCredentials();
authenticationRequest.setNewPassword(credentials.get(NEW_PASS_WORD_KEY));
authenticationRequest.setPassword(credentials.get(PASS_WORD_KEY));
authenticationRequest.setUsername(authentication.getName());
SpringSecurityUser userAuthenticated = cognitoService.authenticate(authenticationRequest);
if (userAuthenticated != null) {
Map<String, String> authenticatedCredentials = new HashMap<>();
authenticatedCredentials.put(ACCESS_TOKEN_KEY, userAuthenticated.getAccessToken());
authenticatedCredentials.put(EXPIRES_IN_KEY, userAuthenticated.getExpiresIn().toString());
authenticatedCredentials.put(ID_TOKEN_KEY, userAuthenticated.getIdToken());
authenticatedCredentials.put(PASS_WORD_KEY, userAuthenticated.getPassword());
authenticatedCredentials.put(REFRESH_TOKEN_KEY, userAuthenticated.getRefreshToken());
authenticatedCredentials.put(TOKEN_TYPE_KEY, userAuthenticated.getTokenType());
return new UsernamePasswordAuthenticationToken(
userAuthenticated.getUsername(),
authenticatedCredentials,
userAuthenticated.getAuthorities());
} else {
return null;
}
} else {
throw new UsernameNotFoundException("No application user for given username");
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
To be honest I don't know what can be done more to solve this problem with not working filter. Will be grateful for help.
Although you indicated the right ignoring pattern and Spring Security is actually ignoring the filter, I think it is being still executed because probably Spring is registering again the filter outside of the security chain because you exposed the filter with #Bean in CognitoJwtAutoConfiguration.
To avoid the problem, perform the following modifications in your code (basically, be sure that only one instance of your filter is in place). First, in WebSecurityConfiguration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableTransactionManagement
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private CustomAuthenticationProvider authProvider;
private AccountControllerExceptionHandler exceptionHandler;
private static final String LOGIN_URL = "/auth/login";
private static final String LOGOUT_URL = "/auth/signOut";
#Autowired
public WebSecurityConfiguration(
CustomAuthenticationProvider authProvider,
AccountControllerExceptionHandler exceptionHandler) {
// Do not provide AwsCognitoJwtAuthenticationFilter() as instance filed any more
this.authProvider = authProvider;
this.exceptionHandler = exceptionHandler;
}
public WebSecurityConfiguration() {
super(true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authProvider).eraseCredentials(false);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) {
// TokenAuthenticationFilter will ignore the below paths
web.ignoring().antMatchers("/auth");
web.ignoring().antMatchers("/auth/**");
web.ignoring().antMatchers("/v2/api-docs");
web.ignoring().antMatchers(GET, "/nutrition/api/**");
web.ignoring().antMatchers(GET, "/**");
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.addFilterAfter(corsFilter(), ExceptionTranslationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(new SecurityAuthenticationEntryPoint())
.accessDeniedHandler(new RestAccessDeniedHandler())
.and()
.anonymous()
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth")
.permitAll()
.anyRequest()
.authenticated()
.and()
// Instantiate a new instance of the filter
.addFilterBefore(
awsCognitoJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin(formLogin -> formLogin.loginProcessingUrl(LOGIN_URL).failureHandler(exceptionHandler))
.logout(logout -> logout.permitAll().logoutUrl(LOGOUT_URL))
.csrf(AbstractHttpConfigurer::disable);
}
private CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader(ORIGIN);
config.addAllowedHeader(CONTENT_TYPE);
config.addAllowedHeader(ACCEPT);
config.addAllowedHeader(AUTHORIZATION);
config.addAllowedMethod(GET);
config.addAllowedMethod(PUT);
config.addAllowedMethod(POST);
config.addAllowedMethod(OPTIONS);
config.addAllowedMethod(DELETE);
config.addAllowedMethod(PATCH);
config.setMaxAge(3600L);
source.registerCorsConfiguration("/v2/api-docs", config);
source.registerCorsConfiguration("/**", config);
return new CorsFilter();
}
// It will also be possible to inject AwsCognitoIdTokenProcessor
private AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter() {
return new AwsCognitoJwtAuthenticationFilter(new AwsCognitoIdTokenProcessor());
}
}
You also need to remove the unnecessary stuff from CognitoJwtAutoConfiguration:
#Configuration
#Import(AWSConfig.class)
#ConditionalOnClass({AwsCognitoJwtAuthenticationFilter.class, AwsCognitoIdTokenProcessor.class})
public class CognitoJwtAutoConfiguration {
private final AWSConfig jwtConfiguration;
public CognitoJwtAutoConfiguration(AWSConfig jwtConfiguration) {
this.jwtConfiguration = jwtConfiguration;
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public CognitoJwtIdTokenCredentialsHolder awsCognitoCredentialsHolder() {
return new CognitoJwtIdTokenCredentialsHolder();
}
/* No longer needed
#Bean
public AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor() {
return new AwsCognitoIdTokenProcessor();
}*/
#Bean
public CognitoJwtAuthenticationProvider jwtAuthenticationProvider() {
return new CognitoJwtAuthenticationProvider();
}
/* No longer needed
#Bean
public AwsCognitoJwtAuthenticationFilter awsCognitoJwtAuthenticationFilter() {
return new AwsCognitoJwtAuthenticationFilter(awsCognitoIdTokenProcessor());
}*/
#SuppressWarnings({"rawtypes", "unchecked"})
#Bean
public ConfigurableJWTProcessor configurableJWTProcessor() throws MalformedURLException {
ResourceRetriever resourceRetriever =
new DefaultResourceRetriever(CONNECTION_TIMEOUT, READ_TIMEOUT);
// https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/jwks.json.
URL jwkSetURL = new URL(jwtConfiguration.getJwkUrl());
// Creates the JSON Web Key (JWK)
JWKSource keySource = new RemoteJWKSet(jwkSetURL, resourceRetriever);
ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor();
JWSKeySelector keySelector = new JWSVerificationKeySelector(RS256, keySource);
jwtProcessor.setJWSKeySelector(keySelector);
return jwtProcessor;
}
#Bean
public AWSCognitoIdentityProvider awsCognitoIdentityProvider() {
return AWSCognitoIdentityProviderClientBuilder.standard()
.withRegion(Regions.EU_CENTRAL_1)
.withCredentials(getCredentialsProvider())
.build();
}
#Bean
public AWSCredentialsProvider getCredentialsProvider() {
return new ClasspathPropertiesFileCredentialsProvider();
}
}
I think this SO question also could be of help.

How to extract claims from token in Resource Server, in Spring Boot

My authentication server is configured to retrieve check credentials against a table on my database, with a token enhancer which I use to pass additional claims - access control related stuff.
As such, I've written it like this:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${security.signing-key}")
private String signingKey;
private #Autowired TokenStore tokenStore;
private #Autowired AuthenticationManager authenticationManager;
private #Autowired CustomUserDetailsService userDetailsService;
private #Autowired JwtAccessTokenConverter accessTokenConverter;
private static final Logger LOGGER = LogManager.getLogger(AuthorizationServerConfig.class);
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource oauthDataSource() {
DataSource ds = null;
try {
Context initialContex = new InitialContext();
ds = (DataSource) (initialContex.lookup("java:/jdbc/oauthdatasource"));
if (ds != null) {
ds.getConnection();
}
} catch (NamingException ex) {
LOGGER.error("Naming exception thrown: ", ex);
} catch (SQLException ex) {
LOGGER.info("SQL exception thrown: ", ex);
}
return ds;
}
#Bean
public JdbcClientDetailsService clientDetailsServices() {
return new JdbcClientDetailsService(oauthDataSource());
}
#Bean
public TokenStore tokenStore() {
return new CustomJdbcTokenStore(oauthDataSource());
}
#Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(oauthDataSource());
}
#Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(oauthDataSource());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
CustomTokenEnhancer converter = new CustomTokenEnhancer();
converter.setSigningKey(signingKey);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsServices());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter)
.userDetailsService(userDetailsService)
.reuseRefreshTokens(false);
}
}
This works very fine. When I make a call via POSTMAN, I get something like this:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2hhcmVwb3J0YWwiXSwiaW5mb19maXJzdCI6IlRoaXMgaXMgdGhlIGZpcnN0IEluZm8iLCJ1c2VyX25hbWUiOiJBdXRoZW50aWNhdGlvbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSIsInRydXN0Il0sImluZm9fc2Vjb25kIjoiVGhpcyBpcyB0aGUgc2Vjb25kIGluZm8iLCJleHAiOjE1ODA3MTMyOTQsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI1MTg4MGJhZC00MGJiLTQ3ZTItODRjZS1lNDUyNGY1Y2Y3MzciLCJjbGllbnRfaWQiOiJzaGFyZXBvcnRhbC1jbGllbnQifQ.ABmBjwmVDb2acZtGSQrjKcCwfZwhw4R_rpW4y5JA1jY",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic2hhcmVwb3J0YWwiXSwiaW5mb19maXJzdCI6IlRoaXMgaXMgdGhlIGZpcnN0IEluZm8iLCJ1c2VyX25hbWUiOiJBdXRoZW50aWNhdGlvbiIsInNjb3BlIjpbInJlYWQiLCJ3cml0ZSIsInRydXN0Il0sImF0aSI6IjUxODgwYmFkLTQwYmItNDdlMi04NGNlLWU0NTI0ZjVjZjczNyIsImluZm9fc2Vjb25kIjoiVGhpcyBpcyB0aGUgc2Vjb25kIGluZm8iLCJleHAiOjE1ODA3MTM0MzQsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiIyZDYxMDU2ZC01ZDMwLTRhZTQtOWMxZC0zZjliYjRiOWYxOGIiLCJjbGllbnRfaWQiOiJzaGFyZXBvcnRhbC1jbGllbnQifQ.qSLpJm4QxZTIVn1WYWH7EFBS8ryjF1hsD6RSRrEBZd0",
"expires_in": 359,
"scope": "read write trust"
}
The problem now is my resource server. This is how it used to be before I added a token enhancer to my authentication server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private #Autowired CustomAuthenticationEntryPoint entryPoint;
private #Autowired TokenStore tokenStore;
private static final String RESOURCE_ID = "resourceid";
private static final Logger LOGGER = LogManager.getLogger(ResourceServerConfig.class);
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource oauthDataSource() {
DataSource ds = null;
try {
Context initialContex = new InitialContext();
ds = (DataSource) (initialContex.lookup("java:/jdbc/oauthdatasource"));
if (ds != null) {
ds.getConnection();
}
} catch (NamingException ex) {
LOGGER.error("Naming exception thrown: ", ex);
} catch (SQLException ex) {
LOGGER.info("SQL exception thrown: ", ex);
}
return ds;
}
#Bean
public TokenStore getTokenStore() {
return new JdbcTokenStore(oauthDataSource());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')")
.antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')")
.antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')")
.and()
.headers().addHeaderWriter((request, response) -> {
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
}
})
.and().exceptionHandling().authenticationEntryPoint(entryPoint);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID).tokenStore(tokenStore).authenticationEntryPoint(entryPoint);
}
}
I wish to retrieve the access control information I've placed as additional claims via the authentication server, but I don't know how to go about it.
I saw a couple of examples on the internet, including this: How to extract claims from Spring Security OAuht2 Boot in the Resource Server?, but none of them are working for me. Or maybe I'm missing something.
Please, what do I have to add to make this possible?
I had to use a third-party library to achieve this.
This is the link to the library: https://github.com/auth0/java-jwt
It works really well.
In my resource server, I can get my token value, and then using the java-jwt library, I can extract any claims I've set in my authorization server:
public Map<String, Claim> getClaims() {
Map<String, Claim> claims = new HashMap<>();
String tokenValue = ((OAuth2AuthenticationDetails)((OAuth2Authentication) authenticationFacade.getAuthentication()).getDetails()).getTokenValue();
try {
DecodedJWT jwt = JWT.decode(tokenValue);
claims = jwt.getClaims();
} catch (JWTDecodeException ex) {
LOGGER.info("Error decoding token value");
LOGGER.error("Error decoding token value", ex);
}
return claims;
}
You should look at the documentation for java-jwt to learn more.

Why is my controller sending the content type "application/octet-stream"?

I have a REST controller:
#RequestMapping(value = "greeting", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
#Transactional(readOnly = true)
#ResponseBody
public HttpEntity<GreetingResource> greetingResource(#RequestParam(value = "message", required = false, defaultValue = "World") String message) {
GreetingResource greetingResource = new GreetingResource(String.format(TEMPLATE, message));
greetingResource.add(linkTo(methodOn(AdminController.class).greetingResource(message)).withSelfRel());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<GreetingResource>(greetingResource, responseHeaders, HttpStatus.OK);
}
As you can see, I'm trying hard to specify the content type returned by the controller.
It is accessed with a REST client:
public String getGreetingMessage() {
String message;
try {
HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + "mypassword");
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
GreetingResource greetingResource = responseEntity.getBody();
message = greetingResource.getMessage();
} catch (HttpMessageNotReadableException e) {
message = "The GET request FAILED with the message being not readable: " + e.getMessage();
} catch (HttpStatusCodeException e) {
message = "The GET request FAILED with the HttpStatusCode: " + e.getStatusCode() + "|" + e.getStatusText();
} catch (RuntimeException e) {
message = "The GET request FAILED " + ExceptionUtils.getFullStackTrace(e);
}
return message;
}
The http headers are created by a utility:
static public HttpHeaders createAuthenticationHeaders(String usernamePassword) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
byte[] encodedAuthorisation = Base64.encode(usernamePassword.getBytes());
headers.add("Authorization", "Basic " + new String(encodedAuthorisation));
return headers;
}
The web security configuration and code work fine. I make sure of this using a mockMvc based integration test which succeeds.
The only test that fails is the one based on the REST template:
#Test
public void testGreeting() throws Exception {
mockServer.expect(requestTo("/admin/greeting")).andExpect(method(HttpMethod.GET)).andRespond(withStatus(HttpStatus.OK));
String message = adminRestClient.getGreetingMessage();
mockServer.verify();
assertThat(message, allOf(containsString("Hello"), containsString("World")));
}
The exception given in the Maven build console output is:
java.lang.AssertionError:
Expected: (a string containing "Hello" and a string containing "World")
got: "The GET request FAILED org.springframework.web.client.RestClientException : Could not extract response: no suitable HttpMessageConverter found for response type [class com.thalasoft.learnintouch.rest.resource.GreetingR esource] and content type [application/octet-stream]\n\tat org.springframework.web.client.HttpMessageConverte rExtractor.extractData(HttpMessageConverterExtract or.java:107)
I'm using the Spring Framework 3.2.2.RELEASE version and the Spring Security 3.1.4.RELEASE version on the Java 1.6 version.
At first, I had a bare bone REST template:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
I have now added to it, hoping it would help:
private static final Charset UTF8 = Charset.forName("UTF-8");
#Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
GreetingResource.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
But it didn't change anything and the exception remains the same.
My understanding is that, it is not the REST template that needs any specific JSON configuration, but rather, that, for some reason, my controller is spitting out some application/octet-stream content type instead of some application/json content type.
Any clue?
Some additional information...
The admin rest client bean in the web test configuration:
#Configuration
public class WebTestConfiguration {
#Bean
public AdminRestClient adminRestClient() {
return new AdminRestClient();
}
#Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
Greeting.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
The base test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration( classes = { ApplicationConfiguration.class, WebSecurityConfig.class, WebConfiguration.class, WebTestConfiguration.class })
#Transactional
public abstract class AbstractControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
protected RestTemplate restTemplate;
protected MockRestServiceServer mockServer;
#Before
public void setup() {
this.mockServer = MockRestServiceServer.createServer(restTemplate);
}
}
The web init class:
public class WebInit implements WebApplicationInitializer {
private static Logger logger = LoggerFactory.getLogger(WebInit.class);
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerListener(servletContext);
registerDispatcherServlet(servletContext);
registerJspServlet(servletContext);
createSecurityFilter(servletContext);
}
private void registerListener(ServletContext servletContext) {
// Create the root application context
AnnotationConfigWebApplicationContext appContext = createContext(ApplicationConfiguration.class, WebSecurityConfig.class);
// Set the application display name
appContext.setDisplayName("LearnInTouch");
// Create the Spring Container shared by all servlets and filters
servletContext.addListener(new ContextLoaderListener(appContext));
}
private void registerDispatcherServlet(ServletContext servletContext) {
AnnotationConfigWebApplicationContext webApplicationContext = createContext(WebConfiguration.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(webApplicationContext));
dispatcher.setLoadOnStartup(1);
Set<String> mappingConflicts = dispatcher.addMapping("/");
if (!mappingConflicts.isEmpty()) {
for (String mappingConflict : mappingConflicts) {
logger.error("Mapping conflict: " + mappingConflict);
}
throw new IllegalStateException(
"The servlet cannot be mapped to '/'");
}
}
private void registerJspServlet(ServletContext servletContext) {
}
private AnnotationConfigWebApplicationContext createContext(final Class... modules) {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(modules);
return appContext;
}
private void createSecurityFilter(ServletContext servletContext) {
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
}
}
The web configuration:
#Configuration
#EnableWebMvc
#EnableEntityLinks
#ComponentScan(basePackages = "com.thalasoft.learnintouch.rest.controller")
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableArgumentResolver resolver = new PageableArgumentResolver();
resolver.setFallbackPageable(new PageRequest(1, 10));
resolvers.add(new ServletWebArgumentResolverAdapter(resolver));
super.addArgumentResolvers(resolvers);
}
}
The application configuration is empty for now:
#Configuration
#Import({ ApplicationContext.class })
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
// Declare "application" scope beans here, that is, beans that are not only used by the web context
}
I had my doubts before, but now that you've posted everything, here's what's up. Assuming the RestTemplate object you use in your getGreetingMessage() method is the same as the one declared in the #Bean method, the problem starts here
this.mockServer = MockRestServiceServer.createServer(restTemplate);
This call overwrites the default ClientHttpRequestFactory object that the RestTemplate object uses internally with a mock. In your getGreetingMessage() method, this call
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
doesn't actually go through the network. The RestTemplate uses the mocked ClientHttpRequestFactory to create a fake ClientHttpRequest which produces a fake ClientHttpResponse which doesn't have a Content-Type header. When the RestTemplate looks at the ClientHttpResponse to determine its Content-Type and doesn't find one, it assumes application/octet-stream by default.
So, your controller isn't setting the content type because your controller is never hit. The RestTemplate is using a default content type for your response because it is mocked and doesn't actually contain one.
From your comments:
I wonder if I understand what the mock server is testing. I understand
it is to be used in acceptance testing scenario. Is it supposed to hit
the controller at all ?
The javadoc for MockRestServiceServer states:
Main entry point for client-side REST testing. Used for tests that
involve direct or indirect (through client code)
use of the RestTemplate. Provides a way to set up fine-grained
expectations on the requests that will be performed through the
RestTemplate and a way to define the responses to send back removing
the need for an actual running server.
In other words, it's as if your application server didn't exist. So you could throw any expectations (and actual return values) you wanted and test whatever happens from the client side. So you aren't testing your server, you are testing your client.
Are you sure you aren't looking for MockMvc, which is
Main entry point for server-side Spring MVC test support.
which you can setup to actually use your #Controller beans in an integration environment. You aren't actually sending HTTP request, but the MockMvc is simulating how they would be sent and how your server would respond.
It is bug in MockHttpServletRequest and I will try to describe it.
Issue in tracker https://jira.springsource.org/browse/SPR-11308#comment-97327
Fixed in version 4.0.1
Bug
When DispatcherServlet looking for method to invoke it using some RequestConditions. One of them is ConsumesRequestCondition. The following is a piece of code:
#Override
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
try {
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM;
return getMediaType().includes(contentType);
}
catch (IllegalArgumentException ex) {
throw new HttpMediaTypeNotSupportedException(
"Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
}
}
We are interested in piece request.getContentType(). There request is MockHttpServletRequest. Let's look on method getContentType():
public String getContentType() {
return this.contentType;
}
It just return value of this.contentType. It does not return a value from the header! And this.contentType is always NULL. Then contentType in matchMediaType methos will be always MediaType.APPLICATION_OCTET_STREAM.
Solution
I have tried many ways but have found only one that works.
Create package org.springframework.test.web.client in your test directory.
Create copy of org.springframework.test.web.client.MockMvcClientHttpRequestFactory but rename it. For example rename to FixedMockMvcClientHttpRequestFactory.
Find line:
MvcResult mvcResult = MockMvcClientHttpRequestFactory.this.mockMvc.perform(requestBuilder).andReturn();
Replace it with code:
MvcResult mvcResult = FixedMockMvcClientHttpRequestFactory.this.mockMvc.perform(new RequestBuilder() {
#Override
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
MockHttpServletRequest request = requestBuilder.buildRequest(servletContext);
request.setContentType(request.getHeader("Content-Type"));
return request;
}
}).andReturn();
And register your ClientHttpReque
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory(MockMvc mockMvc) {
return new FixedMockMvcClientHttpRequestFactory(mockMvc);
}
I know that it is not beautiful solution but it works fine.

Testing Spring MultipartHttpServletRequest

Trying to test a spring controller that we have for multiple file upload. Here is the controller:
#RequestMapping("/vocabularys")
#Controller
public class VocabularyController {
...
The action I want to test:
#RequestMapping(value = "/import", method = {RequestMethod.PUT, RequestMethod.POST})
#ResponseBody
#CacheEvict(value="vocabulary", allEntries=true)
public Object importVocabulary(MultipartHttpServletRequest request, HttpServletResponse response) {
...
The resolver I have in the webmvc-config.xml:
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
The code works just fine and all. I'm running into problems when I am trying to unit/integration test this.
Here is my attempt at the test:
public class VocabularyControllerTest extends BaseControllerTest {
static final private String AdminUsername = "administrator";
#Test
public void shouldBeAbleToUploadAFile() throws Exception {
createTestWorkspace();
login(AdminUsername, "*");
MockMultipartFile file = new MockMultipartFile("test_vocab.xml", new FileInputStream("src/test/files/acme_vocabulary.xml"));
MockMultipartHttpServletRequestBuilder mockMultipartHttpServletRequestBuilder = (MockMultipartHttpServletRequestBuilder) fileUpload("/vocabularys/import").accept(MediaType.ALL).session(httpSession);
mockMultipartHttpServletRequestBuilder.file(file);
mockMultipartHttpServletRequestBuilder.content("whatever");
ResultActions resultActions = mockMvc.perform(mockMultipartHttpServletRequestBuilder);
resultActions.andExpect(status().isFound());
}
}
Ignore the createWorkspace() and login() and stuff - those are for passing through some security filters.
The relevant part of the BaseControllerTest:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextHierarchy({
#ContextConfiguration(locations = {
"file:src/test/resources/META-INF/spring/applicationContext.xml",
"file:src/test/resources/META-INF/spring/applicationContext-security.xml",
"file:src/main/resources/META-INF/spring/applicationContext-database.xml",
"file:src/main/resources/META-INF/spring/applicationContext-activiti.xml",
"file:src/main/resources/META-INF/spring/applicationContext-cache.xml",
"file:src/main/resources/META-INF/spring/applicationContext-jms.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mail.xml",
"file:src/main/resources/META-INF/spring/applicationContext-mongo.xml"}),
#ContextConfiguration(locations = {
"file:src/main/webapp/WEB-INF/spring/webmvc-config.xml",
"file:src/test/webapp/WEB-INF/spring/applicationContext-filters.xml"})
})
#Transactional
public class BaseControllerTest extends BaseTest {
#Autowired
WebApplicationContext wac;
#Autowired
MockHttpSession httpSession;
#Autowired
MockServletContext servletContext;
#Autowired
OpenEntityManagerInViewFilter openEntityManagerInViewFilter;
#Autowired
HiddenHttpMethodFilter hiddenHttpMethodFilter;
#Autowired
CharacterEncodingFilter characterEncodingFilter;
#Autowired
SessionFilter sessionFilter;
#Autowired
WorkflowAsSessionFilter workflowAsSessionFilter;
#Autowired
FilterChainProxy springSecurityFilterChain;
#Autowired
RequestFilter requestFilter;
MockMvc mockMvc;
protected static final String TestFileDir = "src/test/files/";
#Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(openEntityManagerInViewFilter, "/*")
.addFilter(hiddenHttpMethodFilter, "/*")
.addFilter(characterEncodingFilter, "/*")
.addFilter(sessionFilter, "/*")
.addFilter(workflowAsSessionFilter, "/*")
.addFilter(springSecurityFilterChain, "/*")
.addFilter(requestFilter, "/*")
.build();
servletContext.setContextPath("/");
Session session = Session.findBySessionId(httpSession.getId());
if (session == null) {
session = new Session();
session.setJsessionid(httpSession.getId());
session.persist();
}
}
...
The issue is that when I try debugging this, the perform action on the mockMvc object never hits my controller method. I thought it was an issue getting past our security filters (which is why I have all the login and stuff) but I tested other actions in the vocabulary controller and I am able to hit them just fine.
Thoughts? Ideas? Suggestions?
Alright, found the issue.
Spring's MockMultipartHttpServletRequestBuilder returns a MockHttpMultipartServletRequest object eventually.
What the browser does however is post a multipart-encoded request which then gets picked up and parsed by the CommonsMultipartResolver bean defined in the XML.
In the test however, since we are already posting a MockHttpMultipartServletRequest, we don't want the resolver parsing this, so all we got to do is have a profile where the resolver doesn't kick in.
What we have chosen to do however is end up constructing a MockHttpServletRequest that has multipart encoding and put it through the Spring filters so that we can also integration test the resolver kicking in.
Unfortunately I don't see any support/helper in the Spring testing lib which allows you to take a MockHttpServletRequest and addPart() to it, or something to that effect => handcoded browser emulation function :(
The simple way how to test multipart upload is use StandardServletMultipartResolver.
and for test use this code:
final MockPart profilePicture = new MockPart("profilePicture", "stview.jpg", "image/gif", "dsdsdsd".getBytes());
final MockPart userData = new MockPart("userData", "userData", "application/json", "{\"name\":\"test aida\"}".getBytes());
this.mockMvc.perform(
fileUpload("/endUsers/" + usr.getId().toString()).with(new RequestPostProcessor() {
#Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.addPart(profilePicture);
request.addPart(userData);
return request;
}
})
MockPart class
public class MockPart extends MockMultipartFile implements Part {
private Map<String, String> headers;
public MockPart(String name, byte[] content) {
super(name, content);
init();
}
public MockPart(String name, InputStream contentStream) throws IOException {
super(name, contentStream);
init();
}
public MockPart(String name, String originalFilename, String contentType, byte[] content) {
super(name, originalFilename, contentType, content);
init();
}
public MockPart(String name, String originalFilename, String contentType, InputStream contentStream) throws IOException {
super(name, originalFilename, contentType, contentStream);
init();
}
public void init() {
this.headers = new HashMap<String, String>();
if (getOriginalFilename() != null) {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"; filename=\"" + getOriginalFilename() + "\"");
} else {
this.headers.put("Content-Disposition".toLowerCase(), "form-data; name=\"" + getName() + "\"");
}
if (getContentType() != null) {
this.headers.put("Content-Type".toLowerCase(), getContentType());
}
}
#Override
public void write(String fileName) throws IOException {
}
#Override
public void delete() throws IOException {
}
#Override
public String getHeader(String name) {
return this.headers.get(name.toLowerCase());
}
#Override
public Collection<String> getHeaders(String name) {
List<String> res = new ArrayList<String>();
if (getHeader(name) != null) {
res.add(getHeader(name));
}
return res;
}
#Override
public Collection<String> getHeaderNames() {
return this.headers.keySet();
}
}

Categories

Resources