Storing User Credientials in the Bayeux Server - cometd

I'd like to store the clients UserName and SessionId when a client subscribes to a particular channel. When i override canHandshake() i can get the user credentials using the following:
userName = (String) authentication.get("userName");
sessionId = (String) authentication.get("sessionId");
Just wondering how i can store these credentials and later retrieve them? I've had a look at the authentication documentation here and it just mentions linking the authentication data to the session. Is this the Bayeux Server side session??
Thanks

The "linking" can be done in several ways.
You can link this information in an external map via:
#Override
public boolean canHandshake(BayeuxServer server, ServerSession session, ServerMessage message)
{
...
Map<String, Object> authentication = ...;
map.put((String)authentication.get("userName"), session);
...
}
where the map can be a java.util.ConcurrentHashMap<String, ServerSession> field in the security policy itself, or in another object such as a user service.
For simpler use cases, the userName can be linked directly to the session in this way:
session.setAttribute("userName", authentication.get("userName"));
Or you can use both techniques.
This is the updated link for the authentication how-to, and you can find the latest comprehensive CometD documentation at http://docs.cometd.org.

Related

How to get the last login session details of a user in Keycloak using Keycloak rest endpoints?

How to get the last login session details of a user in Keycloak using keycloak rest endpoints?
Example:
builder.append(OAuth2Constants.AUDIENCE+"="+clientId+"&");
builder.append(OAuth2Constants.GRANT_TYPE+"="+OAuth2Constants.UMA_GRANT_TYPE+"&");
headers.put("Content-Type", "application/x-www-form-urlencoded");
headers.put("Authorization", "Bearer "+accessToken);
//String keycloakURL = keyCloakCFGBean.getCreateRefreshSession();
String keycloakURL="http://10.10.8.113:10004/auth/realms/{realm}/protocol/openid-connect/token";
keycloakURL = keycloakURL.replace("{realm}", realmName);
URL url = new URL(keycloakURL);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setDoOutput(true);
if (headers != null && headers.size() > 0) {
Iterator<Entry<String, String>> itr = headers.entrySet().iterator();
while (itr.hasNext()) {
Entry<String, String> entry = itr.next();
httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
}
}
outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream(), StandardCharsets.UTF_8);
outputStreamWriter.write(builder.toString());
outputStreamWriter.flush();
So there are a couple of scenarios here. All of this information assumes that you have an appropriate bearer token that you are sending in the header of the request for authentication/authorisation, and requires that you have sufficient admin privileges in the Keycloak realm.
I've not gone into detail in terms of the precise code you write in a particular language, but hopefully the instructions are clear in terms of what you need your code to do.
Sessions
If you are interested in ACTIVE user sessions specifically, you can use the API endpoint as described at: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_getsessions
That is:
GET /{realm}/users/{id}/sessions
e.g. the full URL would be:
https://{server}/auth/admin/realms/{realm}/users/{id}/sessions
In the response there will be a property called lastAccess that will contain a number that is the usual UNIX milliseconds since 1/1/1970. If you take that number, you can then parse it in your language of choice (Java from the looks of it?) to get the date/time in the format that you require.
All Logins
However I suspect what you really want is to look at the last login across all of the stored information in Keycloak, not just active user sessions, so for that you need to look for the Realm EVENTS. Note that Keycloak only stores events for a certain amount of time, so if it's older than that then you won't find any entries. You can change how long events are stored for in the events config page of the realm admin console.
To get all realm events you call the endpoint mentioned here: https://www.keycloak.org/docs-api/11.0/rest-api/index.html#_getevents (Search for "Get events Returns all events, or filters them based on URL query parameters listed here" if the link doesn't take you straight there).
i.e.
GET /{realm}/events
e.g. the full URL would be: https://{server}/auth/admin/realms/{realm}/events
You will need to filter the results based on "type" (i.e. so that you only have events of type "LOGIN"), and if you want to check a specific user you would also want to filter the results on userId based on the ID of that user account.
You can perform both of these filters as part of the request, to save you having to get the full list of events and filter it client-side. To filter in the request you do something like the following:
https://{server}/auth/admin/realms/{realm}/events?type=LOGIN&user={id}
From the resultant JSON you can then get the result with the highest value of the time property, that represents that login event. The time property will be a UNIX time of milliseconds since 1/1/1970 again, so again you can convert this to a format that is appropriate to you once you have it.
Hope that's helpful!
use Keycloak rest Api
${keycloakUri}/admin/realms/${keycloakRealm}/users
and you will get a response as JWT. Decode it and you will get all the info related to the user.
OR you may use the java client API for example by
Keycloak kc = KeycloakBuilder.builder()
.serverUrl("https://localhost:8443/auth")
.realm("master")
.username("admin")
.password("admin")
.clientId("Mycli")
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
.build();
CredentialRepresentation credential = new CredentialRepresentation();
credential.setType(CredentialRepresentation.PASSWORD);
credential.setValue("test123");
UserRepresentation user = new UserRepresentation();
user.setUsername("testuser2");
user.setFirstName("Test2");
user.setLastName("User2");
user.setEmail("aaa#bbb.com");
user.setCredentials(Arrays.asList(credential));
user.setEnabled(true);
user.setRealmRoles(Arrays.asList("admin"));
UsersResource usersResource = kc.realm("my-realem").users();
UserResource userResource = usersResource.get("08afb701-fae5-40b4-8895-e387ba1902fb");
you will get the list of users. Filter by user ID then you will find all user info.

How to implement user login in Jersey API

I am implementing RESTful API using javax.ws.rs. The response of the calls that I need to implement requires knowing which user is logged in currently. I tried making a separate call for the user login:
api.myapi.co.uk/authenticate?username=xxxx&password=xxxx
where I basically save the user information is a global variable
and then tried to make another call to retrieve information from the database based on the user that has been saved earlier but I find the value as null during the second call. What am I missing? Is my approach wrong? do you have any suggestions please?
Your code probably looks like this, right?
#Path("/")
public class MyResource{
private String username;
private String password;
#Path("authenticate")
public String authenticate(#QueryParam("username") username, #QueryParam("password") password) {
if(!username.equals("zainab") || !password.equals("letmein"))
return "Incorrect username or password";
this.username=username;
this.password=password;
return "Sucessfully authenticated";
}
#Path("secret")
public String secret() {
if(username == null || password == null)
return "Sorry, not authorized";
return "You are authorized: "+username;
}
}
If so, the problem is that JAX-RS creates a new Resource object for each request. A request to "/secret" then uses a new instance of MyResource, which has username and password as null.
Then, you might think, I'll just make it static! Then, your resource can't handle concurrent requests. What if Person A calls "/authenticate", then "/secret". Then Person B calls "/secret" without authenticating. He can then access "/secret" without authenticating!
Anyways, this violates the idea of RESTful services. The S in RESTful stands for "Stateless". This means that the server should store no state per client, and possibly give the user a token to pass with concurrent requests.
One possibility is to accept the username and password for every request to secret ("/secret?username=zainab&password=letmein"). Or you could implement token-based authentication, where the user calls "/authenticate" to get a token, and they pass that token on all later requests. Here is a link to help with that.
Also, note that username and password is usually not send in the URL as a query param, but instead in the Authorization HTTP header as such Authorization: base64encode("USERNAME:PASSWORD")

How can the resource server identify the resource owner using token in oauth2?

The typical scenario I am looking into is:
User1 provides proper credentials to the front-end rest client (grant type: password) and the client gets the token in return.
The client sends the token and accesses the resources owned by User1.
In my scenario, once the client has the access token for user1, I want the client to have access limited to User1's resources only.
Consider that the client accesses the URI /users/1/books. The response will contain all the books associated with User1. The main problem is that if the client accesses the URL /users/2/books with User1's token, it gets the list of all the books for User2 which shouldn't be allowed.
How can I limit the scope to the user whose credentials were used to obtain the token?
How can I map the token to a specific user in my resource server?
I am using Spring/Java. But any general theory will also help.
After a lot of debugging, I got the answer.
Spring security 1.4
Token store: InMemoryTokenStore()
In ResourceServerConfiguration, configure HttpSecurity.
#Override
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests().
// antMatchers("/oauth/token").permitAll().
antMatchers("/api/users/{userId}").access("#webSecurity.checkUserId(authentication,#userId)")
.anyRequest().authenticated().and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();
// #formatter:on
}
Create a class WebSecurity and provide the implementation.
public class WebSecurity {
public boolean checkUserId(Authentication auth, int id) {
return ((UmUser)auth.getPrincipal()).getId() == id;
}
}
Resources:
http://docs.spring.io/spring-security/site/docs/current/reference/html/el-access.html#el-access-web-path-variables
http://www.baeldung.com/get-user-in-spring-security
I had to debug a lot as I was using JwtTokenStore. This returned the Principal as a String and not the instance of UserDetails as I was expecting.
Once I switched to InMemoryTokenStore, I got the expected results. This was not a problem for me as I had the choice, but I would still like to know how to achieve it with the JWT.

Jira 5.2 Seraph SSO Login behind reverse proxy

Since a few days I'm trying to enable SSO for Jira 5.2 and figured out, that the help page from Jira is outdated.
Each example uses an old version of atlassian-seraph (Jira 5.2 uses 2.6.0).
Goal:
I want to get automatically logged in into Jira if I'm logged in into Webseal (reverse proxy).
Background:
Jira is behind a reverse proxy (see picture).
This proxy authentificatates the user and holds the session.
If I'm logged in I want to be logged in in Jira, too
The only information provided is the user name
Question:
How to write a custom login module that reads the username from http_header and authentificates the user?
Links:
https://confluence.atlassian.com/display/DEV/Single+Sign-on+Integration+with+JIRA+and+Confluence
http://docs.atlassian.com/atlassian-seraph/latest/sso.html
https://answers.atlassian.com/questions/23245/how-to-integrate-jira-with-my-company-s-sso
In the end i figured it out by myself:
You need a custom authenticator
public class MyCustomAuthenticator extends DefaultAuthenticator {
protected boolean authenticate(Principal user, String password)
throws AuthenticatorException {
return true;
}
protected Principal getUser(String username) {
return getCrowdService().getUser(username);
}
private CrowdService getCrowdService() {
return (CrowdService)ComponentManager.getComponent(CrowdService.class);
}
}
Add the MyCustomAuthenticator to seraph-config.xml
<authenticator class="com.company.jira.MyCustomAuthenticator"/>
Write a Custom Filter to set the user name from http-header
public class CustomFilter extends PasswordBasedLoginFilter {
#Override
protected UserPasswordPair extractUserPasswordPair(
HttpServletRequest request) {
String username = request.getHeader("iv-header");
if (username != null && username.trim().length() != 0) {
return new PasswordBasedLoginFilter.UserPasswordPair(
username, "DUMMY", false);
}
return null;
}
}
Replace the filter within the web.xml
<filter>
<filter-name>login</filter-name>
<filter-class>com.company.jira.CustomFilter</filter-class>
</filter>
These jar's are needed for Jira 5.2
embedded-crowd-api-2.6.2
jira-core-5.2.1
atlassian-seraph-2.6.0
I am not familiar with Jira authentication, but I do understand well the SiteMinder/ WebSeal authentication.
Both systems authenticate user and send the user name in an HTTP header.
The name of HTTP header can be configured. Also, they can send additional user properties, like the user email in the additional HTTP headers.
TO authenticate a user behind SiteMinder/ WebSeal it is just required to take the HTTP header and to create an application session using the user name from the header.
You definitely can solve it in Jira. You have 2 options:
To use already created SiteMinder authenticator:
https://confluence.atlassian.com/display/DEV/SiteMinder+Custom+Seraph+Authenticator+for+Confluence
The problem that I did not find how to configure the HTTP header name for the user name header. It assumes that the header name is uid
You need to configure the header uid in WebSeal or try to obtain sources and make the header name configurable.
Implement your own authenticator according to your link:
http://docs.atlassian.com/atlassian-seraph/latest/sso.html
Obtain the user name using the code
httpServletRequest.getHeader(userNameHeaderName);

Smack service discovery without login gives bad-request(400)

I am trying to discover items that a pubsub service provides. When I log into the target server, I can get the response successfully. But when I connect bu do not login, it gives a bad request error.
This is the code:
ConnectionConfiguration config = new ConnectionConfiguration(serverAddress, 5222);
config.setServiceName(serviceName);
connection = new XMPPConnection(config);
connection.connect();
connection.login(userName, password); //!!!when I remove this line, bad request error is received
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection);
DiscoverItems items;
try {
items = discoManager.discoverItems("pubsubservice." + serverName);
} catch (XMPPException e) {
e.printStackTrace();
}
Is there a way to discover items when the user is not logged in, but the connection is established?
No, you must authenticate to send stanzas to any JID in XMPP (otherwise they would not be able to reply to you, since they wouldn't know your address).
Perhaps one option for you is anonymous authentication. Most servers support it, and it creates a temporary account on the server for you, with a temporary JID. You don't need a password, and login time is quick.
#MattJ is correct and you could try using anon login. That will get you part way there.
Your current request will only get you the nodes though, after which you will have to get the items for each node. It would be simpler to use PubsubManager to get the information you want since it provides convenience methods for accessing/using all things pubsub.
Try the documentation here, the getAffiliations() method is what you are looking for.
BTW, I believe the typical default service name for pubsub is pubsub not pubsubservice. At least this is the case for Openfire.