I'm using azure keyvault to pull my application properties. I'm using spring #value annotation to set the property value from the keyvault by placing the placeholder in the application.properties file. In my main application context I was able to pull the properties and test the application flow. Were as in test context its throwing some issuing saying vault properties aren't injected. Here is my properties bean class looks like, and the stack trace of the issue. I tried to mock the KeyVaultProperties in the ControllerTest class still having same issue.
KeyVault.java
#Data
#Component
public class KeyVaultProperties {
#Value("${by-pass-token}")
private String token;
#Value("${backend-clients}")
private String clients;
}
ControllerTest.java
#SpringBootTest
#SpringBootConfiguration
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
Controller controller;
#Autowired
private MockMvc mockMvc;
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
}
Controller.java
#RestController
#Slf4j
#RequestMapping("/api/test")
public class Controller {
#GetMapping(value = "/hello")
public String getString() {
return "Hello";
}
}
AuthConfiguration.java
#Slf4j
#Component
public class AuthConfiguration extends HandlerInterceptorAdapter {
#Autowired
private KeyVaultProperties keyVaultProperties;
private static final String CORRELATION_ID_LOG_VAR_NAME = "correlationId";
private static final String CORRELATION_ID_HEADER_NAME = "Correlation-Id";
#PostConstruct
public void setup() {
System.out.println("-------#PostConstruct------setup----------------");
sub = keyVaultProperties.getClients();
ByPass = keyVaultProperties.getAuthByPassToken();
}
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
System.out.println("-------preHandle----------------------");
final Boolean isValidToken;
final String correlationId = getCorrelationIdFromHeader(request);
log.info("correlationId:{}",correlationId);
MDC.put(CORRELATION_ID_LOG_VAR_NAME, correlationId);
return true;
}
#Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
final Object handler, final Exception ex) {
System.out.println("-------afterCompletion----------------------");
MDC.remove(CORRELATION_ID_LOG_VAR_NAME);
}
private String getCorrelationIdFromHeader(final HttpServletRequest request) {
String correlationId = request.getHeader(CORRELATION_ID_HEADER_NAME);
if (correlationId == null) {
correlationId = generateUniqueCorrelationId();
}
return correlationId;
}
}
app/src/main/resources/application.properties
by-pass-token = ${BY-PASS-TOKEN}
backend-clients = ${CLIENTS}
azure.keyvault.enabled=true
Stack Trace:
2021-04-04 13:28:03.640 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AuthConfiguration': Unsatisfied dependency expressed through field 'KeyVaultProperties'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'KeyVaultProperties': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'by-pass-token' in value "${by-pass-token}"
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject
You could set the value of properties to Azure Key Vault by authenticating via Azure AD.
Note: In order for your application to have access to the Key Vault contents, you must set the appropriate permissions for your application in the Key Vault. Navigate to Azure Key Vault > Access Policies > Add access policy > select your application in select principal.
Dependencies:
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-keyvault</artifactId>
<version>1.0.0</version>
</dependency>
Connect to key vault via AzureAD based on client credentials flow:
public class ClientSecretKeyVaultCredential extends KeyVaultCredentials
{
private String clientId;
private String clientKey;
public ClientSecretKeyVaultCredential( String clientId, String clientKey ) {
this.clientId = clientId;
this.clientKey = clientKey;
}
#Override
public String doAuthenticate(String authorization, String resource, String scope) {
AuthenticationResult token = getAccessTokenFromClientCredentials(
authorization, resource, clientId, clientKey);
return token.getAccessToken();
}
private static AuthenticationResult getAccessTokenFromClientCredentials(
String authorization, String resource, String clientId, String clientKey) {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(authorization, false, service);
ClientCredential credentials = new ClientCredential(clientId, clientKey);
Future<AuthenticationResult> future = context.acquireToken(
resource, credentials, null);
result = future.get();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
service.shutdown();
}
if (result == null) {
throw new RuntimeException("authentication result was null");
}
return result;
}
}
Access Key vault:
You could use client.setSecret("Secret-Name", "value") to set your properties.
// ClientSecretKeyVaultCredential is the implementation of KeyVaultCredentials
KeyVaultClient client = new KeyVaultClient(
new ClientSecretKeyVaultCredential(clientId, clientKey));
// KEYVAULT_URL is the location of the keyvault to use: https://<yourkeyvault>.vault.azure.net
SecretBundle secret = client.getSecret( KEYVAULT_URL, "Secret-name" );
log( secret.value() );
Related
I have a spring service:
#Service
public class AuthorizationServiceImpl implements AuthorizationService {
#Value("${local.address}")
private String localIP;
private final String LOCALHOST_IPV4 = "127.0.0.1";
private final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
#CrossOrigin(origins = "*")
#Override
public boolean isAuthorized(String token, HttpServletRequest request) {
try {
TokenUserModel user = new TokenUserModel();
user.setSessionID("");
user = getTokenUserModel(token);
IServiceAAA client = new ServiceAAA().getBasicHttpBindingIServiceAAA();
int systemId = client.getUserSystemId("InvoiceAdmin");
String xRealIP = request.getRemoteAddr();
String ipAddress = xRealIP.equals(LOCALHOST_IPV4) || xRealIP.equals(LOCALHOST_IPV6) ? localIP : xRealIP;
String userAgent = request.getHeader("User-Agent");
int response = client.checkPermissionAndUserData(user.getSessionID(), "Admin",
userAgent,
ipAddress, systemId, systemId, "");
return response == 0;
} catch (Exception ex) {
throw new AuthorizationException(AuthorizationError.ERR_WHILE_AUTHORISATION);
}
}
}
#Value("${local.address}")
private String localIP; - this line of code didn't working, not populating local.address field from application.properties:
local.address=10.10.16.13
Update:
I have 3 application properties file for spring profiles, named:
application.properties,application-dev.properties,application-prod.properties,
my current active profile is application-dev.properties
('spring.profiles.active=dev' - it's inside of my
application.properties file).
I'm sure that 'local.address'
property is defined in application-dev.properties
local.address=10.10.16.13
AuthorizationService is a just interface that AutheroziationServiceImpl impliments.
public interface AuthorizationService {
boolean isAuthorized(String token, HttpServletRequest request) throws UnsupportedEncodingException;
void logout(String token) throws UnsupportedEncodingException;
TokenUserModel getTokenUserModel(String token) throws UnsupportedEncodingException;
}
I'm using it in constructor of one of controllers:
#Autowired
public AuthorizationController(AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
private final AuthorizationService authorizationService;
Yes, it's spring managed bean annotated as #Service
I'm developping a Java library that uses Spring framework.
On this library I created a class that will act as application manager. It uses the singleton design pattern.
This class uses dependency injection for external HTTP calls.
public class SharePointManager {
private static SharePointManager instance = null;
private static IAuthenticationResult iAuthenticationResult;
private static String token;
private static SiteService siteService;
public static SharePointManager getInstance() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
SharePointManager.dummyService = context.getBean(DummyService.class);
SharePointManager.siteService = context.getBean(SiteService.class);
if(instance == null) {
instance = new SharePointManager();
}
return instance;
}
private SharePointManager() {
}
public SharePointSiteResponse getAllSites(SharePointCredentialRequest creds) throws Exception {
if( iAuthenticationResult != null && iAuthenticationResult.accessToken() != null ) {
token = iAuthenticationResult.accessToken();
if (Utils.checkToken(iAuthenticationResult.accessToken())) {
token = Utils.getToken(creds).accessToken();
}
} else {
token = Utils.getToken(creds).accessToken();
}
token = iAuthenticationResult.accessToken();
return siteService.getAllSites(creds, token);
}
}
In my Service layer I also want to do dependency injection with a HttpRequest class :
#Service
public class SiteServiceImpl implements SiteService {
private final HttpRequest httpRequest;
public SiteServiceImpl(HttpRequest httpRequest) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
this.httpRequest = context.getBean(HttpRequest.class);
}
#Override
public SharePointSiteResponse getAllSites(SharePointCredentialRequest credentialRequest, String token) throws Exception {
if(Utils.checkToken(token))
token = Utils.createToken(credentialRequest);
URL url = new URL("https://graph.microsoft.com/v1.0/sites?search=*");
return httpRequest.getAllSitesRequest(token, url);
}
}
In my httpRequest class i just build my calls:
#Component
public class HttpRequest {
private final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH").disableHtmlEscaping().create();
public SharePointSiteResponse getAllSitesRequest(String token, URL url)
throws IOException, RequestException {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
return getSiteConnection(token, url, conn);
}
....
My AppConfig class is just here for configuration:
#Configuration
#ComponentScan(basePackages = {"fr.dsidiff.sharepoint"})
public class AppConfig {
}
I obtain an error message :
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class org.springframework.beans.CachedIntrospectionResults
at org.springframework.context.support.AbstractApplicationContext.resetCommonCaches(AbstractApplicationContext.java:969)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
at fr.dsidiff.sharepoint.SharePointManager.getInstance(SharePointManager.java:20)
I tried two solutions that solved my issue in my service layer:
#Service
public class SiteServiceImpl implements SiteService {
private final HttpRequest httpRequest;
#Autowired
public SiteServiceImpl(HttpRequest httpRequest) {
this.httpRequest = httpRequest;
}
...
I also tried javax #Inject too and it works
I didn't expect i could inject using autowired...
I am working with Spring boot and Spring security to build a back end application, I am storing users into Cloud Firestore NonRelational Database, I am using an Admin SDK token provided by Google on the Firebase platform. I am initializing my Firestore in the following way.
#Service
public class UserFirestoreInitialize {
#Value("classpath:static/gamingplatform-c922d-firebase-adminsdk-c25o8-06e92edfd5.json")
Resource resourceFile;
#PostConstruct
public void initialize() {
try {
InputStream serviceAccount = resourceFile.getInputStream();
GoogleCredentials cred = GoogleCredentials.fromStream(serviceAccount)
.createScoped("https://www.googleapis.com/auth/datastore");
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(cred)
.setDatabaseUrl("FIREBASE_URL")
.build();
FirebaseApp.initializeApp(options);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
I also had a Configuration class which looks like this.
#Configuration
public class UserFirestoreConfiguration{
#Bean
public Firestore getFirestore(){
return FirestoreClient.getFirestore();
}
}
After this, I could easily use this bean in my UserService as follows:
#Service
public class UserService{
#Autowired
private Firestore firestore;
public User getUser(){
//Query for user.
}
//Post, Put, delete
}
This worked at some point. The problem came when I added Spring Security into my applicatio. When I ran a Maven Install, the application did not build, the problem looks like this:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jwtTokenFilter': Unsatisfied dependency expressed through field 'userDetailsService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userServiceDetailsImpl' defined in file [/Users/igorzelaya/SoftwareDev/D1Gaming-User-Back-end1/target/classes/com/d1gaming/user/security/UserServiceDetailsImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userService' defined in file [/Users/igorzelaya/SoftwareDev/D1Gaming-User-Back-end1/target/classes/com/d1gaming/user/user/UserService.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.d1gaming.user.user.UserService]: Constructor threw exception; nested exception is java.lang.IllegalStateException: FirebaseApp with name [DEFAULT] doesn't exist.
I have a tried to isolate the problem, I created the another project and tried connecting to my database like I did in the snippet above^, everything worked just fine, I then copied my configuration classes one by one, I figured my application started to crash the moment I added my JWT Token Filter class, this class looks like this
#Component
public class JwtTokenFilter extends OncePerRequestFilter{
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserServiceDetailsImpl userDetailsService;
#Override
//Get authorization header and validate it.
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException, NullPointerException {
try {
String jwt = parseJwt(request);
if(jwt != null && jwtTokenUtil.validate(jwt)) {
String username = jwtTokenUtil.getUserNameFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
catch(Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
chain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if(StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7, headerAuth.length());
}
return null;
}
}
As you can notice on my stack trace shown above, the error starts on the dependencies injected in this class, the jwtTokenUtil class and the UserDetailsImpl
the JwtTokenUtil class is this:
#Component
public class JwtTokenUtil {
#Value("${app.jwtSecret}")
private String jwtSecret;
#Value("${app.jwtExpirationMs}")
private int jwtExpirationMs;
private final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.setSubject(userPrincipal.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret)
.parseClaimsJws(token).getBody().getSubject();
}
public String getUserId(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject().split(",")[0];
}
public String getUsername(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getSubject().split(",")[0];
}
public Date getExpirationDate(String token) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
return claims.getExpiration();
}
public boolean validate(String token) {
try {
Jwts.parser()
.setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
}
catch(SignatureException e) {
logger.error("Invalid JWT signature - {}",e.getMessage());
}
catch(MalformedJwtException e) {
logger.error("Invalid JWT token - {}", e.getMessage());
}
catch(ExpiredJwtException e) {
logger.error("Invalid JWT token - {}",e.getMessage());
}
catch(UnsupportedJwtException e) {
logger.error("Invalid JWT token - {}", e.getMessage());
}
catch(IllegalArgumentException e) {
logger.error("Invalid JWT token - {}", e.getMessage());
}
return false;
}
}
The UserServiceDetailsImpl class:
public class UserDetailsImpl implements UserDetails{
private static final long serialVersionUID = 1L;
private String userId;
private String userRealName;
private String userName;
#JsonIgnore
private String userPassword;
private String userEmail;
private UserStatus userStatusCode;
private Team userTeam;
private Map<String,Object> userBilling;
private String userCountry;
private int userTokens;
private double userCash;
private Map<String, Object> userBirthDate;
private Collection<? extends GrantedAuthority> authorities;
public UserDetailsImpl(String userId, String userRealName, String userName, String userPassword, String userEmail,
UserStatus userStatusCode, Team userTeam, Map<String, Object> userBilling, String userCountry,
int userTokens, double userCash, Map<String, Object> userBirthDate,
Collection<? extends GrantedAuthority> authorities) {
this.userId = userId;
this.userRealName = userRealName;
this.userName = userName;
this.userPassword = userPassword;
this.userEmail = userEmail;
this.userStatusCode = userStatusCode;
this.userTeam = userTeam;
this.userBilling = userBilling;
this.userCountry = userCountry;
this.userTokens = userTokens;
this.userCash = userCash;
this.userBirthDate = userBirthDate;
this.authorities = authorities;
}
public static UserDetailsImpl build(User user) {
List<GrantedAuthority> authorities = user.getUserRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getRoleType().name()))
.collect(Collectors.toList());
return new UserDetailsImpl(user.getUserId(),user.getUserRealName(),user.getUserName(),user.getUserPassword(),user.getUserEmail()
,user.getStatusCode(),user.getUserTeam(),user.getUserBilling(),user.getUserCountry(),
user.getUserTokens(),user.getUserCash(),user.getUserBirthDate(),authorities
);
}
//Getters and setters
}
As you can see, these classes both have nothing to do with Firestore, At the moment I was pretty confused so I did the following. I noticed that for some strange reason the #PostConstruct annotation was not working, Spring was not initializing my initializeFirestore() method on the first snippet of code, so I placed a initialize() method directly on spring boot as follows:
#SpringBootApplication
public class UserApplication(){
public static void main(String[] args) {
//Basically does the same as the initialize method mentioned before.
UserFirestoreUtils.initialize(UserFirestoreUtils.getOptions());
SpringApplication.run(D1GamingUserBackEnd1Application.class, args);
}
}
The interesting thing is that when I debugged this I had the following error:
The dependencies of some of the beans in the application context form a cycle:
jwtTokenFilter (field private com.d1gaming.user.security.UserServiceDetailsImpl com.d1gaming.user.security.JwtTokenFilter.userDetailsService)
┌─────┐
| userServiceDetailsImpl defined in file [/Users/igorzelaya/SoftwareDev/D1Gaming-User-Back-end1/target/classes/com/d1gaming/user/security/UserServiceDetailsImpl.class]
↑ ↓
| userService (field private org.springframework.security.crypto.password.PasswordEncoder com.d1gaming.user.user.UserService.passwordEncoder)
↑ ↓
| userSecurityConfiguration (field com.d1gaming.user.security.UserServiceDetailsImpl com.d1gaming.user.security.UserSecurityConfiguration.userDetailsService)
└─────┘
The weird thing is that when I run a Maven install on it, the stack trace is the same as the one I showed at the beginning of the question, I feel there is not enough documentation on Cloud Firestore and its normal considering it is "New", but still it was annoying because I couldn't find the "Right way" of doing this and Google Documentation was clearly not enough. I am sorry If I included way too much code, I think it is necessary, but anyways thank you for your time, I appreciate if anyone could help me. Have a nice day.
This looks like a collision between Spring dependencies and Firebase Admin. You can try with Firestore client library. Firebase admin SDK is a wider library that some part of code can collision with Spring security
I have some spring service that can submit some AWS batch job. This is simple spring batch job that invoke requst to external service. And i want to propagate traceId that generated in my service by including "org.springframework.cloud:spring-cloud-starter-sleuth" lib into classpath , to this job and add "TraceRestTemplateInterceptor" interceptor to external request initilaized with this traceId.
How can i do that? How can i initilaze interceptor which will put existing traceId from application parameter, environment, properties?
Or may be need to create some configuration beans?
UPDATE:
Simplified example:
#SpringBootApplication
public class DemoApplication implements CommandLineRunner {
Logger logger = LoggerFactory.getLogger(DemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
//#Autowired
//RestTemplate restTemplate;
#Override
public void run(String... args) {
logger.info("Hello, world!");
//restTemplate.getForObject("some_url", String.class);
}
}
File application.properties:
x-b3-traceId=98519d97ce87553d
File build.gradle:
dependencies {
implementation('org.springframework.cloud:spring-cloud-starter-sleuth')
}
Output:
INFO [-,,,] 15048 --- [ main] com.example.demo.DemoApplication : Hello, world!
First of all, I want to see here traceId which initilized in application.properties. Secondly, when uncomment resttemplate clause, this traceId propagated into request.
Is it possible?
Resolved this issue only by manually putting into request HEADER key "X-B3-TRACEID" with corresponding value, which is inserted by external application as system property when submits target spring boot application. And manually inserting this key in MDC. Example, this snipet from spring boot application that must get traceId and propagate:
#Bean
public void setTraceIdToMDC(#Value("${x.b3.traceid}") String traceId) {
MDC.put("x-b3-traceId", traceId);
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Bean
public CommandLineRunner commandLineRunnerer(RestTemplate restTemplate, #Value("${x.b3.traceid}") String traceId) {
return args -> {
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
header.add("X-B3-TRACEID", traceId);
HttpEntity httpEntity = new HttpEntity(header);
logger.info("Execute some request"); //<-- prints expected traceId
restTemplate.exchange("some_url", HttpMethod.GET, httpEntity, String.class);
};
}
You can get the bean:
#Autowired
private Tracer tracer;
And get the traceId with
tracer.getCurrentSpan().traceIdString();
Just add the dependency to the classpath and set rest template as a bean. That's enough.
As spring sleuth doesn't support Webservicetemplate by default, here is an example of how to use spring cloud sleuth with Webservicetemplate,
if service A sends a request to service B,
At first you'll send the trace id in the header of the sent request by the below code
#Service
public class WebServiceMessageCallbackImpl implements WebServiceMessageCallback {
#Autowired
private Tracer tracer;
public void doWithMessage(WebServiceMessage webServiceMessage) throws TransformerException {
Span span = tracer.currentSpan();
String traceId = span.context().traceId();
SoapMessage soapMessage = (SoapMessage) webServiceMessage;
SoapHeader header = soapMessage.getSoapHeader();
StringSource headerSource = new StringSource("<traceId>" + traceId + "</traceId>");
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(headerSource, header.getResult());
}
}
then in service B, you'll create an interceptor, then read the trace id from the header of the coming request, then put this trace id in the MDC like in the below code
#Slf4j
#Component
public class HttpInterceptor2 extends OncePerRequestFilter {
private final String traceId = "traceId";
#Autowired
private Tracer tracer;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
String traceId = traceId(payload);
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove(traceId);
}
}
private String traceId(String payload) {
StringBuilder token = new StringBuilder();
if (payload.contains(traceId)) {
int index = payload.indexOf(traceId);
while (index < payload.length() && payload.charAt(index) != '>') {
index++;
}
index++;
for (int i = index; ; i++) {
if (payload.charAt(i) == '<') {
break;
}
token.append(payload.charAt(i));
}
}
if (token.toString().trim().isEmpty()) {
token.append(traceId());
}
return token.toString().trim();
}
private String traceId() {
Span span = tracer.currentSpan();
String traceId = span.context().traceId();
return traceId;
}
}
So I created a custom AuthenticationFilter for a Jersey Rest service. Locally it deploys and works as intended but when deployed to our actual test server I get the below error when starting Wildfly server.
Caused by: A MultiException has 3 exceptions. They are:
1. java.lang.IllegalStateException: A descriptor SystemDescriptor( implementation=org.glassfish.jersey.server.internal.process.ServerProcess>ingBinder$UriRoutingContextFactory
contracts={org.glassfish.jersey.server.ExtendedUriInfo,javax.ws.rs.core.UriInfo,javax.ws.rs.container.ResourceInfo}
scope=org.glassfish.jersey.process.internal.RequestScoped
qualifiers={}
descriptorType=PROVIDE_METHOD
descriptorVisibility=NORMAL
metadata=
rank=0
loader=org.glassfish.hk2.utilities.binding.AbstractBinder$2#19fb4349
proxiable=true
proxyForSameScope=false
analysisName=null
id=16
locatorId=5
identityHashCode=481594768
reified=true) requires a proxy, but the proxyable library is not on the classpath
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.lanyon.rest.service.filters.AuthenticationFilter errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.lanyon.rest.service.filters.AuthenticationFilter
Caused by: java.lang.IllegalStateException: A descriptor SystemDescriptor(
implementation=org.glassfish.jersey.server.internal.process.ServerProcess>ingBinder$UriRoutingContextFactory
contracts={org.glassfish.jersey.server.ExtendedUriInfo,javax.ws.rs.core.UriInfo,javax.ws.rs.container.ResourceInfo}
scope=org.glassfish.jersey.process.internal.RequestScoped
qualifiers={}
descriptorType=PROVIDE_METHOD
descriptorVisibility=NORMAL
metadata=
rank=0
loader=org.glassfish.hk2.utilities.binding.AbstractBinder$2#19fb4349
proxiable=true
proxyForSameScope=false
analysisName=null
id=16
locatorId=5
identityHashCode=481594768
reified=true) requires a proxy, but the proxyable library is not on the classpath"}}}}
Here is the Auth filter:
#Provider
public class AuthenticationFilter implements javax.ws.rs.container.ContainerRequestFilter {
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final String AUTHENTICATION_SCHEME = "Basic";
private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED)
.entity("Access Denied").build();
private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN)
.entity("Forbidden").build();
private static final String WS_CREDENTIALS_KEY = "DEMAND_WEBSERVICE_CREDENTIALS";
private static final String DEFAULT_DAO_BEAN_NAME = "exchangeDefaultsDAO";
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) {
Method method = getResourceMethod();
if (isAnnotationPresent(method, PermitAll.class)) {
return;
}
if (isAnnotationPresent(method, DenyAll.class)) {
requestContext.abortWith(ACCESS_FORBIDDEN);
return;
}
final List<String> authorization = getAuthorizationHeaders(requestContext);
if (authorization == null || authorization.isEmpty()) {
requestContext.abortWith(ACCESS_DENIED);
return;
}
String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
final String authToken = new String(encodedUserPassword.getBytes());
if (isAnnotationPresent(method, RolesAllowed.class)) {
RolesAllowed rolesAnnotation = getRolesAllowedAnnotation(method);
Set<String> rolesSet = new HashSet<>(Arrays.asList(rolesAnnotation.value()));
if (!isUserAllowed(authToken, rolesSet)) {
requestContext.abortWith(ACCESS_DENIED);
return;
}
}
}
private boolean isUserAllowed(final String authToken, final Set<String> rolesSet) {
String[] webServiceCredentials = getWebServiceCredentials();
if (webServiceCredentials.length != 2) {
return false;
}
return authToken.equals(webServiceCredentials[0]) && rolesSet.contains(webServiceCredentials[1]);
}
private String[] getWebServiceCredentials() {
return getExchangeDefaultsBean().getDefault(WS_CREDENTIALS_KEY).split("\\|");
}
private Method getResourceMethod() {
return resourceInfo.getResourceMethod();
}
private List<String> getAuthorizationHeaders(ContainerRequestContext requestContext) {
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
return authorization;
}
private boolean isAnnotationPresent(Method method, Class<? extends Annotation> annotation) {
return method.isAnnotationPresent(annotation);
}
private RolesAllowed getRolesAllowedAnnotation(Method method) {
return method.getAnnotation(RolesAllowed.class);
}
private ExchangeDefaultsDAO getExchangeDefaultsBean() {
return (ExchangeDefaultsDAO)ApplicationContextProvider.getApplicationContext().getBean(DEFAULT_DAO_BEAN_NAME);
}
}
Then the Jersey Application:
public class JerseyApplication extends ResourceConfig {
public JerseyApplication() {
register(AuthenticationFilter.class);
register(TestAPIService.class);
}
}
Finally the Rest service:
#Path("/somepathname")
public class TestAPIService {
#RolesAllowed("API_ADMIN")
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response getRejectReasons() {
return Response.status(Response.Status.OK).entity("Working as expected!").build();
}
}
I have added javaassist to my classpath but that did nothing. Any help you can provide would be greatly appreciated.
Cheers!