I installed Vault locally. I was able to start local dev server and write/read some secrets into Vault kv based on this official tutorial https://learn.hashicorp.com/vault/
Then I wanted to create some very basic Java/Spring Boot demo client that would connect to my local Vault dev server in order to write/read secrets. I read Baeldung tutorial for inspiration https://www.baeldung.com/spring-vault.
This is my vault-config.properties:
vault.uri=http://127.0.0.1:8200
vault.token=s.EXg6MQwUuB63Z7Xra4zybOut (token generated after the latest start of server)
Then service class:
#Service
public class CredentialsService {
#Autowired
private VaultTemplate vaultTemplate;
public void secureCredentials(Credentials credentials) throws URISyntaxException {
vaultTemplate.write("credentials/myapp", credentials);
}
public Credentials accessCredentials() throws URISyntaxException {
VaultResponseSupport<Credentials> response = vaultTemplate.read("credentials/myapp", Credentials.class);
return response.getData();
}
}
Configuration class:
#Configuration
public class VaultConfig extends AbstractVaultConfiguration {
#Override
public ClientAuthentication clientAuthentication() {
return new TokenAuthentication("s.EXg6MQwUuB63Z7Xra4zybOut");
}
#Override
public VaultEndpoint vaultEndpoint() {
return VaultEndpoint.create("host", 8200);
}
}
and this:
#Configuration
#PropertySource(value = { "vault-config.properties" })
#Import(value = EnvironmentVaultConfiguration.class)
public class VaultEnvironmentConfig {
}
One domain object:
public class Credentials {
private String username;
private String password;
public Credentials() {
}
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
#Override
public String toString() {
return "Credential [username=" + username + ", password=" + password + "]";
}
}
And finally my main Spring Boot class:
#RestController
#ComponentScan
#SpringBootApplication
public class SpringVaultTutorial {
#Autowired
CredentialsService credentialsService;
#RequestMapping("/")
String home() throws URISyntaxException {
Credentials credentials = new Credentials("oliver","exxeta123");
credentialsService.secureCredentials(credentials);
return credentialsService.accessCredentials().getUsername().toString();
}
public static void main(String[] args) {
SpringApplication.run(SpringVaultTutorial.class, args);
}
}
Main class should write secret and then immediately read it and print username. But I am getting this error message:
There was an unexpected error (type=Internal Server Error, status=500).
I/O error on POST request for "https://host:8200/v1/credentials/myapp": host; nested exception is java.net.UnknownHostException: host
Does somebody have a clue what can be wrong?
EDIT:
Based on advice from Arun I followed this tutorial https://drissamri.be/blog/java/enable-https-in-spring-boot/
I have been trying both approaches.
1) Modify application.properties:
server.port: 8443
server.ssl.key-store: keystore.p12
server.ssl.key-store-password: oliver
server.ssl.keyStoreType: PKCS12
server.ssl.keyAlias: tomcat
security.require-ssl=true
After modification, when I call https://localhost:8443, I am getting Exception:
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at sun.security.ssl.InputRecord.handleUnknownRecord(InputRecord.java:710) ~[na:1.8.0_121]
at sun.security.ssl.InputRecord.read(InputRecord.java:527) ~[na:1.8.0_121]
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:973) ~[na:1.8.0_121]
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375) ~[na:1.8.0_121]
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403) ~[na:1.8.0_121]
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387) ~[na:1.8.0_121]
2) Second approach based on tutorial is about adding ConnectorConfig class:
#Configuration
public class ConnectorConfig {
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat =
new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(redirectConnector());
return tomcat;
}
private Connector redirectConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8090);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}
But after calling localhost:8090 that redirects me to https://localhost:8443, I am getting the same error: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at sun.security.ssl.InputRecord.handleUnknownRecord(InputRecord.java:710) ~
Now the question is: Do I have to configure something also on the Vault side regarding certificate? Or do you think there could be some certificate problem on Java client side? But I thing if there was Java certificate problem, exception would be thrown already during startup.
Problem solved. Now I am able to connect to local Vault from Java client. I paste code here if someone in the future wants to run simple Java Client - Vault demo.
Controller:
#RestController
#RequestMapping(Paths.ROOT)
#Api(value = Paths.ROOT, description = "Endpoint for core testing")
public class Controller {
#Autowired
CredentialsService credentialsService;
#GetMapping("/")
String home() throws URISyntaxException {
Credentials credentials = new Credentials("oliver", "exxeta123");
credentialsService.secureCredentials(credentials);
return credentialsService.accessCredentials().toString();
}
#GetMapping("/test")
public String test() throws IOException {
// http://127.0.0.1:8200/v1/sys/internal/ui/mounts/secret/mysecrets
VaultConfig vc = new VaultConfig();
String bearerToken = vc.clientAuthentication().login().getToken();
System.out.println(bearerToken);
// credentialsService.accessCredentials()
// Sending get request
//URL url = new URL("http://127.0.0.1:8200/v1/sys/internal/ui/mounts/secret/mysecrets");
// URL updated to match readme.adoc
URL url = new URL("http://127.0.0.1:8200/v1/kv/my-secret");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Authorization", "Bearer " + bearerToken);
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String output;
StringBuffer response = new StringBuffer();
while ((output = in.readLine()) != null) {
response.append(output);
}
in.close();
// printing result from response
return "Response: - " + response.toString();
}
#GetMapping(value = { "/add/{name}/{username}/{password}" })
public ResponseEntity<String> addKey(#PathVariable(value = "name", required = false, name = "name") String name,
#PathVariable(value = "username", required = false, name = "username") String username,
#PathVariable(value = "password", required = false, name = "password") String password) throws URISyntaxException {
Credentials credentials = new Credentials(username, password);
credentialsService.secureCredentials(name, credentials);
return new ResponseEntity<>("Add success: " + credentialsService.accessCredentials(name).getUsername(), HttpStatus.OK);
}
#GetMapping(value = {"/get", "/get/{name}"})
public ResponseEntity<Credentials> getKey(#PathVariable(value = "name", required = false, name = "name") String name) {
return new ResponseEntity<>(credentialsService.accessCredentials(name), HttpStatus.OK);
}
#GetMapping(value= {"/delete", "/delete/{name}"})
public String removeKey(#PathVariable(value = "name", required = false, name = "name") String name) {
return "Delete success: " + credentialsService.deleteCredentials(name);
}
}
Service:
#Service
public class CredentialsService {
private VaultTemplate vaultTemplate;
/**
* To Secure Credentials
*
* #param credentials
* #return VaultResponse
* #throws URISyntaxException
*/
public void secureCredentials(Credentials credentials) throws URISyntaxException {
//vaultTemplate.write("credentials/myapp", credentials);
initVaultTemplate();
vaultTemplate.write("kv/myapp", credentials);
}
public void secureCredentials(String storagePlace, Credentials credentials) {
initVaultTemplate();
vaultTemplate.write("kv/" + storagePlace, credentials);
}
/**
* To Retrieve Credentials
*
* #return Credentials
* #throws URISyntaxException
*/
public Credentials accessCredentials() throws URISyntaxException {
//VaultResponseSupport<Credentials> response = vaultTemplate.read("credentials/myapp", Credentials.class);
initVaultTemplate();
VaultResponseSupport<Credentials> response = vaultTemplate.read("kv/myapp", Credentials.class);
return response.getData();
// TODO special case when there are no values
}
/**
* #param nameOfsecrets key name
* #return if is presented or empty object
*/
public Credentials accessCredentials(String nameOfsecrets) {
initVaultTemplate();
VaultResponseSupport<Credentials> response = vaultTemplate.read("kv/" + nameOfsecrets, Credentials.class);
if (response != null) {
return response.getData();
} else {
return new Credentials();
}
}
public boolean deleteCredentials(String name) {
initVaultTemplate();
vaultTemplate.delete("kv/" + name);
return true;
}
}
private void initVaultTemplate() {
VaultEndpoint endpoint = new VaultEndpoint();
endpoint.setHost("localhost");
endpoint.setPort(8200);
endpoint.setScheme("http");
vaultTemplate = new VaultTemplate(endpoint, new VaultConfig().clientAuthentication());
}
VaultConfig:
#Configuration
public class VaultConfig extends AbstractVaultConfiguration {
#Override
public ClientAuthentication clientAuthentication() {
return new TokenAuthentication("00000000-0000-0000-0000-000000000000");
}
#Override
public VaultEndpoint vaultEndpoint() {
return VaultEndpoint.create("localhost", 8200);
}
}
VaultEnvironmentConfig:
#Configuration
#PropertySource(value = { "vault-config.properties" })
#Import(value = EnvironmentVaultConfiguration.class)
public class VaultEnvironmentConfig {
}
Main class:
#SpringBootApplication
#EnableSwagger2
public class SpringVaultTutorial {
public static void main(String[] args) {
SpringApplication.run(SpringVaultTutorial.class, args);
}
//SWAGGER DOCUMENTATION BEANS
// default group contains all endpoints
#Bean
public Docket defaultApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())//all
.build().apiInfo(apiInfo());
}
// Management group contains Spring Actuator endpoints
#Bean
public Docket swaggerAdminEndpoints() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName(Paths.ROOT)
.apiInfo(apiInfo())
.select()
.paths(PathSelectors.regex("/v1/.*"))
.build()
.forCodeGeneration(true);
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Vault Demo Application")
.description("Demo Application using vault")
.version("1.0")
.build();
}
}
vault-config.properties:
vault.uri=http://127.0.0.1:8200
vault.token=00000000-0000-0000-0000-000000000000
application.properties:
server.port=8443
server.ssl.key-alias=selfsigned_localhost_sslserver
server.ssl.key-password=changeit
server.ssl.key-store=classpath:ssl-server.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS
Paths:
public class Paths {
public static final String ROOT = "/v1";
}
Credentials:
public class Credentials {
private String username;
private String password;
public Credentials() {
}
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
#Override
public String toString() {
return "Credential [username=" + username + ", password=" + password + "]";
}
}
UnknownHostException is due to no server is available with the name 'host'. you can either add an entry in hosts file map to localhost. or try changing the host name while creating vault as
#Override
public VaultEndpoint vaultEndpoint() {
return VaultEndpoint.create("localhost", 8200);
}
Related
I have a web application that uses Kafka and Spring WebSocket for emitting different events to a mobile app. Events are triggered based on the logic where mobile app is online or offline.
The logic works perfectly fine if an event is triggered when mobile app is already online. But if it's offline, the triggered event is stored in a postgres table and marked with status PENDING. So when the mobile app comes back online, these table searches for the message and then triggers the event.
In order to update the mobile status, I am using Socket Sessions, and ChannelInterceptor which updates the mobile app status on SUBSCRIBE and DISCONNECT event.
I have logs at every possible place, and all the logs are executed when the mobile app comes back online. There's no error anywhere. The last log is right before :
messagingTemplate.convertAndSendToUser();
Logs :
Device Status in request : true
Send Mode : IMMEDIATE
inside immediate never expire >>>>>
Enter: SocketService.send() with argument[s] = [{Status=200, Response={PayloadId=1415, PayloadExpiryDateTime=0, PayloadPriority=1, EventType=NEW_MESSAGE, TemporaryMessage={Messages={Data=[{Title=Message1, DataToSend={Message=Message1, Data=bZijp8pcSKwnbRhnm2khEUkaonv3XpZ6PnS0IRP+ab4p1ub2Cmuuylga6z8RASwPWbTbxbZH5ickk4HcWKeEM1Qq8PHnmAEj/VmnLmhP9UbITDrFbzWaeUcAhdWuFzD1QKfp4lmjsFl6LeswN6x+tgFi+mimURK9EaKuKiQKnP3sZHfw6Bk2o/jH4ik8hoKetO2GRxNOKs/8H9NYUS4hLP/RP3vJttUtHo3fiJd3WwWcxT+w+YZneDFmc28ECjpoJ0ZzgV7NRJlhKBcOMl7V6eV2Rax7yNC2k7zyHH/ylkhkq74heWAtde/S43S6WLTMbtc2+t1MgHNOevsxEyI/ghBE1wJzgBIdvlhFX4M1jam+AYHyXhF7CNC/Hr/i+dpw3/SogNQwIidW8FZ5EWwBJP2nYv0eZ0owK/8zwEs3FZWF9fRUeUGn7VwFNFDsf4P1FUbeWcwHA847iHBuUF2qDVtLNgQtyejGKxhn3+a8yoKyU96cpteOmGmbZPuyU3xRk401gkk4a0noS3SOjWO8B0OKyq782K3gRUoVssPV8Hg=, ReqNr=9.0000001E7}}]}, Playlists={Data=[{Title=TemporaryPlaylist, DataToSend={Message=Temporary Playlist, Data=G7Prf2ZRUh+MSBNTjLH2BT7nNB1o8zXgz/mn9leZ7Q4UoZH1MzMrJj3ISvdkQZNAbVCnYQjh5XhOnUr1kGrZpWJ/lHUyJi1coc7fdMxe0fkgBJk9SySvaFN/TvFQaUlv2wRWIoPVXmLVhukFgzLsDeVWmuzNaWZXTQCemIzrK6SaCJ+FfFAzgxG2Z7QxWkVx, ReqNr=9.0000002E7}}]}, ShowCommands={Data=[{DataToSend={Type=ShowPlaylistRequest, ShowAll=[{CallWord=t2212161322}]}}]}}}, Message=Success}, message, 108]
Sending to Producer : {"Status":200,"Response":{"PayloadId":1415,"PayloadExpiryDateTime":0,"PayloadPriority":1,"EventType":"NEW_MESSAGE","TemporaryMessage":{"Messages":{"Data":[{"Title":"Message1","DataToSend":{"Message":"Message1","Data":"bZijp8pcSKwnbRhnm2khEUkaonv3XpZ6PnS0IRP+ab4p1ub2Cmuuylga6z8RASwPWbTbxbZH5ickk4HcWKeEM1Qq8PHnmAEj/VmnLmhP9UbITDrFbzWaeUcAhdWuFzD1QKfp4lmjsFl6LeswN6x+tgFi+mimURK9EaKuKiQKnP3sZHfw6Bk2o/jH4ik8hoKetO2GRxNOKs/8H9NYUS4hLP/RP3vJttUtHo3fiJd3WwWcxT+w+YZneDFmc28ECjpoJ0ZzgV7NRJlhKBcOMl7V6eV2Rax7yNC2k7zyHH/ylkhkq74heWAtde/S43S6WLTMbtc2+t1MgHNOevsxEyI/ghBE1wJzgBIdvlhFX4M1jam+AYHyXhF7CNC/Hr/i+dpw3/SogNQwIidW8FZ5EWwBJP2nYv0eZ0owK/8zwEs3FZWF9fRUeUGn7VwFNFDsf4P1FUbeWcwHA847iHBuUF2qDVtLNgQtyejGKxhn3+a8yoKyU96cpteOmGmbZPuyU3xRk401gkk4a0noS3SOjWO8B0OKyq782K3gRUoVssPV8Hg=","ReqNr":9.0000001E7}}]},"Playlists":{"Data":[{"Title":"TemporaryPlaylist","DataToSend":{"Message":"Temporary Playlist","Data":"G7Prf2ZRUh+MSBNTjLH2BT7nNB1o8zXgz/mn9leZ7Q4UoZH1MzMrJj3ISvdkQZNAbVCnYQjh5XhOnUr1kGrZpWJ/lHUyJi1coc7fdMxe0fkgBJk9SySvaFN/TvFQaUlv2wRWIoPVXmLVhukFgzLsDeVWmuzNaWZXTQCemIzrK6SaCJ+FfFAzgxG2Z7QxWkVx","ReqNr":9.0000002E7}}]},"ShowCommands":{"Data":[{"DataToSend":{"Type":"ShowPlaylistRequest","ShowAll":[{"CallWord":"t2212161322"}]}}]}}},"Message":"Success"}
Exit: SocketService.send() with result = null
Received from Kafka : {"Status":200,"Response":{"PayloadId":1415,"PayloadExpiryDateTime":0,"PayloadPriority":1,"EventType":"NEW_MESSAGE","TemporaryMessage":{"Messages":{"Data":[{"Title":"Message1","DataToSend":{"Message":"Message1","Data":"bZijp8pcSKwnbRhnm2khEUkaonv3XpZ6PnS0IRP+ab4p1ub2Cmuuylga6z8RASwPWbTbxbZH5ickk4HcWKeEM1Qq8PHnmAEj/VmnLmhP9UbITDrFbzWaeUcAhdWuFzD1QKfp4lmjsFl6LeswN6x+tgFi+mimURK9EaKuKiQKnP3sZHfw6Bk2o/jH4ik8hoKetO2GRxNOKs/8H9NYUS4hLP/RP3vJttUtHo3fiJd3WwWcxT+w+YZneDFmc28ECjpoJ0ZzgV7NRJlhKBcOMl7V6eV2Rax7yNC2k7zyHH/ylkhkq74heWAtde/S43S6WLTMbtc2+t1MgHNOevsxEyI/ghBE1wJzgBIdvlhFX4M1jam+AYHyXhF7CNC/Hr/i+dpw3/SogNQwIidW8FZ5EWwBJP2nYv0eZ0owK/8zwEs3FZWF9fRUeUGn7VwFNFDsf4P1FUbeWcwHA847iHBuUF2qDVtLNgQtyejGKxhn3+a8yoKyU96cpteOmGmbZPuyU3xRk401gkk4a0noS3SOjWO8B0OKyq782K3gRUoVssPV8Hg=","ReqNr":9.0000001E7}}]},"Playlists":{"Data":[{"Title":"TemporaryPlaylist","DataToSend":{"Message":"Temporary Playlist","Data":"G7Prf2ZRUh+MSBNTjLH2BT7nNB1o8zXgz/mn9leZ7Q4UoZH1MzMrJj3ISvdkQZNAbVCnYQjh5XhOnUr1kGrZpWJ/lHUyJi1coc7fdMxe0fkgBJk9SySvaFN/TvFQaUlv2wRWIoPVXmLVhukFgzLsDeVWmuzNaWZXTQCemIzrK6SaCJ+FfFAzgxG2Z7QxWkVx","ReqNr":9.0000002E7}}]},"ShowCommands":{"Data":[{"DataToSend":{"Type":"ShowPlaylistRequest","ShowAll":[{"CallWord":"t2212161322"}]}}]}}},"Message":"Success"}
Sending to Socket Client.......
But the front-end never gets the message.
Below are some code snippets:
Channel Interceptor:
public class SocketInterceptor implements ChannelInterceptor {
#Autowired
SocketSessionService socketSessionService;
private final Logger log = LoggerFactory.getLogger(SocketInterceptor.class);
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
MessageHeaders headers = message.getHeaders();
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
MultiValueMap<String, String> multiValueMap = headers.get(StompHeaderAccessor.NATIVE_HEADERS,MultiValueMap.class);
if (multiValueMap !=null) {
if (multiValueMap.containsKey("destination")) {
String destination = multiValueMap.get("destination").get(0);
log.info("Destination in interceptor : " + destination);
}
}
if (accessor.getCommand()!=null) {
log.info("Client Method : " + accessor.getCommand().name());
if (accessor.getCommand().equals(StompCommand.CONNECT) || accessor.getCommand().equals(StompCommand.SUBSCRIBE) || accessor.getCommand().equals(StompCommand.SEND)) {
if (!multiValueMap.containsKey("access-token")) {
log.error("Token Error " + "No Token was provided in + " + accessor.getCommand().name());
throw new MessagingException("Forbidden: No Token was provided in header for Socket Command: " + accessor.getCommand().name());
}
String token = multiValueMap.get("access-token").get(0);
if (JwtTokenUtil.verifyToken(JwtTokenUtil.decrypt(token))) {
log.info("Token Verified Successfully >>>>>>>>>>>>>>>>>>>> ");
Claims claims = JwtTokenUtil.verifyJwtToken(JwtTokenUtil.decrypt(token));
accessor.setHeader("access-token", token);
Principal principal = new UsernamePasswordAuthenticationToken(claims.getId(), null, null);
accessor.setUser(principal);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
claims.getId(), null, null);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
if (accessor.getCommand().equals(StompCommand.SUBSCRIBE)){
log.info("Storing Socket Session on Socket Command SUBSCRIBE ");
SocketSessionModel model=new SocketSessionModel();
model.setSessionId(accessor.getSessionId());
model.setDeviceId(multiValueMap.get("destination").get(0));
model.setAccessToken(token);
model.setCurrentStatus(SocketSessionStatus.ONLINE);
model.setReportedOnlineAt(ZonedDateTime.now().with(ZoneOffset.UTC));
model.setReportedOfflineAt(null);
socketSessionService.onSubscribe(model);
}
ObjectMapper objectMapper= new ObjectMapper();
try {
log.info("Returning back the frame : " + objectMapper.writeValueAsString(message.getPayload()));
} catch (Exception e){
e.printStackTrace();
}
return new GenericMessage<>(message.getPayload(), accessor.getMessageHeaders());
}
throw new MessagingException("Token either Expired or Invalid");
}
}
return message;
}
}
WebSocket Config :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
Logger log= LoggerFactory.getLogger(WebSocketConfig.class);
#Autowired
SocketSessionService socketSessionService;
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/user")
.setTaskScheduler(customTaskScheduler())
.setHeartbeatValue(new long[]{25000,10000});
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(socketInterceptor());
}
#Bean
public SocketInterceptor socketInterceptor() {
return new SocketInterceptor();
}
#Bean
public TaskScheduler customTaskScheduler(){
return new ThreadPoolTaskScheduler();
}
#EventListener(SessionDisconnectEvent.class)
public void handleWsDisconnectListener(SessionDisconnectEvent event) {
Optional<String> sessionId= getSessionIdFromEvent(event);
sessionId.ifPresent(s -> socketSessionService.onDisconnect(s));
}
private Optional<String> getSessionIdFromEvent(AbstractSubProtocolEvent event) {
String sessionId = null;
System.out.println("Session Id in event : " + (event.getMessage().getHeaders()).get("simpSessionId"));
Object sessionIdAsObject = (event.getMessage().getHeaders()).get("simpSessionId");
if (nonNull(sessionIdAsObject) && sessionIdAsObject.getClass().equals(String.class)) {
sessionId = (String) sessionIdAsObject;
}
return Optional.ofNullable(StringUtils.isEmpty(sessionId) ? null : sessionId);
}
}
Kafka Producer Config :
#Service
public class KafkaProducerConfig {
#Autowired
private KafkaTemplate<String, String> kafkaTemplate;
#Async
public void send(String data, String kafkaTopic, String key) {
kafkaTemplate.send(kafkaTopic, key, data);
}
#Async
public void send(String topic, String data ){
kafkaTemplate.send(topic, data);
}
}
KafkaConsumer Config :
#EnableKafka
#Configuration
public class KafkaConsumerConfig {
#Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
public ConsumerFactory<String, String> consumerFactory(String groupId) {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(props);
}
#Bean
public ConcurrentKafkaListenerContainerFactory<String, String> rawKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory("something"));
return factory;
}
}
Kafka Listener :
This is the last step where socket emits the messages to clients. The last line of log mentioned here is executed.
#Component
public class KafkaListenerForSocket {
private final Logger log= LoggerFactory.getLogger(KafkaListenerForSocket.class);
#Autowired
SimpMessagingTemplate messagingTemplate;
#KafkaListener(topics = KafkaTopicConstants.MESSAGE, containerFactory = "rawKafkaListenerContainerFactory")
public void listenToMessages(ConsumerRecord<String, String> consumerRecord) throws IOException {
ObjectMapper objectMapper=new ObjectMapper();
String destination="/queue/message";
BaseSocketResponse response=objectMapper.readValue(consumerRecord.value(), BaseSocketResponse.class);
messagingTemplate.convertAndSendToUser(consumerRecord.key(),destination,response);
}
public void send(Object data, String key){
log.info("Bypassing Kafka and sending directly");
String destination="/queue/message";
log.info("sending to destination : " + "/user/"+key+destination);
messagingTemplate.convertAndSendToUser(key, destination, data);
}
}
BaseSocketResponse.Java class :
public class BaseSocketResponse<T> {
#JsonProperty(value = "Status", required = true)
private Integer status;
#JsonProperty(value = "Message", required = true)
private String statusMessage = "";
#JsonProperty(value = "Response", required = true)
private T response;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getStatusMessage() {
return statusMessage;
}
public void setStatusMessage(String statusMessage) {
this.statusMessage = statusMessage;
}
public T getResponse() {
return response;
}
public void setResponse(T response) {
this.response = response;
}
}
front-end code :
var stompClient = null;
function setConnected(connected) {
$("#connect").prop("disabled", connected);
$("#disconnect").prop("disabled", !connected);
if (connected) {
$("#conversation").show();
}
else {
$("#conversation").hide();
}
$("#userinfo").html("");
}
function connect() {
let header={"access-token" : $("#token").val()};
var socket = new SockJS('/services/toplight/ws');
stompClient = Stomp.over(socket);
stompClient.connect(header, function (frame) {
if(frame.command == "CONNECTED") {
console.log("Inside frame command : >>>>> " + frame);
setConnected(true);
subscribe(stompClient)
}
}, (error) => {
console.log("Inside Connect error : " + error );
onConnectError(error);
});
}
function subscribe(stompClient){
var token=$("#token").val();
var header={"access-token": token};
console.log("Subscribing to Destination : " + '/user/'+$("#name").val()+'/queue/message');
stompClient.subscribe('/user/'+$("#name").val()+'/queue/message' , greeting, header);
var greeting=function(message){
var body=JSON.parse(message.body);
if(body.Response.EventType=="NEW_MESSAGE"){
showNewMessage(response);
}
if(body.Response.EventType=="DEVICE_UPDATE_RESPONSE"){
showDeviceUpdate(response);
}
if(body.Response.EventType=="MESSAGE_UPDATE_RESPONSE"){
showMessageUpdate(response);
}
};
}
function onConnectError(error){
console.log("Error : >>>>> " + error);
$("#greetings").append("<h3><tr><td>" + error + "</h3>");
}
I'm trying to get the the Principal user name from Spring websocket SessionConnectEvent but it is null on every listener. What I can be doing wrong?
To implement it I followed the answers you will find here: how to capture connection event in my webSocket server with Spring 4?
#Slf4j
#Service
public class SessionEventListener {
#EventListener
private void handleSessionConnect(SessionConnectEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
String sessionId = headers.getSessionId();
log.debug("sessionId is " + sessionId);
String username = headers.getUser().getName(); // headers.getUser() is null
log.debug("username is " + username);
}
#EventListener
private void handleSessionConnected(SessionConnectEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
String sessionId = headers.getSessionId();
log.debug("sessionId is " + sessionId);
String username = headers.getUser().getName(); // headers.getUser() is null
log.debug("username is " + username);
}
#EventListener
private void handleSubscribeEvent(SessionSubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
String sessionId = headers.getSessionId();
log.debug("sessionId is " + sessionId);
String subscriptionId = headers.getSubscriptionId();
log.debug("subscriptionId is " + subscriptionId);
String username = headers.getUser().getName(); // headers.getUser() is null
log.debug("username is " + username);
}
#EventListener
private void handleUnsubscribeEvent(SessionUnsubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
String sessionId = headers.getSessionId();
log.debug("sessionId is " + sessionId);
String subscriptionId = headers.getSubscriptionId();
log.debug("subscriptionId is " + subscriptionId);
String username = headers.getUser().getName(); // headers.getUser() is null
log.debug("username is " + username);
}
#EventListener
private void handleSessionDisconnect(SessionDisconnectEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
log.debug("sessionId is " + event.getSessionId());
String username = headers.getUser().getName(); // headers.getUser() is null
log.debug("username is " + username);
}
}
This is my security config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.permitAll()
.and().csrf().disable();
}
}
As I'm not implementing an authentication mechanisim, Spring has no enough information to provide a Principal user name. So what I had to do is to configure a HandshakeHandler that generates the Principal.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
public static final String ENDPOINT_CONNECT = "/connect";
public static final String SUBSCRIBE_USER_PREFIX = "/private";
public static final String SUBSCRIBE_USER_REPLY = "/reply";
public static final String SUBSCRIBE_QUEUE = "/queue";
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker(SUBSCRIBE_QUEUE, SUBSCRIBE_USER_REPLY);
registry.setUserDestinationPrefix(SUBSCRIBE_USER_PREFIX);
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint(ENDPOINT_CONNECT)
// assign a random username as principal for each websocket client
// this is needed to be able to communicate with a specific client
.setHandshakeHandler(new AssignPrincipalHandshakeHandler())
.setAllowedOrigins("*");
}
}
/**
* Assign a random username as principal for each websocket client. This is
* needed to be able to communicate with a specific client.
*/
public class AssignPrincipalHandshakeHandler extends DefaultHandshakeHandler {
private static final String ATTR_PRINCIPAL = "__principal__";
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
final String name;
if (!attributes.containsKey(ATTR_PRINCIPAL)) {
name = generateRandomUsername();
attributes.put(ATTR_PRINCIPAL, name);
} else {
name = (String) attributes.get(ATTR_PRINCIPAL);
}
return new Principal() {
#Override
public String getName() {
return name;
}
};
}
private String generateRandomUsername() {
RandomStringGenerator randomStringGenerator =
new RandomStringGenerator.Builder()
.withinRange('0', 'z')
.filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS).build();
return randomStringGenerator.generate(32);
}
}
Looking into the implementation of AbstractSubProtocolEvent (the superclass of all the events you're interested in) you can see that the user is hold in a seperate field. So you can simply access the user by calling event.getUser(). You don't need to get it from the message.
E.g. for the SessionConnectedEvent you can see that the user gets populated in the event but not the message.
Update:
You can only access the user when you authenticated the http upgrade. So you need to have a WebSecurityConfigurerAdapter that configures something like:
#Configuration
public static class UserWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers(WebsocketPaths.WEBSOCKET_HANDSHAKE_PREFIX); //You configured the path in WebSocketMessageBrokerConfigurer#registerStompEndpoints
http
.authorizeRequests()
.anyRequest().authenticated();
}
}
In my first developmentstep i logged in with my FeignClient.
That works well.
public class FeignClientConfiguration {
public String userkrz = "abc";
public String password = "123xy456";
#Bean // ({"authBean"})
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor( userkrz, password );
}
}
public class FeignClientConfiguration {
public String userkrz = "abc";
public String password = "123xy456";
#Bean // ({"authBean"})
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor( userkrz, password );
}
}
But the client authorized by starting the client and I dont know how I authorized me later.
I would set the credentials concerning the runtime.
I want spring-vault configuration marked with VaultPropertySource to be able to retry the requests to the vault if they fail.
What should i mark as retryable ? I'm using Spring-Retry and i was looking over http://www.baeldung.com/spring-retry .
There is no visible method to mark as retryable. Should I change the implementation of the vaultTemplate and mark the vaultOperations as retryable ?
ProvisioningSecrets.java
#Configuration
#VaultPropertySource(
value="secret/provisioning",
propertyNamePrefix = "provisioning.",
renewal = Renewal.RENEW
)
#EnableRetry
#Lazy
#Profile("!test")
public class ProvisioningSecrets {
private static final Logger logger = LoggerFactory.getLogger(ProvisioningSecrets.class);
#Autowired
public void setPassword(#Value("${provisioning.password}") final String password) throws Exception {
logger.info("We successfully set the provisioning db password.");
EnvVars.changeSetting(Setting.PROVISIONING_PASS, password);
}
#Autowired
public void setHost(#Value("${provisioning.host}") final String host) throws Exception {
logger.info("We successfully set the provisioning db host.");
EnvVars.changeSetting(Setting.PROVISIONING_HOST, host);
}
#Autowired
public void setPort(#Value("${provisioning.port}") final int port) throws Exception {
logger.info("We successfully set the provisioning db port.");
EnvVars.changeSetting(Setting.PROVISIONING_PORT, Integer.toString(port));
}
#Autowired
public void setUsername(#Value("${provisioning.username}") final String username) throws Exception {
logger.info("We successfully set the provisioning db username.");
EnvVars.changeSetting(Setting.PROVISIONING_USER, username);
}
#Autowired
public void setDbName(#Value("${provisioning.name}") final String name) throws Exception {
logger.info("We successfully set the provisioning db name.");
EnvVars.changeSetting(Setting.PROVISIONING_DB_NAME, name);
}
}
VaultConfiguration.java
#Configuration
#Profile("!test")
public class VaultConfiguration extends AbstractVaultConfiguration {
private static final Logger logger = LoggerFactory.getLogger(VaultConfiguration.class);
private URI vaultHost;
private String vaultToken;
/**
* Configure the Client Authentication.
*
* #return A configured ClientAuthentication Object.
* #see ClientAuthentication
*/
#Override
public ClientAuthentication clientAuthentication() {
// testing out environment variable value injection
logger.debug("Vault Token configuration done.");
return new TokenAuthentication(vaultToken);
}
#Override
#Bean
#DependsOn("vaultToken")
public SessionManager sessionManager() {
return super.sessionManager();
}
#Override
public SslConfiguration sslConfiguration() {
logger.info("Configuring Vault SSL with NONE.");
return SslConfiguration.NONE;
}
/**
* Specify an endpoint for connecting to Vault.
*
* #return A configured VaultEndpoint.
* #see VaultEndpoint
*/
#Override
public VaultEndpoint vaultEndpoint() {
logger.debug("Vault Host:" + vaultHost.toString());
if (vaultHost.toString().isEmpty()) {
logger.info("Creating default Vault Endpoint.");
return new VaultEndpoint();
}
logger.info("Creating Vault Endpoint based on address: " + vaultHost.toString());
final VaultEndpoint endpoint = VaultEndpoint.from(vaultHost);
logger.info("Created Vault Endpoint: " + endpoint.toString());
return endpoint;
}
#Bean("vaultHost")
public URI vaultHost(#Value("${spring.vault.host}") final URI vaultHost) {
this.vaultHost = vaultHost;
return vaultHost;
}
#Override
#Bean
#DependsOn("vaultHost")
public VaultTemplate vaultTemplate() {
return super.vaultTemplate();
}
#Bean("vaultToken")
public String vaultToken(#Value("${spring.vault.token}") final String vaultToken) {
this.vaultToken = vaultToken;
return vaultToken;
}
}
How about creating a custom VaultTemplate bean class using RetryTemplate?
public class RetryableVaultTemplate extends VaultTemplate {
private final RetryTemplate retryTemplate;
public RetryableVaultTemplate(VaultEndpointProvider endpointProvider,
ClientHttpRequestFactory clientHttpRequestFactory,
SessionManager sessionManager, RetryTemplate retryTemplate) {
super(endpointProvider, clientHttpRequestFactory, sessionManager);
this.retryTemplate = retryTemplate;
}
#Override
public VaultResponse read(final String path) {
return retryTemplate
.execute(new RetryCallback<VaultResponse, RuntimeException>() {
#Override
public VaultResponse doWithRetry(RetryContext context) {
System.out.println("doWithRetry");
return RetryableVaultTemplate.super.read(path);
}
});
}
#Override
public <T> VaultResponseSupport<T> read(final String path, final Class<T> responseType) {
return retryTemplate
.execute(new RetryCallback<VaultResponseSupport<T>, RuntimeException>() {
#Override
public VaultResponseSupport<T> doWithRetry(RetryContext context) {
return RetryableVaultTemplate.super.read(path, responseType);
}
});
}
}
Make sure to register this bean class as vaultTemplate bean instead of VaultTemplate.
Using Dropwizard 1.2.0 and Dropwizard JWT library, I am trying to create json web tokens from an API endpoint called /token
This endpoint requires the client to pass a username and password, using Basic Authentication method. If successful the response will contain a JSON web token.
Principal
public class ShepherdAuth implements JwtCookiePrincipal {
private String name;
private Set<String> roles;
public ShepherdAuth(String name, Set<String> roles) {
this.name = checkNotNull(name, "User name is required");
this.roles = checkNotNull(roles, "Roles are required");
}
#Override
public boolean isPersistent() {
return false;
}
#Override
public boolean isInRole(final String s) {
return false;
}
#Override
public String getName() {
return this.name;
}
#Override
public boolean implies(Subject subject) {
return false;
}
public Set<String> getRoles() {
return roles;
}
}
Authenticator
public class ShepherdAuthenticator implements Authenticator<BasicCredentials, ShepherdAuth> {
private static final Map<String, Set<String>> VALID_USERS = ImmutableMap.of(
"guest", ImmutableSet.of(),
"shepherd", ImmutableSet.of("SHEPHERD"),
"admin", ImmutableSet.of("ADMIN", "SHEPHERD")
);
#Override
public Optional<ShepherdAuth> authenticate(BasicCredentials credentials) throws AuthenticationException {
if (VALID_USERS.containsKey(credentials.getUsername()) && "password".equals(credentials.getPassword())) {
return Optional.of(new ShepherdAuth(credentials.getUsername(), VALID_USERS.get(credentials.getUsername())));
}
return Optional.empty();
}
}
Resource / Controller
public class ShepherdController implements ShepherdApi {
public ShepherdController() {
}
#PermitAll
#GET
#Path("/token")
public ShepherdAuth auth(#Auth final BasicCredentials user) {
return new ShepherdAuth(user.getUsername(),
ImmutableSet.of("SHEPHERD"));
}
App / Config
#Override
public void run(final ShepherdServiceConfiguration configuration,
final Environment environment) {
final ShepherdController shepherdController = new ShepherdController();
// app authentication
environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<ShepherdAuth>()
.setAuthenticator(new ShepherdAuthenticator())
.setAuthorizer(new ShepherdAuthorizer())
.setRealm(configuration.getName())
.buildAuthFilter()));
When I try to make a request to /shepherd/token I do not get a prompt for basic auth, instead I get a HTTP 401 response with
Credentials are required to access this resource.
How do I get the controller to prompt for username and password and generate a JWT on success?
I implemented JWT tokens in my project by using https://github.com/jwtk/jjwt but the solution will easily be applied to another library. The trick is to use different authenticators.
This answer is not suited for Dropwizard JWT Library but does the fine job of providing JWT for Dropwizard :)
First, the application:
environment.jersey().register(new TokenResource(configuration.getJwsSecretKey()));
environment.jersey().register(new HelloResource());
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
environment.jersey()
.register(
new AuthDynamicFeature(
new ChainedAuthFilter<>(
Arrays
.asList(
new JWTCredentialAuthFilter.Builder<User>()
.setAuthenticator(
new JWTAuthenticator(configuration.getJwsSecretKey()))
.setPrefix("Bearer").setAuthorizer(new UserAuthorizer())
.buildAuthFilter(),
new JWTDefaultCredentialAuthFilter.Builder<User>()
.setAuthenticator(new JWTDefaultAuthenticator())
.setAuthorizer(new UserAuthorizer()).setRealm("SUPER SECRET STUFF")
.buildAuthFilter()))));
Please note that the configuration class must contain a configuration setting:
String jwsSecretKey;
Here, the TokenResource is the token supplying resource, and the HelloResource is our test resource. User is the principal, like this:
public class User implements Principal {
private String name;
private String password;
...
}
And there is one class for communicating the JWT token:
public class JWTCredentials {
private String jwtToken;
...
}
TokenResource provides tokens for a user "test" with password "test":
#POST
#Path("{user}")
#PermitAll
public String createToken(#PathParam("user") String user, String password) {
if ("test".equals(user) && "test".equals(password)) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(this.secretKey);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder builder = Jwts.builder().setIssuedAt(now).setSubject("test")
.signWith(signatureAlgorithm, signingKey);
return builder.compact();
}
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
And the HelloResource just echoes back the user:
#GET
#RolesAllowed({"ANY"})
public String hello(#Auth User user) {
return "hello user \"" + user.getName() + "\"";
}
JWTCredentialAuthFilter provides credentials for both authentication schemes:
#Priority(Priorities.AUTHENTICATION)
public class JWTCredentialAuthFilter<P extends Principal> extends AuthFilter<JWTCredentials, P> {
public static class Builder<P extends Principal>
extends AuthFilterBuilder<JWTCredentials, P, JWTCredentialAuthFilter<P>> {
#Override
protected JWTCredentialAuthFilter<P> newInstance() {
return new JWTCredentialAuthFilter<>();
}
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
final JWTCredentials credentials =
getCredentials(requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
if (!authenticate(requestContext, credentials, "JWT")) {
throw new WebApplicationException(
this.unauthorizedHandler.buildResponse(this.prefix, this.realm));
}
}
private static JWTCredentials getCredentials(String authLine) {
if (authLine != null && authLine.startsWith("Bearer ")) {
JWTCredentials result = new JWTCredentials();
result.setJwtToken(authLine.substring(7));
return result;
}
return null;
}
}
JWTAuthenticator is when JWT credentials are provided:
public class JWTAuthenticator implements Authenticator<JWTCredentials, User> {
private String secret;
public JWTAuthenticator(String jwtsecret) {
this.secret = jwtsecret;
}
#Override
public Optional<User> authenticate(JWTCredentials credentials) throws AuthenticationException {
try {
Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(this.secret))
.parseClaimsJws(credentials.getJwtToken()).getBody();
User user = new User();
user.setName(claims.getSubject());
return Optional.ofNullable(user);
} catch (#SuppressWarnings("unused") ExpiredJwtException | UnsupportedJwtException
| MalformedJwtException | SignatureException | IllegalArgumentException e) {
return Optional.empty();
}
}
}
JWTDefaultAuthenticator is when no credentials are present, giving the code an empty user:
public class JWTDefaultAuthenticator implements Authenticator<JWTCredentials, User> {
#Override
public Optional<User> authenticate(JWTCredentials credentials) throws AuthenticationException {
return Optional.of(new User());
}
}
UserAuthorizer permits the "ANY" role, as long as the user is not null:
public class UserAuthorizer implements Authorizer<User> {
#Override
public boolean authorize(User user, String role) {
return user != null && "ANY".equals(role)
}
}
If all goes well,
curl -s -X POST -d 'test' http://localhost:8080/token/test
will give you something like:
eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MDk3MDYwMjYsInN1YiI6InRlc3QifQ.ZrRmWTUDpaA6JlU4ysIcFllxtqvUS2OPbCMJgyou_tY
and this query
curl -s -X POST -d 'xtest' http://localhost:8080/token/test
will fail with
{"code":401,"message":"HTTP 401 Unauthorized"}
(BTW, "test" in the URL is the user name and "test" in the post data is the password. As easy as basic auth, and can be configured for CORS.)
and the request
curl -s -X GET -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MDk3MDYwMjYsInN1YiI6InRlc3QifQ.ZrRmWTUDpaA6JlU4ysIcFllxtqvUS2OPbCMJgyou_tY' http://localhost:8080/hello
will show
hello user "test"
while
curl -s -X GET -H 'Authorization: Bearer invalid' http://localhost:8080/hello
and
curl -s -X GET http://localhost:8080/hello
will result in
{"code":403,"message":"User not authorized."}
Your missing this line in your configuration.
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));