In my java-play application I have the annotation #RequiresAuthentication(clientName = "CasClient") inside my controller.
I only want to authenticate users in my production environment.
How can I apply annotations conditionally?
If the way I'm approaching authentication is wrong, what is the conventional way of doing CAS authentication only on production in a java play application?
You could implement authenticators to authenticate users. you could you write your authentication logic in your authenticator implementation.
Play already comes with a built in authenticator action, which we will extend to add our logic. We will call this authenticator Secured.
import play.*;
import play.mvc.*;
import play.mvc.Http.*;
import models.*;
public class Secured extends Security.Authenticator {
#Override
public String getUsername(Context ctx) {
return ctx.session().get("email");
}
#Override
public Result onUnauthorized(Context ctx) {
return redirect(routes.Application.login());
}
}
We have implemented two methods here. getUsername is used to get the username of the current logged in user. In our case this is the email address, that we set in the email attribute in the session when the user logged in. If this method returns a value, then the authenticator considers the user to be logged in, and lets the request proceed. If however the method returns null, then the authenticator will block the request, and instead invoke onUnathorized, which we have implemented to redirect to our login screen.
You could implement your own business logic for user verify user.
Now let’s use this authenticator. In Application.java, add the #Security.Authenticated annotation with our authenticator to the index method:
import play.mvc.Controller;
import play.mvc.Result;
public class Application extends Controller {
#Security.Authenticated(Secured.class)
public static Result index() {
return ok(index.render(
Project.findInvolving(request().username()),
Task.findTodoInvolving(request().username()),
User.find.byId(request().username())
));
}
}
Refs:Play Implementing Authenticator Example
Related
I have gone down many rabbit holes and cannot get this working. I am hoping someone can help me.
I am using Keycloak and my REST endpoints are successfully secured like this abbreviated example:
#Path("/api")
public class MyResource {
#Inject
SecurityIdentity securityIdentity;
#Inject
JsonWebToken jwt;
#GET
#Path("/mydata")
#RolesAllowed("user")
#NoCache
public Uni<Response> getMyData(Request request) {
// Get a claim from the Keycloak JWT
String mySpecialClaim = (String) jwt.claim("myCustomClaim").get();
// Do some work...
String resJson = "{result of work here}";
return Uni.createFrom().item(resJson)
.onItem()
.transform(item -> item != "" ? Response.ok(item) : Response.status(Response.Status.NO_CONTENT))
.onItem()
.transform(Response.ResponseBuilder::build);
}
}
The access token is supplied by the client app which manages the Keycloak authentication and which sends the API request with a Bearer token. Standard stuff, all working. :-)
Now, I want to do something similar with a WebSocket endpoint.
I am using the Quarkus Websockets sample as my guide and can get it all working without Authorization - ie making unsecured calls.
I am stuck trying to secure the WebSocket connection.
The closest I have come to finding a solution is this post in the Quarkus GitHub issues:
https://github.com/quarkusio/quarkus/issues/29919
I have coded that up as per the sample code in the post. Logging shows the reactive route and WebSocketSecurityConfigurator are both being called and the access_token from the JS WebSocket client is present and presumably being processed by the Quarkus default security processes, as it does for REST end points. All good.
The missing piece is how to code the onOpen() and onMessage() methods in my WebSocket endpoint so they are secure, reactive, and I can access the JWT to get the claims I need.
Can anyone elaborate on this code fragment from the Quarkus issue post mentioned above, please? I have added what I think I need as per the sample further below.
The fragment from the issue post:
#Authenticated
#ServerEndpoint(
value ="/ws",
configurator = WebSocketSecurityConfigurator.class
)
public class WebSocket {
#Inject
UserInfo userInfo;
// ...
}
My additions:
#Authenticated
#ServerEndpoint(
value ="/services/{clientid}",
configurator = WebSocketSecurityConfigurator.class
)
public class WebSocket {
#Inject
SecurityIdentity securityIdentity;
#Inject
JsonWebToken jwt;
#Inject
UserInfo userInfo;
#OnOpen
#RolesAllowed("user") // Is this possible here? Or do I use the JWT and test myself?
public void onOpen(Session session, #PathParam("clientid") String clientid) {
// Get a claim from the Keycloak JWT
String mySpecialClaim = (String) jwt.claim("myCustomClaim").get();
// Do some setup work...
// eg cache the session in a map, etc
}
#OnMessage
public void onMessage(String message, #PathParam("clientid") String clientid) {
// Get a claim from the Keycloak JWT
String myOtherSpecialClaim = (String) jwt.claim("myOtherCustomClaim").get();
// Do some work using the message...
String someMessage = "tell the world";
// Broadcast something ...
myBroadcastFunction(someMessage);
}
}
In the non-secure version, the onOpen() and onMessage() methods return void because, of course, unlike a REST endpoint, one broadcasts the result instead of returning it.
In this secured version that does not work. If I only have an onOpen() method, and code it like this:
#OnOpen
public void onOpen(Session session, #PathParam("clientid") String clientid) {
Log.info("websocket onOpen session=" + session.getId());
}
It throws:
Unhandled error in annotated endpoint org.flowt.orgserver.gateway.WebSocketGateway_Subclass#732f20f8
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException:
io.quarkus.runtime.BlockingOperationNotAllowedException: Blocking security check attempted in code running on the event loop.
Make the secured method return an async type, i.e. Uni, Multi or CompletionStage,
or use an authentication mechanism that sets the SecurityIdentity in a blocking manner prior to delegating the call
I would like not to block the event loop, so the first suggestion is the preferred one.
But how should I code that?
If I make the onOpen() return a Uni, how do I subscribe to it so it runs?
Can I still access the JWT to get the claims I need?
Do annotations like #RolesAllowed("user") still work in this context?
I wont waste space here with all my failed attempts. I am thinking I am not the first person to need to do this and there must be some kind of pattern to implement. The Quarkus docs are silent on this.
Can anyone tell me how to code the onOpen() and onMessage() methods using Quarkus so that the WebSocket endpoints are secured and the JWT is available inside those methods?
EDIT =======
To resolve the blocking exception, the Quarkus docs here say
To work around this you need to #Inject an instance
of io.quarkus.security.identity.CurrentIdentityAssociation,
and call the Uni<SecurityIdentity> getDeferredIdentity(); method.
You can then subscribe to the resulting Uni and will be
notified when authentication is complete and the identity
is available.
I cannot work out how to implement that instruction. Debugging into the Quarkus code I see that my access_token is being processed, the user is retrieved from Keycloak but the deferredIdentity is not being set. Therefore onOpen() never runs.
Clearly this is not what the docs mean me to do!
Here is my class:
package org.flowt.orgserver.gateway;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.websocket.Session;
import javax.websocket.OnOpen;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import io.quarkus.logging.Log;
import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.CurrentIdentityAssociation;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;
#ApplicationScoped
#Authenticated
#ServerEndpoint(value = "/services/{clientid}", configurator = WebSocketSecurityConfigurator.class)
public class WebSocketGateway {
#Inject
SecurityIdentity securityIdentity;
#Inject
CurrentIdentityAssociation identities;
#OnOpen
public Uni<Void> onOpen(Session session, #PathParam("clientid") String clientid) {
// This never runs
Log.info("=========================== onOpen session=" + session.getId());
return identities.getDeferredIdentity()
.onItem()
.transformToUni(identity -> {
// Just to see if we reach here
Log.info("identity=" + identity.toString());
return Uni.createFrom().voidItem();
});
}
}
And just to reiterate: the REST endpoints in this same app for the same logged in user work perfectly.
Thanks,
Murray
What I want to do:
Make Keycloak work as authentication provider using OIDC, for users provided by a custom User Storage SPI implementation.
What I have done so far:
Setup Keycloak 8.0.1 (realm, client, "native" test users, etc.)
Setup a small Spring Boot App (Spring Security without the Keycloak Adapter) as test client
Implement and install a custom User Storage SPI, following this guide https://www.keycloak.org/docs/8.0/server_development/#simple-read-only-lookup-example
I took small liberties to keep it more extendable (for example, creating an actual class for the UserModel), but that shouldn't influence the functional bits.
What works:
Authentication (Authorization Grant Flow) in the client app for "native" Keycloak users
What doesn't work:
Authentication in the client app for users provided through the User Storage SPI
From what I can tell, the failure occurs when the access token is created. Login, code creation and redirect happen.
I assume the problem is that my UserModel implementation does not provide all needed attributes to create a token. Could be something different...
Here is what it looks like:
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.storage.adapter.AbstractUserAdapter;
import de.dfb.keycloak.spi.userstorage.service.DfbUserDto;
public class DfbUser extends AbstractUserAdapter {
private String username;
public DfbUser(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel, DfbUserDto userDto) {
super(session, realm, storageProviderModel);
this.username = userDto.getUsername();
}
#Override
public String getUsername() {
return username;
}
// TODO Only implemented to avoid this problem
// https://issues.redhat.com/browse/KEYCLOAK-6115
#Override
public void setSingleAttribute(String name, String value) {
if (!name.equals(LOCALE)) {
super.setSingleAttribute(name, value);
}
}
...
}
What I want to know:
How do I make this work? If it's just missing user attributes: Which ones am I missing? What should they look like?
Are there more resources available, to get a better grasp of keycloaks data model, to avoid similar problems? The developer docs do not seem quite extensive enough and the java doc is missing a bunch of commentary.
I am having trouble grasping the idea of authorization in PlayFramework (version 2.5). My situation is I have a REST API method getUser and I want to restrict its access by performing authorization with a token that is coming in custom request header named "X-Authorization". Now my controller code looks like that:
package controllers;
import models.User;
import org.bson.types.ObjectId;
import play.mvc.*;
import org.json.simple.*;
import views.html.*;
public class ApiController extends Controller {
public Result getUser(String userId) {
User user = User.findById(new ObjectId(userId));
JSONObject userG = new JSONObject();
//Some code to append data to userG before return
return ok(userG.toJSONString());
}
}
The route URL is defined like this:
GET /api/user/:id controllers.ApiController.getUser(id)
Option 1 could be to check the Authorization token inside the method getUser and also check for other credentials but I want to restrict access before even it get calls getUser method. As in future I will be adding more method calls to this REST API. So I will be reusing the same authorization to those future REST APIs as well.
I found there is authorization available in Play Framework which I am not able to understand. I tried to implement Authorization by extending class Security.Authenticator and overriding methods getUserName and onUnauthorized like this:
package controllers;
import models.Site;
import models.User;
import org.json.simple.JSONObject;
import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Security;
public class Secured extends Security.Authenticator {
#Override
public String getUsername(Context ctx) {
String auth_key = ctx.request().getHeader("X-Authorization");
Site site = Site.fineByAccessKey(auth_key);
if (site != null && auth_key.equals(site.access_key)) {
return auth_key;
} else {
return null;
}
}
#Override
public Result onUnauthorized(Context ctx) {
JSONObject errorAuth = new JSONObject();
errorAuth.put("status", "error");
errorAuth.put("msg", "You are not authorized to access the API");
return unauthorized(errorAuth.toJSONString());
}
}
Then I've appended the annotation to the getUser method with #Security.Authenticated(Secured.class). It works fine and returns unauthorized error. But now I am not sure if that is the preferred way. I feel this is not the right way to do it as the name of the override of function getUsername suggests that too. I am not checking for any username in session or cookie rather only the token present in the header of request.
Also I know there is a module named Deadbolt which is used for authorization but I read its documents and I am not able to integrate it. It was relatively complex integration for a beginner like me. I was confused about how to use it. I thought about using SubjectPresent controller authorization but still I was not able to implement it successfully.
In the end what do you guys suggest that should I use Security.Authenticator the way I have implemented? Or do you suggest that I go to my first option that is checking authorization inside getUser method? Or Anyone can tell me how to implement Deadbolt in my scenario?
You are mixing Authorization and Authentication.
Here is a good thread: Authentication versus Authorization
I like this answer:
Authentication = login + password (who you are)
Authorization = permissions (what you are allowed to do)
Authentication == Authorization (excluding anonymous user) if you allow doing something for all users that you know (i.e. Authenticated users)
The main goal of Deadbolt is Authorization (already Authenticated users). Your main goal is Authentication.
I would advise you to use Pac4J, it Authentication library not only for Play, and it has versions as for Java as for Scala. There is a good sample project: https://github.com/pac4j/play-pac4j-java-demo
I use this library myself in my projects and the task
As in future i will be adding more method calls to this REST api. So i
will be reusing the same authorization to those future REST apis as
well.
I solve as easy as just add the configuration in the 'application.conf`:
pac4j.security {
rules = [
{"/admin/.*" = {
authorizers = "ADMIN"
clients = "FormClient"
}}
]
}
Just do not forget to add Security filter. This feature present in the example project, so just clone and try.
Another example form the official page:
pac4j.security.rules = [
# Admin pages need a special authorizer and do not support login via Twitter.
{"/admin/.*" = {
authorizers = "admin"
clients = "FormClient"
}}
# Rules for the REST services. These don't specify a client and will return 401
# when not authenticated.
{"/restservices/.*" = {
authorizers = "_authenticated_"
}}
# The login page needs to be publicly accessible.
{"/login.html" = {
authorizers = "_anonymous_"
}}
# 'Catch all' rule to make sure the whole application stays secure.
{".*" = {
authorizers = "_authenticated_"
clients = "FormClient,TwitterClient"
}}
]
I have a project where I have one application "a" with rest services and another one ("b") for a web page where the users can login and use 1 or 2 rest services of "a" (e.g. posting a comment).
The problem is, the rest services are all secured and shall authorize via httpBasic every time (no session).
The services needed for "b" on the other hand shall have a session.
so:
requests with the rest services directly -> httpBasic everytime(no session)
login via webpage/webpage handling requests -> session via login
Is there a way to achieve this? My only solution right now would be to delete the session after the httpBasic process, but I don't know how to do that.
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests().anyRequest().permitAll()
.and().httpBasic();
If you really wish to delete session data it can be achieved by implementing LogoutSuccessHandler interface.
package com.arjun.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Service;
import com.informage.arnav.domain.CassandraLoginSession;
#Service
public class AppLogoutSuccessHandler implements LogoutSuccessHandler {
#Autowired
private CassandraLoginSessionDao cassandraLoginSessionDao;
public AppLogoutSuccessHandler() {
super();
}
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (authentication != null) {
final Object principal = authentication.getPrincipal();
final AppUserDetails appUserDetails;
if (principal != null && principal instanceof AppUserDetails) {
appUserDetails = (AppUserDetails) principal;
CassandraLoginSession cassandraLoginSession=cassandraLoginSessionDao.findBySessionId(appUserDetails.getSessionId().toString());
//can also delete based on userId
//CassandraLoginSession cassandraLoginSession=cassandraLoginSessionDao.findByUserId(appUserDetails.getUserId());
cassandraLoginSessionDao.hardDelete(cassandraLoginSession);
//delete the session data from DB where session is stored
}
}
}
}
In your security configuration you need to configure this handler.
<logout logout-url="/logout" success-handler-ref="appLogoutSuccessHandler" />
You can write following to permit access to your webservice without login:
http.authorizeRequests()
.antMatchers("/webserviceNotRequiredAuthorization*").permitAll();
Then you must write following to which you webservices need authorization :
http.authorizeRequests().antMatchers("/webserviceRequiredAuthorization").hasAnyRole("ROLE_ADMIN");
in short first write the URL pattern for which authorization is not required and then write the URL pattern for which authorization is required.
http.authorizeRequests()
.antMatchers("/webserviceNotRequiredAuthorization*").permitAll();
http.authorizeRequests().antMatchers("/webserviceRequiredAuthorization").hasAnyRole("ROLE_ADMIN");
I followed the instructions to create a custom security realm for my glassfish. It all works fine, users are authenticated correctly. The problem however is the following:
The user credentials are encrypted in a string
The realm decrypts this string and performs the authentication against a database (works)
Instead of using the decrypted values as principal in the securityContext the encrypted
String is passed.
I already tried to override the commit() method to replace the _userPrincipal or attach my own implementation using getSubject().getPrincipals().add(new PrincipalImpl("user")). Neither was working as expected. Basically the question is a simple as this: How can I set my own principal in a custom security realm in glassfish in a way which makes it possible to use it together with an injected securityContext?
My environment:
Glassfish 3.1.2.2 (Build 5) Full Profile
The application running behind the authentication is a JAX-RS 1.1 based application
The SecurityContext is obtained using injection
I already tried to override the commit() method to replace the
_userPrincipal or attach my own implementation using getSubject().getPrincipals().add(new PrincipalImpl("user")). Neither
was working as expected.
What kind of error(s) do you get?
Regardless, I think your issue lies on the third step of this process. SecurityContext only defines BASIC_AUTH, FORM_AUTH, CLIENT_CERT_AUTH, DIGEST_AUTH as AuthenticationScheme so perhaps SecurityContext cannot see your implementation of your security scheme or type. But you can try these steps and I hope they would work for you.
A- Implement a Java Authentication and Authorization Service (JAAS) LoginModule or extend com.sun.appserv.security.AppservPasswordLoginModule
public class MyLoginModule extends AppservPasswordLoginModule {
#Override
protected void authenticateUser() throws LoginException {
if (!authenticate(_username, _password)) {
//Login fails
throw new LoginException("LoginFailed");
}
String[] myGroups = getGroupNames(_username);
commitUserAuthentication(myGroups);
}
private boolean authenticate(String username, String password) {
/*
Check the credentials against the authentication source, return true if authenticated, return false otherwise
*/
return true;
}
private String[] getGroupNames(String username) {
// Return the list of groups this user belongs to.
}
B- Implementing your realm class.
public class MyRealm extends AppservRealm {
#Override
public void init(Properties props)
throws BadRealmException, NoSuchRealmException {
//here you initialize the realm
}
#Override
public String getAuthType() {
return "Custom Realm";
}
}
C- Installing and configuring the realm and LoginModule into the server.
for this you need to look at JSR 196 and write you own SAM by implmenting javax.security.auth.message.module.ServerAuthModule. Take a look at thelink below.
https://blogs.oracle.com/enterprisetechtips/entry/adding_authentication_mechanisms_to_the