How to add basic auth using retrofit2? - java

I already search all around the places on internet how to add basic auth using retrofit2 but still no luck. I already implemented a simple login mechanism but basic auth must be use in order for successful login.
My model
public class ResObj {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
ApiUtils.java
public class ApiUtils {
public static final String BASE_URL = "xxxxx";
public static UserService getUserService(){
return RetrofitClient.getClient(BASE_URL).create(UserService.class);
}
}
RetrofitClient.java
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String url){
if(retrofit == null){
retrofit = new Retrofit.Builder().baseUrl(url).addConverterFactory(GsonConverterFactory.create()).build();
}
return retrofit;
}
}
UserService.java
public interface UserService {
#GET("login?username={username}&password={password}")
Call<ResObj> login(#Path("username") String username, #Path("password") String password);
}
LoginActivity.java
private void doLogin(String username, String password){
Call<ResObj> call = userService.login(username, password);
call.enqueue(new Callback<ResObj>() {
#Override
public void onResponse(Call<ResObj> call, Response<ResObj> response) {
if(response.isSuccessful()){
ResObj resObj = response.body();
if(resObj.getMessage().equals("true")){
Intent intent = new Intent(TextLoginActivity.this, MainActivity.class);
startActivity(intent);
} else {
Toast.makeText(TextLoginActivity.this, "The username and password is incorrect", Toast.LENGTH_SHORT).show();
}
} else{
Toast.makeText(TextLoginActivity.this, "Error! Please try again!", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<ResObj> call, Throwable t) {
Toast.makeText(TextLoginActivity.this, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}

As I see your login API request, it exposes the user's password and the username in the query. The least you can do is maybe encrypt it. But a much better solution would be to have a POST request instead of a GET request with the username and password in the body. You add the Authorization header to the API request as follows:
public interface UserService {
String authorization = "Authorization: Basic XXXXXX";
String contentType = "Content-Type: application/json";
// Static Header
#POST("login")
#Headers({
contentType,
authorization
})
Call<ResObj> login(#Body UserCredential userCred);
// Dynamic Header
#POST("login")
Call<ResObj> login(#Header("Authorization") String basicAuth, #Body UserCredential userCred);
}
The Body model can be:
public class UserCredential {
private String username, password;
public String getPassword() {
...encrypt your password here...
return encrypted_password;
}
}
Also, it is safe to put a null check before you do this: response.body(); in the API response.

Related

how to get Oauth2 Authorization Token using RestAssured

public static Response getCode() {
String authorization = encode(username, password);
return
given()
.header("authorization", "Basic " + authorization)
.contentType(ContentType.URLENC)
.formParam("response_type", "code")
.queryParam("client_id", clientId)
.queryParam("redirect_uri", redirectUri)
.queryParam("scope", scope)
.post("/oauth2/authorize")
.then()
.statusCode(200)
.extract()
.response();
}
public static String parseForOAuth2Code(Response response) {
return response.jsonPath().getString("code");
}
#BeforeAll
public static void setup() {
RestAssured.baseURI = "https://some-url.com";
}
#Test
public void iShouldGetCode() {
Response response = getCode();
String code = parseForOAuth2Code(response);
Assertions.assertNotNull(code);
}
}
response is null therefore keeps throwing cannot parse json

How can I add a Principal in configureClientInboundChannel?

I am using SockJS + STOMP + Spring WebSocket with a configuration that receives user credentials during the handshake. The credentials are received in the configureClientInboundChannel method:
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message,
StompHeaderAccessor.class);
if (accessor != null && (StompCommand.CONNECT.equals(accessor.getCommand())
|| StompCommand.SEND.equals(accessor.getCommand()))) {
List<String> auth = accessor.getNativeHeader("Authorization");
System.out.printf("Authorization: %s%n", auth.get(0));
}
return message;
}
});
}
I would like to add a Principal based on the token I receive in the header. I have a handler that registers Principals:
public class PrincipalHandshakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler handler, Map<String, Object> attributes) {
System.out.println(attributes);
return new Principal() {
#Override
public String getName() {
return userId;
}
};
}
}
But how do I get pass the token I get in configureClientInboundChannel to the above handshake handler? Basically, the token should go there in place of userId.

Provide different ClientInterceptor per request using Spring Web Services

I've created a custom web service client by extending WebServiceGatewaySupport and also implement custom ClientInterceptor to log some request/response data.
I have to create new interceptor for every call because it has to store some data about the request.
The problem occurs when I make two or more calls to my client. The first request applies its own interceptor with its clientId. The second should do the same. But since both requests use the same WebServicetemplate in my client, the second request replaces the interceptor with its own, with its clientId there.
As a result, I should get the following output to the console:
Request: clientId-1
Request: clientId-2
Response: clientId-1
Response: clientId-2
But I got this:
Request: clientId-1
Request: clientId-2
Response: clientId-2
Response: clientId-2
Here is come code examples (just for understanding how it should work):
#Data
class Response {
private final String result;
public Response(String result) {
this.result = result;
}
}
#Data
class Request {
private final String firstName;
private final String lastName;
}
#Data
class Context {
private final String clientId;
}
#Data
class Client {
private final String clientId;
private final String firstName;
private final String lastName;
}
class CustomInterceptor extends ClientInterceptorAdapter {
private final String clientId;
public CustomInterceptor(String clientId) {
this.clientId = clientId;
}
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Request: " + clientId);
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Response: " + clientId);
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Error: " + clientId);
return true;
}
}
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor(context.getClientId())};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate().marshalSendAndReceive(request);
}
}
#Service
#RequiredArgsConstructor
class CustomService {
private final CustomClient customClient;
public String call(Request request, Context context) {
Response response = customClient.sendRequest(request, context);
return response.getResult();
}
}
#RestController
#RequestMapping("/test")
#RequiredArgsConstructor
class CustomController {
private final CustomService service;
public CustomController(CustomService service) {
this.service = service;
}
#PostMapping
public String test(#RequestBody Client client) {
Request request = new Request(client.getFirstName(), client.getLastName());
Context context = new Context(client.getClientId());
return service.call(request, context);
}
}
Is it possible to implement custom interceptors with some state for each call? Preferably without any locks on WebServicetemplate to avoid performance degradation.
Okay. I've found the solution for my case.
I've created an implementation of WebServiceMessageCallback and using it I'm saving data of each request not in interceptor but in WebServiceMessage's mime header.
#Data
class CustomMessageCallback implements WebServiceMessageCallback {
private final String clientId;
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
MimeHeaders headers = ((SaajSoapMessage) message).getSaajMessage().getMimeHeaders();
headers.addHeader("X-Client-Id", clientId);
}
}
And pass this callback in my client implementation:
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor()};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate()
.marshalSendAndReceive(request, new CustomMessageCallback(context.getClientId()));
}
}
So now I can get this data while processing request/response/error via interceptor.
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String clientId = ((SaajSoapMessage) messageContext.getRequest())
.getSaajMessage()
.getMimeHeaders()
.getHeader("X-Client-Id")[0];
System.out.println("Request: " + clientId);
return true;
}

Principal is null for every Spring websocket event

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();
}
}

Creating JSON Web Tokens through Basic Authentication endpoint? Dropwizard

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));

Categories