I am using Angular 5 + spring REST.
I have created rest service, and keeping some data in session.
#RequestMapping(value ="/controls" , method= RequestMethod.GET)
#ResponseBody
public List<InputBase> onLoadResponse(#RequestParam (value = "userId") String inputParam ,HttpServletRequest httpServletReques,HttpServletResponse httpServletResponse, HttpSession session){
super.responseHeader(httpServletReques, httpServletResponse);
session.setAttribute("root", "root");
return controlService.getControl(inputParam, session);
}
Issue is that when I hit this request form Postman i get same session, but from angular if get different session for every request.
This new session is created by browser or angular ? and is it possible to keep same session for each request ?
Adding proxy configuration resolved the issue. Here is the link : Link
Related
I need to implement a relatively complex authorization process for a Spring Boot application and consider using Keycloak for this.
Is it possible to do following things using Keycloak (incl. by extending it with custom authentication/authorization mechanisms)?
Keeping track of sessions: Does Keycloak know on how many devices a user is logged in into the application?
Keeping track of failed login attempts: Does Keycloak know how many times a particular user entered an incorrect password?
Terminating sessions: Is it possible to terminate some (but not all) sessions of a user?
My answers
1. Keeping track of session
According to the user manual, section "Managing user sessions", "Viewing client sessions" it is possible to see all active sessions of a user via the web UI.
According to this answer, it is possible to do so programmatically.
2. Keeping track of failed login attempts
According to this page, it may be possible to implement it using a custom event listener.
3. Terminating sessions
It looks like it is possible using the http://auth-server{kc_realms_path}/{realm-name}/protocol/openid-connect/logout endpoint according to documentation and this answer.
Update 1: It looks like items 1 and 2 are indeed possible.
However, I am having trouble with termination of sessions.
This is what I want:
User logs in via Keycloak into a Spring Boot application.
When I terminate the session, the user is logged out of that application.
First, I tried to delete sessions using code like this:
final Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8080")
.realm("KeycloakDemo")
.username("postman")
.password("postman2022")
.clientId("postman")
.clientSecret("ZY006ddQbWHdSiAK3A06rrPlKgSz3XS0")
.build();
final UserRepresentation user =
keycloak.realm("KeycloakDemo").users().search("user1").get(0);
final String userId = user.getId();
final UserSessionRepresentation[] sessions = keycloak
.realm("KeycloakDemo")
.users().get(userId).getUserSessions()
.toArray(new UserSessionRepresentation[0]);
if (sessions.length > 0) {
final String sessionId = sessions[0].getId();
keycloak.realm("KeycloakDemo").deleteSession(sessionId);
}
This piece of code deletes sessions (i. e. they are not visible in the Keycloak GUI any longer), but it does not log out the user.
Another attempt was to log out the user after the session was deleted using the following code.
final String token = getToken();
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
.url("http://localhost:8080/realms/KeycloakDemo/protocol/openid-connect/logout?id_token_hint=" + token)
.method("GET", null)
.build();
Response response = client.newCall(request).execute();
getToken() is defined as follows:
private String getToken() throws IOException {
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "client_id=admin-cli&username=postman&password=postman2022&grant_type=client_credentials&scope=openid&realm=KeycloakDemo&client_secret=CMewUzBUsiy0gUqg6uEmCRBgR5p6f5Nu");
Request request = new Request.Builder()
.url("http://localhost:8080/realms/KeycloakDemo/protocol/openid-connect/token")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Authorization", "bearer ... ")
.build();
Response response = client.newCall(request).execute();
if (response.code() != 200) {
System.exit(1);
}
final ObjectMapper om = new ObjectMapper();
final JsonNode jsonNode = om.readTree(response.body().string());
return jsonNode.get("id_token").asText();
}
This does not work, either (the user stays logged in that application, ever if I refresh the page in the browser).
I'm developing simple microservices with Oauth2 with keycloak as authorization server.
Following is the code which returns list of albums when hitting the url: - http://localhost:8087/albums
#Controller
public class AlbumController {
#Autowired
OAuth2AuthorizedClientService oauthService;
#Autowired
RestTemplate restTemplate;
#GetMapping("/albums")
public String getAlbums(Model model,#AuthenticationPrincipal OidcUser principal,Authentication auth) {
OidcIdToken token=principal.getIdToken();
String tokenValue=token.getTokenValue();
Authentication authentication=SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationToken oauthToken=(OAuth2AuthenticationToken) authentication;
OAuth2AuthorizedClient client=oauthService.loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName());
String jwtAccessToken=client.getAccessToken().getTokenValue();
String url="http://localhost:9091/albums";
HttpHeaders headers=new HttpHeaders();
headers.add("Authorization", "Bearer "+jwtAccessToken);
HttpEntity<List<AlbumsRest>> entity=new HttpEntity<List<AlbumsRest>>(headers);
ResponseEntity<List<AlbumsRest>> responseEntity=restTemplate.exchange(url, HttpMethod.GET, entity, new ParameterizedTypeReference<List<AlbumsRest>>() {
});
List<AlbumsRest> albums=responseEntity.getBody();
model.addAttribute("Albums", albums);
return "albums";
}
}
Following is my application.properties file :-
server.port=8087
spring.security.oauth2.client.registration.mywebclient.client-id=tcs
spring.security.oauth2.client.registration.mywebclient.client-secret=2KcMN6xsmJH235k6TlLXUXj3iY3sAl8i
spring.security.oauth2.client.registration.mywebclient.scope=openid,profile,roles
spring.security.oauth2.client.registration.mywebclient.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.mywebclient.redirect-uri=http://localhost:8087/login/oauth2/code/mywebclient
spring.security.oauth2.client.provider.mywebclient.authorization-uri=http://localhost:7070/auth/realms/developer/protocol/openid-connect/auth
spring.security.oauth2.client.provider.mywebclient.token-uri=http://localhost:7070/auth/realms/developer/protocol/openid-connect/token
spring.security.oauth2.client.provider.mywebclient.jwk-set-uri=http://localhost:7070/auth/realms/developer/protocol/openid-connect/certs
spring.security.oauth2.client.provider.mywebclient.user-info-uri=http://localhost:7070/auth/realms/developer/protocol/openid-connect/userinfo
spring.security.oauth2.client.provider.mywebclient.user-name-attribute=Utkarsh0911
Whenever I'm hitting the url http://localhost:8087/albums, it performs too many redirections and finally end up on a page with message "This Page isn't working".
this loop usually occurs when the redirect URL is not defined as permitted in the application. Please check here:
https://dzone.com/articles/spring-boot-how-to-solve-oauth2-err-too-many-redir
I am trying to connect to AWS API gateway using the Vertx Webclient:
HttpRequest<Buffer> request = webClient.postAbs(targetHost);
request.putHeader("Authorization", auth);
request.putHeader("Content-Type", contentType);
request.putHeader("Host", hostName);
request.sendJson(new JsonObject(jsonData), response -> {
if (response.succeeded()) {
final JsonObject result = response.result().bodyAsJsonObject();
logger.info(result.toString());
routingContext.response()
.setStatusCode(200)
.putHeader("Content-Type", "application/json")
.end(result.toString());
} else {
logger.error(response.cause().getMessage());
routingContext.fail(new Exception(response.cause().getMessage()));
}
});
and always am receiving the same error response i.e. The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.
But if I use a javax.ws.rs.client.WebTarget,
Client client = ClientBuilder.newClient();
WebTarget target = client.target(targetHost);
response=target.request()
.header("Authorization", auth)
.header("Content-Type", contentType)
.header("Host", hostName)
.post(jsonData,Response.class);
I am able to receive a proper response.
Any idea what Vertx web client is doing differently?
So we figured out the issue, turns out the json data that we were using was not being passed properly in the Vertx implementation.
So we can just create a new JSONObject, create the data and use this object to eveluate the aws signature as well as pass the same object in the actual API call.
Thanks!
I have implemented the custom security on my reporting services 2016 and it displays the login page once the URL for reporting services is typed on browser URL bar (either reports or reportserver)
I am using the following code to pass the Credentials
when i use the code WITHOUT my security extension it works and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"NTLM", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
when i use the code WITH the security extension it doesnt work and looks like this
ICredentials _executionCredentials;
CredentialCache myCache = new CredentialCache();
Uri reportServerUri = new Uri(ReportServerUrl);
myCache.Add(new Uri(reportServerUri.GetLeftPart(UriPartial.Authority)),
"Basic", new NetworkCredential(MyUserName, MyUserPassword));
_executionCredentials = myCache;
and i get an Exception saying "The response to this POST request did not contain a 'location' header. That is not supported by this client." when i actually use this credentials
Is "basic" the wrong option ?
Have anyone done this ?
Update 1
Well it turns out that my SSRS is expecting an Authorisation cookie
which i am unable to pass (according to fiddler, there is no cookie)
HttpWebRequest request;
request = (HttpWebRequest)HttpWebRequest.Create("http://mylocalcomputerwithRS/Reports_SQL2016/api/v1.0");
CookieContainer cookieJar = new CookieContainer();
request.CookieContainer = cookieJar;
Cookie authCookie = new Cookie("sqlAuthCookie", "username:password");
authCookie.Domain = ".mydomain.mylocalcomputerwithRS";
if (authCookie != null)
request.CookieContainer.Add(authCookie);
request.Timeout = -1;
HttpWebResponse myHttpWebResponse = (HttpWebResponse)request.GetResponse();
That's how I got it (SSRS 2017; api v2.0). I took the value for the "body" from Fiddler:
var handler = new HttpClientHandler();
var httpClient = new HttpClient(handler);
Assert.AreEqual(0, handler.CookieContainer.Count);
// Create a login form
var body = new Dictionary<string, string>()
{
{"__VIEWSTATE", "9cZYKBmLKR3EbLhJvaf1JI7LZ4cc0244Hpcpzt/2MsDy+ccwNaw9hswvzwepb4InPxvrgR0FJ/TpZWbLZGNEIuD/dmmqy0qXNm5/6VMn9eV+SBbdAhSupsEhmbuTTrg7sjtRig==" },
{"__VIEWSTATEGENERATOR", "480DEEB3"},
{ "__EVENTVALIDATION", "IS0IRlkvSTMCa7SfuB/lrh9f5TpFSB2wpqBZGzpoT/aKGsI5zSjooNO9QvxIh+QIvcbPFDOqTD7R0VDOH8CWkX4T4Fs29e6IL92qPik3euu5QpidxJB14t/WSqBywIMEWXy6lfVTsTWAkkMJRX8DX7OwIhSWZAEbWZUyJRSpXZK5k74jl4x85OZJ19hyfE9qwatskQ=="},
{"txtUserName", "User"},
{"txtPassword", "1"},
{"btnLogin","Войти"}
};
var content = new FormUrlEncodedContent(body);
// POST to login form
var response = await httpClient.PostAsync("http://127.0.0.1:777/ReportServer/Logon.aspx", content);
// Check the cookies created by server
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
var cookies = handler.CookieContainer.GetCookies(new Uri("http://127.0.0.1:777/ReportServer"));
Assert.AreEqual("sqlAuthCookie", cookies[0].Name);
// Make new request to secured resource
var myresponse = await httpClient.GetAsync("http://127.0.0.1:777/Reports/api/v2.0/Folders");
var stringContent = await myresponse.Content.ReadAsStringAsync();
Console.Write(stringContent);
As an alternative you can customize SSRS Custom Security Sample quite a bit.
I forked Microsoft's Custom Security Sample to do just what you are describing (needed the functionality at a client long ago and reimplemented as a shareable project on GitHub).
https://github.com/sonrai-LLC/ExtRSAuth
I created a YouTube walkthrough as well to show how one can extend and debug SSRS security with this ExtRSAuth SSRS security assembly https://www.youtube.com/watch?v=tnsWChwW7lA
TL; DR; just bypass the Microsoft example auth check in Login.aspx.cs and put your auth in Page_Load() or Page_Init() event of Login.aspx.cs- wherever you want to perform some custom logging check- and then immediately redirect auth'd user to their requested URI.
The Sharepoint Rest API uses a simple URL of the type http://mysite/_api/search/query?querytext='search_key' to return search results as an XML. When I run this directly in a browser, I see a valid XML response:
(1) Am I right in assuming the above response is generated using the current user's authorization?
(2) Can this URL be invoked from server side? I tried it in a web method (WCF web service), but received a 401 - Unauthorized:
public string GetSearchResults(string searchKey)
{
string webURL = SPContext.Current.Web.Url;
string searchURL = webURL + "/_api/search/query?querytext='" + searchKey + "'";
WebClient client = new WebClient();
string xmlResponse = client.DownloadString(searchURL); // throws 401
// parse xmlResponse and return appropriately
}
(3) What I really need is to be able to get the search results irrespective of the current user's access rights (the requirement is that users will see all search results, with an option to "request access" when needed).
I tried this in the above web method, but it still throws the same 401:
public string GetSearchResults(string searchKey)
{
string webURL = SPContext.Current.Web.Url;
string searchURL = webURL + "/_api/search/query?querytext='" + searchKey + "'";
string xmlResponse;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
WebClient client = new WebClient();
xmlResponse = client.DownloadString(searchURL); // still 401
});
// parse xmlResponse and return appropriately
}
What is the right way to invoke the Rest URL from server side? Specifically, from a web method? And how can it be run as super user?
In order to perform REST request, authenticate the request via WebClient.Credentials Property
On Premise (your scenario)
WebClient client = new WebClient();
client.Credentials = new NetworkCredential(userName,password,domain);
SharePoint Online
WebClient client = new WebClient();
client.Credentials = new SharePointOnlineCredentials(username,securedPassword);
client.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
Search results are always security trimmed by SharePoint so to make this work, you'd need to run your query after specifying new credentials as mentioned by Vadim. This is almost certainly not a good idea. If you're running code server side already, don't use the REST interface, just query directly using the search API.