I am writing a service that talks to Alfresco Core repository using Apache Chemistry opencmis library and need to authenticate through SPNEGO. Cmis library requires me to provide custom authentication provider which so far doesn't work for me. So far I came up with following:
public class KerberosAuthProvider extends AbstractAuthenticationProvider {
#Override
public Map<String, List<String>> getHTTPHeaders(String url) {
try {
String authToken = …. // generate token
Map<String, List<String>> headers = Maps.newHashMap();
headers.put("Authorization", Lists.newArrayList("Negotiate " + authToken));
return headers;
} catch (Exception ex) {
throw new IllegalStateException("Couldn't get token", ex);
}
}
}
I will appreciate any suggestions.
This is correct implementation. I was not generating token correctly so was denied access. I have used WireShark to confirm the Authorisation header was added to cmis request.
Related
I have a page to access with form post request (webview page for mobile apps). My application can't have a login form but I need to secure it. Mobile applications will call this webview page with authentication parameters such as (email/password). I need to call third-party api with given authentication parameters and decide it was authenticated or not. Which approach should I use for my scenarios ?
If it possible to pass authentication parameters in the Authorization header, you can enable http basic authentication in your application:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
Otherwise, you can implement your own security filter to create a UsernamePasswordAuthenticationToken (or any other class implementing Authentication) instance from your specific authentication parameters and pass to AuthenticationManager; but in the case of another class, you need to make the authentication provider below support it by overriding the public boolean supports(Class<?> authentication) method.
Then implement a custom AuthenticationProvider that will delegate authentication to the third-party API, e.g.:
public class RestAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String AUTH_URL = "http://third-party-service/authentication/basic";
private RestTemplate restTemplate;
public RestAuthenticationProvider() {
this.restTemplate = new RestTemplate();
}
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
if (!authentication.getCredentials().toString().equals(userDetails.getPassword())) {
this.logger.debug("Authentication failed: invalid credentials");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
#Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
String password = authentication.getCredentials().toString();
try {
ResponseEntity<String> authenticationResponse = authenticate(username, password);
if (authenticationResponse.getStatusCode().value() == 401) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
return createUser(authenticationResponse.getBody());
} catch (BadCredentialsException ex) {
throw ex;
} catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
private ResponseEntity<String> authenticate(String username, String password) {
HttpEntity entity = new HttpEntity(createHeaders(username, password));
return restTemplate.exchange(AUTH_URL, HttpMethod.GET, entity, String.class);
}
private HttpHeaders createHeaders(String username, String password) {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
String authorization = username + ":" + password;
String basic = Base64.getEncoder().encodeToString(authorization.getBytes());
headers.set("Authorization", "Basic " + basic);
return headers;
}
private UserDetails createUser(String json) {
return null; // TODO: Implement
}
}
And finally, make Spring Security to use your provider:
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(new RestAuthenticationProvider())
.eraseCredentials(false);
}
I need to call third-party api with given authentication parameters
and decide it was authenticated or not
Assuming you need to use username / password to send to 3rd party, when you first login into the app, you can create a long lived token in back-end and sent it to the app to store in secure store. Later when you want to load the protected webview, send this token along with the request (in header or body via javascript) and in the server side pick the user / password corresponding to the token and authenticate with 3rd party.
This way you will never need to store password on client side and you can manually make the token at backend inactive too. If you like to go standard way, then you may take a look at Password Grant of OAuth 2 / OpenID Connect / . With the correct infrastructure in place you can get access tokens during login process and use that for your protected page. Spring Security has support for this flow - you can take a look here.
I've devloped a chat bot application using the Facebook Messenger platform.
I used Spring Boot with embedded Tomcat for the web platform.
The application should run on Amazon aws, open to the WWW, and to be used as a webhook for recieving callbacks from Messenger over https.
I need an advice how to secure the application, so it won't be hacked or flooded with requests that are not coming from Facebook.
I thought to make the application require secured (ssl) connection, but using the "security.require_ssl=true" in application.properties didn't do the work. Perhaps I don't know what is the meaning of this and how to configure it propertly.
Is there a best practice how to block requests which are not https requests? Or a way to block requests which are coming outside Messenger in the application level?
Thank you very much!
EDIT
In the meantime, I blocked requests from other IPs in application layer using the handler interceptor:
#Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer, WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (! (request.getRemoteAddr().equals("173.252.88.66") || request.getRemoteAddr().equals("127.0.0.1")|| request.getRemoteAddr().equals("0:0:0:0:0:0:0:1"))){
logger.warn("Request is not coming from authorized remote address: " + request.getRemoteAddr()+". Rejecting");
response.getWriter().write("Unauthorized Address");
response.setStatus(401);
return false;
} else {
return true;
}
}
}
You should check the X-Hub-signature HTTP header available in the requests sent by Facebook to your webhook URL.
In your case, you may define a filter or interceptor for the verification of the signature. You can also do it in your controller as in the this example I found in RealTimeUpdateController.java from the spring social project.
private boolean verifySignature(String payload, String signature) throws Exception {
if (!signature.startsWith("sha1=")) {
return false;
}
String expected = signature.substring(5);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
SecretKeySpec signingKey = new SecretKeySpec(applicationSecret.getBytes(), HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(payload.getBytes());
String actual = new String(Hex.encode(rawHmac));
return expected.equals(actual);
}
a lot to say so I am sure I will miss some points.
setting SSL is a first good thing but make sure you get a certificate. lets encrypt is a good thing if you dont want to pay for SSL certificate.
Just seeing aws provides an alternative to letsencrypt
Security Group You can see Security Group as something similar to a firewall so you can control which port is opened, external and internal flows.
Look at IAM which control who and how can get access to your AWS account
obvious : change your password. do not let default password for installation you could make on the instance
read some of https://aws.amazon.com/security/security-resources/ to get more information about what you can do
it won't be hacked or flooded with requests
sorry to say but most probably it will be - It does not need to be an advanced hacker to run scanner and scan IPs and check open ports / brute force login etc ...
Thanks to Guy Bouallet help I added the signature check.
I added it in my controller and not in the interceptor, to avoid the problem of How to read data twice in spring which seems a little complicated.
So here is it:
#RequestMapping(path = "/")
public void doHandleCallback(#RequestBody String body, #RequestHeader(value = "X-Hub-Signature") String signature) throws IOException {
if (!verifyRequestSignature(body.getBytes(), signature)){
logger.error ("Signature mismatch.");
throw new MismatchSignatureException(signature);
}
MessengerCallback callback = mapper.readValue(body, MessengerCallback.class);
logger.info("Incoming Callback: " + body );
for (EventData entry : callback.getEntry()) {
for (ReceivedMessagingObject message : entry.getMessaging()) {
if (message.isMessage() || message.isPostback()) {
doHandleMessage(message);
}
else if (message.isDelivery()){
doHandleDelivery(message);
}
}
}
}
private boolean verifyRequestSignature(byte[] payload, String signature) {
if (!signature.startsWith("sha1="))
return false;
String expected = signature.substring(5);
System.out.println("Expected signature: " + expected); //for debugging purposes
String hashResult = HmacUtils.hmacSha1Hex(APP_SECRET.getBytes(), payload);
System.out.println("Calculated signature: " + hashResult);
if (hashResult.equals(expected)) {
return true;
} else {
return false;
}
}
And this is the Exception handling class:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Request Signature mismatch")
public class MismatchSignatureException extends RuntimeException {
private String signature;
public MismatchSignatureException(String signature) {
this.signature = signature;
}
#Override
public String getMessage() {
return "Signature mismatch: " + signature;
}
I am developing a multi-tenant application registered on my Azure AD that consumes Office 365 apis, Graph API etc.
I followed this Microsoft sample to build my work which uses ADAL .NET library and OpenIdConnect: Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.19.0.0
In ADAL.NET, we use an AuthenticationContext instance with a custom inherited class for the TokenCache (see code the sample code here).
For each request to the authorized resources, depending on the API, we invoke one of these methods (see code below) to get the auth_token that will be put in the request Bearer parameter. Is it the correct way to do it?
We never make use of the method AcquireTokenByRefreshTokenAsync, does it mean that our application never uses the refresh_token? Does it mean that our user will have to relog after one hour? Should we implement a kind of refreshing procedure with AcquireTokenByRefreshTokenAsync in the catch statement? Can it be made without prompting anything to the end-user?
REMARK: I posted a question regarding OpenIdConnect authentication ticket lifetime. To me these two questions are unrelated but they may be.
string signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
string userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
string tenantId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
public async Task<string> AcquireOutlook365TokenAsync()
{
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", SettingsHelper.AuthorizationUri, tenantId), new ADALTokenCache(signInUserId));
try
{
var result = await authContext.AcquireTokenSilentAsync(#"https://outlook.office365.com/",
new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
catch (AdalException exception)
{
//handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
}
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
}
public async Task<string> AcquireAzureGraphTokenAsync()
{
AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", SettingsHelper.AuthorizationUri, tenantId), new ADALTokenCache(signInUserId));
try
{
var result = await authContext.AcquireTokenSilentAsync(#"https://graph.windows.net/",
new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return result.AccessToken;
}
catch (AdalException exception)
{
//Same as other method
}
}
ADAL uses the stored refresh tokens automatically and transparently, you aren't required to perform any explicit action. AcquireTOkenByRefreshToken is in the ADAL surface for legacy reasons, and has been removed from version 3.x. More background at http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/
I am facing a rather simple situation but I cannot wrap my head around it. Maybe the OkHttp gurus could light my path.
I am using Picasso, Retrofit and OkHttp for multiple purposes on my android app. yay!. As I read properly, the developer should make an effort in keeping on OkHttpClient (as read here ).
With the approach in mind, I want any of my HTTP calls (be it an API call, an Image loading, a resource download) to :
Send the request
If HTTP401 is received, then Send another HTTP Request that sends a token back
When that token is received, the call is re-emitted with that token included in the headers
Any subsequent call (be it an API, a resource or image call) should use that token until the next HTTP401 is received (invalid token).
Of course, I would reuse the same client for Retrofit and Picasso.
One route I am considering is to use a mix of Authenticator and an application Interceptor. Authenticator should catch HTTP401, but can I make it make another sync request in the meantime, store the token and activate the new interceptor ?
Looks like I found the solution myself to that problem so let's share the knowledge to everyone.
In order to this, OkHttp already gives all the necessary hooks.
Make sure to use Authenticator
Install an interceptor once the authenticator succeed
Return a request with the good token.
This also imply that the Authenticator handles an HTTP to set your token back (done in another android service).
okHttpClient.setAuthenticator(new Authenticator() {
#Override
public Request authenticate(Proxy proxy, Response response) {
AccountManager accountManager = AccountManager.get(context);
Account[] accounts = accountManager.getAccountsByType(Authenticator.ACCOUNT_TYPE);
// No account, do not even try to authenticate
if (accounts.length == 0) {
Log.i(TAG, "... But we dont have any account yet, so I will just back off for now.");
return null;
}
Account account = accounts[0];
try {
final String mCurrentToken = accountManager.blockingGetAuthToken(account, "", false);
// For now, we just re-install blindly an interceptor
okHttpClient.interceptors().clear();
Log.i(TAG, "... Installing interceptor after authentication");
okHttpClient.interceptors().add(new Interceptor() {
#Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request newReq = request.newBuilder()
.addHeader("Authorization", mCurrentToken)
.build();
Response response = chain.proceed(newReq);
return response;
}
});
Log.i(TAG, "Install temporary auth token in request");
return response.request().newBuilder()
.addHeader("Authorization", mCurrentToken)
.build();
} catch (OperationCanceledException e) {
Log.e(TAG, "Interrupted exception");
return null;
} catch (AuthenticatorException e) {
Log.e(TAG, "Authentication error");
return null;
} catch (IOException e) {
Log.e(TAG, "IO Error");
return null;
}
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) {
return null; // Null indicates no attempt to authenticate.
}
})
With this, just use this OkClient in Picasso and Retrofit.
I'm trying to perform an basic auth to the login-module which runs on my jboss using REST. I already found an StackOverflow topic which explains how to authenticate with credentials.
RESTEasy client framework authentication credentials
This does not work. Analysing the established connection with Wireshark I was not able to see an HTTP package with Authorization: Basic. After more research I found this article, http://docs.jboss.org/resteasy/docs/2.3.3.Final/userguide/html/RESTEasy_Client_Framework.html which describes how to append basic auth to ApacheHttpClient4Executor from resteasy.
// Configure HttpClient to authenticate preemptively
// by prepopulating the authentication data cache.
// 1. Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// 2. Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put("com.bluemonkeydiamond.sippycups", basicAuth);
// 3. Add AuthCache to the execution context
BasicHttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.AUTH_CACHE, authCache);
// 4. Create client executor and proxy
httpClient = new DefaultHttpClient();
ApacheHttpClient4Executor executor = new ApacheHttpClient4Executor(httpClient, localContext);
client = ProxyFactory.create(BookStoreService.class, url, executor);
But this does not work either. There is no description how to append username and passwort for basic auth to the construct. Why is that information not associated with any class from httpcomponent?
One can use org.jboss.resteasy.client.jaxrs.BasicAuthentication which is packaged with resteasy-client 3.x and is meant specifically for basic authentication.
Client client = ClientBuilder.newClient();
ResteasyWebTarget resteasyWebTarget = (ResteasyWebTarget)client.target("http://mywebservice/rest/api");
resteasyWebTarget.register(new BasicAuthentication("username", "passwd"));
You can add a raw authorization header to your REST client by invoking .header(HttpHeaders.AUTHORIZATION, authHeader) in your client configuration.
The credentials must be packed in authorization header in the format of "user:pass", encoded as base64 byte array and then appended to the string "Basic " which identifies basic auth.
This is the whole snippet (inspired by this post on baeldung)
String auth = userName + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("ISO-8859-1")));
String authHeader = "Basic " + new String(encodedAuth);
authToken = restClient.target(restApiUrl + loginPath)
.request()
.accept(MediaType.TEXT_PLAIN)
.header(HttpHeaders.AUTHORIZATION, authHeader)
.get(String.class);
This worked for me in a Resteasy client. For information, when testing this with wget I had to use the --auth-no-challenge flag.
Consider the solution from Adam Bien:
You can attach an ClientRequestFilter to the RESTEasy Client, which adds the Authorization header to the request:
public class Authenticator implements ClientRequestFilter {
private final String user;
private final String password;
public Authenticator(String user, String password) {
this.user = user;
this.password = password;
}
public void filter(ClientRequestContext requestContext) throws IOException {
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
final String basicAuthentication = getBasicAuthentication();
headers.add("Authorization", basicAuthentication);
}
private String getBasicAuthentication() {
String token = this.user + ":" + this.password;
try {
return "Basic " +
DatatypeConverter.printBase64Binary(token.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new IllegalStateException("Cannot encode with UTF-8", ex);
}
}
}
Client client = ClientBuilder.newClient()
.register(new Authenticator(user, password));
I recently upgraded to resteasy-client:4.0.0.Final to deal with some Jackson upgrade issues, and I noticed that setting headers seem to work differently (I was getting 401: Authorization Errors for every authenticated request that previously worked). I also couldn't find much documentation, (the 4.0.0.Final release is only a month old and has some dependency issues, if my experience is representative of the broader case).
The code previously injected headers into the ClientRequestContext:
public AddAuthHeadersRequestFilter(String username, String password) {
this.username = username;
this.password = password;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
String token = username + ":" + password;
String base64Token = Base64.encodeString(token);
requestContext.getHeaders().add("Authorization", "Basic " + base64Token);
}
}
then we set the filter on the ResteasyClient like so:
ResteasyClient client = new ResteasyClientBuilder()
.sslContext(buildSSLContext())
.hostnameVerifier(buildHostVerifier())
.build();
client.register(new AddAuthHeadersRequestFilter(user, pass));
However, this appears not to set the HeaderDelegate, which is where headers are retrieved in 4.x(?) and possibly earlier versions.
The trick was to register that filter on the ResteasyWebTarget instead of the client in the 4.0.0.Final version (you may notice the clientBuilder works a little differently now too).
ResteasyClient client = (ResteasyClient)ResteasyClientBuilder.newBuilder()
.sslContext(buildSSLContext())
.hostnameVerifier(buildHostVerifier())
.build();
ResteasyWebTarget target = client.target(url);
target.register(new AddAuthHeadersRequestFilter(user, pass));