I have a web application deployed on Tomcat, which uses Tomcat's form authentication. When writing a new servlet, this allows me to find a request's user via HttpServletRequest#getUserPrincipal.
I would like to use Restlet in this app, and I was able to do so using Restlet's ServerServlet adaptor. However, it looks like I no longer have access to the user principal when receiving a new request in my resource classes. That is, the user prinicpal information is not carried through from Tomcat to Restlet.
Is there any way of obtaining the principal?
You should use the user principal with Restlet. As a matter of fact, Restlet has its own mechanism regarding security based on the challenge response. This allows to authenticate the user for a request, get its roles and set within ClientInfo#user. The servlet extension must be seen as an adapter to embed a Restlet engine within a servlet container but you shouldn't rely on the servlet API.
Here is the way to use security with Restlet:
public class MyApplication extends Application {
public Restlet createInboundRoot() {
Router router = new Router(getContext());
(...)
ChallengeAuthenticator ca = new ChallengeAuthenticator(getContext(),
ChallengeScheme.HTTP_BASIC, "admin");
Verifier verifier = (...)
Enroler enroler = new MyEnroler(this);
ca.setNext(router);
return ca;
}
}
Here is a sample implementation of Verifier:
public class MyVerifier extends SecretVerifier {
#Override
public boolean verify(String identifier, char[] secret) {
System.out.println(identifier);
System.out.println(secret);
//TODO compare with the Database
return true;
}
}
Here is a sample implementation of Enroler:
public class MyEnroler implements Enroler {
private Application application;
public MyEnroler(Application application) {
this.application = application;
}
public void enrole(ClientInfo clientInfo) {
Role role = new Role(application, "roleId",
"Role name");
clientInfo.getRoles().add(role);
}
}
You can then have access the security / authentication hints from the request within filter, server resource, ..., as described below:
User user = getRequest().getClientInfo().getUser();
List<Role> roles = getRequest().getClientInfo().getRoles();
You can notice this mechanism is opened in Restlet and can support a wide set of authentication (oauth2, ...). It's not really the good approach to use cookie-based authentication with REST. That said, you can use it even with Restlet.
Hope it helps you,
Thierry
Related
I've been following this tutorial in order to create an Authentication Server, but I'm facing some problems regarding the concepts, I guess.
Look, when I register a Client in Repository, I have to define some parameters, like its id, secret, authentication method, grant types, redirection uris and scopes:
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("articles-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope("articles.read")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
When I'm back to my Resource Server, I find that my client was successfully logged in and it returns with an "articles.read" scope. Everything is fine here, supposing that I want to protect my endpoints with the Client's scope, but this is not my case.
In my situation, I want to protect my endpoints according to my User's role in database.
I'll give you an example, so you don't have to read the whole Baeldung's website:
I try to access: http://localhost:8080/articles.
It redirects to: http://auth-server:9000, where a Spring Security Login Form appears.
When you submit the proper credentials (which are compared from a database using the default Spring Security schema), it basically gets you back to: http://localhost:8080/articles.
Well, in that point, I have an Authorization Token with the Client scope, but not the logged User role.
Is there an standard way to configure my project to achieve this or, do I have to think of a creative way to do so?
Thank you in advance.
For role based authentication you should map authorities in Oauth token.
OAuth2AuthenticationToken.getAuthorities() is used for authorizing requests, such as in hasRole('USER') or hasRole('ADMIN').
For this you need to implement the userAuthoritiesMapper, something like this:
#Configuration
public class AppConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(this.userAuthoritiesMapper());
//.oidcUserService(this.oidcUserService());
super.configure(http);
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
if (userInfo.containsClaim("role")){
String roleName = "ROLE_" + userInfo.getClaimAsString("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
if (userAttributes.containsKey("role")){
String roleName = "ROLE_" + (String)userAttributes.get("role");
mappedAuthorities.add(new SimpleGrantedAuthority(roleName));
}
}
});
return mappedAuthorities;
};
}
}
I'm using a very simple httpServer in Java for an api rest with GET, POST, PUT and DELETE. I'm using Basic Authentication and I have a couple classes Authentication.java and Authorisation.java which I use to authenticate and check permissions for the users.
So, the thing is that I want all users (authenticated) to be able to GET information from my api rest, but only users with certain privileges to be able to POST, PUT and DELETE. So how can I do that?
This is what I got
public class Server {
private static HttpServer server;
public static void start() throws IOException {
server = HttpServer.create(new InetSocketAddress(8000), 0);
HttpContext ctx = server.createContext("/users", new UserHandler());
ctx.setAuthenticator(new ApiRestBasicAuthentication("users"));
server.start();
}
}
And this is my ApiRestBasicAuthentication
public class ApiRestBasicAuthentication extends BasicAuthenticator {
private UserAuthentication authentication = new UserAuthentication();
public ApiRestBasicAuthentication(String realm) {
super(realm);
}
#Override
public boolean checkCredentials(String user, String pwd) {
int authCode = authentication.authenticate(user, pwd);
return authCode == UserAuthentication.USER_AUTHENTICATED;
}
}
As this is now, check credentials is only checking if the user is authenticated.
But I'd like to check, if the method is POST, DELETE or PUT I should also check the specific credentials. But how can I get the method in my ApiRestBasicAuthentication? I'm doing that in my handler class
public void handle(HttpExchange httpExchange) throws IOException {
String method = httpExchange.getRequestMethod();
if ("post".equalsIgnoreCase(method)) {
createUser(httpExchange);
} else if ("get".equalsIgnoreCase(method)) {
readUsers(httpExchange);
} else if ("put".equalsIgnoreCase(method)) {
updateUser(httpExchange);
} else if ("delete".equalsIgnoreCase(method)) {
deleteUser(httpExchange);
}
}
Maybe this is supposed to be done some other way.
Any ideas?
Many thanks.
A simple way to do it would be to change your
ApiRestBasicAuthentication like:
public class ApiRestBasicAuthentication extends BasicAuthenticator {
private UserAuthentication authentication = new UserAuthentication();
public ApiRestBasicAuthentication(String realm) {
super(realm);
}
#Override
public Authenticator.Result authenticate(HttpExchange exch) {
Authenticator.Result result=super.authenticate(exch);
if(result instanceof Authenticator.Success) {
HttpPrincipal principal=((Authenticator.Success)result).getPrincipal();
String requestMethod=exch.getRequestMethod();
if( ADD SOME LOGIC HERE FOR PRINCIPAL AND REQUEST METHOD) {
return new return new Authenticator.Failure(401);
}
return result;
}
}
#Override
public boolean checkCredentials(String user, String pwd) {
int authCode = authentication.authenticate(user, pwd);
return authCode == UserAuthentication.USER_AUTHENTICATED;
}
}
And add some logic there for requests/users that you want to fail the authenticator. I have shown you here how to get the method in the authenticate method but you need to specify the types of credentials.
Another solution would be if you check the source code of BasicAuthenticator you can see how it implements authenticate method and you can create your own implementation in a similar way instead of extending BasicAuthenticator and use the get method instead of just the username and password. You can see the source code here and I am sure you will be able to find your way around ;)
Usually in enterprise application you can use some external security management system - for example if you use Spring (the de facto standard in the current java web apps) you can use spring security and do such security patterns and filters in a more declarative way
While the above answers might be valid for you, I think you should also consider using defined roles and security-constraints which can be defined in your web.xml and used in the REST Resource using #RolesAllowed annotation. This then allows you to specifically allow permissions for methods individually or at the REST resource/class level.
In web.xml, this looks something like this:-
<security-role>
<role-name>SERVERTOSERVER</role-name>
</security-role>
<security-constraint>
<web-resource-collection>
<web-resource-name>REST API description</web-resource-name>
<url-pattern>/<path name>/*</url-pattern>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<description>Only allow users
from following roles</description>
<role-name>SERVERTOSERVER</role-name>
</auth-constraint>
</security-constraint>
The following links have some examples: Purpose of roles tags in tomcat-users.xml? ,
https://www.thecoderscorner.com/team-blog/hosting-servers/17-setting-up-role-based-security-in-tomcat/
In case helpful, here is another type of solution for a Jersey based application: https://howtodoinjava.com/jersey/jersey-rest-security/
There might be many ways to solve this issue. Here is one of my proposal:
Create a User Object with fields that you want and one field called something like "role". Lets say only "admins" are allowed to do make Http requests other than "GET" while "regular" users can only do "GET". Many ways to do this but one way is to make the "role" field String and assign values to it using an ENUM, so that it's easy to change later and only specific terms are used. But you don't have to do that. Write get and set method for the fields you create and that you might need later, and definitely for role.
You need to make sure that class containing the handle(HttpExchange httpExchange) is able to see the currently logged in user, and refer to the User object associated with them. Then you need to modify the method so that
if(loggedInUser.getRole().equals("admin")){
//allow whatever method
} else {
// allow "GET" or give some denied permission error
}
Since other implementations have not been provided, I can't give a more detailed answer or be sure that this will work for you.
I think what you should create an AuthenticationInterceptor and by-pass GET the requests there and correspondingly apply authentication mechanism for rest non-GET requests.
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
#Autowired
private ApiRestBasicAuthentication apiRestBasicAuthentication;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
switch (request.getMethod()) {
case "GET" :
// by-passing all GET requests
return true;
default :
return apiRestBasicAuthentication.checkCredentials(username, password);
}
}
}
I have this very basic authentication for my app:
MapVerifier mapVerifier = new MapVerifier();
mapVerifier.getLocalSecrets().put("user", "pass".toCharArray());
ChallengeAuthenticator guard= new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "Secured Resources");
guard.setContext(getContext());
guard.setVerifier(mapVerifier);
How do I adapt this to use Google authentication scheme? That, instead of showing the Username/Password browser popup, it will go to the Google authentication page.
I think that you aren't in the context of a challenge authentication and you need to leverage the authentication service of Google.
Here is an implementation of this approach (not tested) if you want a custom Restlet Authenticator implementation:
public class GoogleAuthenticator extends Authenticator {
private UserService userService;
public GoogleAuthenticator(Context context) {
super(context);
this.userService = UserServiceFactory.getUserService();
}
protected User createUser(com.google.appengine.api.users.User googleUser,
Request request, Response response) {
return new User(googleUser.getUserId());
}
protected boolean authenticate(Request request, Response response) {
// Get current Google user
com.google.appengine.api.users.User googleUser = userService.getCurrentUser();
// Check if the user is authenticated
if (googleUser!=null) {
// Authenticated through Google authentication service
request.getClientInfo().setUser(
createUser(googleUser, request, response));
return true;
} else {
// Not authenticated. Redirect to the login URL
response.redirectSeeOther(userService.createLoginURL(
request.getRequestURI()));
return false;
}
}
}
However such authenticator exists in the extension org.restlet.ext.gae for a while. It leverages the service UserService of GAE. So I think that you have it with the version of Restlet you use. Here is a sample of use below:
public Restlet createInboundRoot() {
Router router = new Router(getContext());
(...)
GaeAuthenticator guard= new GaeAuthenticator(getContext());
guard.setNext(router);
return guard;
}
Edited:
You can notice that the GAE authenticator can use the GAE enroler for this purpose (i.e. if it's an admin one).
To implement this, you simply need to instantiate such enroler and set it on your authenticator, as desribed below:
GaeEnroler enroler = new GaeEnroler();
GaeAuthenticator guard = new GaeAuthenticator(getContext());
guard.setEnroler(enroler)
guard.setNext(router);
Within your server resource, you can then check the role, as described below:
protected boolean hasAdminRole() {
ClientInfo clientInfo = getClientInfo();
List<Role> roles = clientInfo.getRoles();
boolean isAdmin = false;
for (Role role : roles) {
if (role.getName().equals("admin")) {
isAdmin = true;
break;
}
}
return isAdmin;
}
#Post
public XX handlePost(YY content) {
if (!hasAdminRole()) {
throw new ResourceException(Status.CLIENT_ERROR_FORBIDDEN);
}
(...)
}
Hope it helps you,
Thierry
I haven't fully understood what's ur question is ? If u wanted to integrate Google authentication in yours system check the link
google Oauth2
It's not depend upon any framework it's simply redirection and callback which u can do with plain servlets , obviously you can do with restlets too
I have written an simply library to integrate google and Facebook oauth 2, you can check this to see how it works
java oauth2 gae
I'm attempting to implement a RESTful API using Restlet and have found very little on anything more than the basic Role and Method Authorizers. I have stored in a database the routes and methods for those routes that a user can access. The issue I'm running into now is how to get the path in the Authorizer. Is it the resource I'm needing to gather? And how exactly am I supposed to route to the authorizer? I've posted what I have so far an am looking how in my Authorizer to get the path or resource. Any information is appreciated, I've looked though books and many generic examples and haven't found quiet what I'm looking for.
My Routing Application:
public class MyRoutingApp extends org.restlet.Application {
#Override
public synchronized Restlet createInboundRoot() {
Context context = getContext();
Router router = new Router(context);
router.attach("/user", Users.class);
router.attach("/post", Posts.class);
router.attach("/comment", Comments.class);
ChallengeAuthenticator authenticator = new ChallengeAuthenticator(
context, ChallengeScheme.HTTP_BASIC, "My test realm" );
//create Verifier to ensure that the user is authenicated
MyVerifier verifier = new MySecretVerifier();
//grab user Roles and add them to the request
MyEnroler enroler = new MyEnroler();
authenticator.setVerifier( verifier );
authenticator.setEnroler( enroler );
//Looks up if user can be allowed to resource
MyAuthorizer authorizer = new MyAuthorizer();
authorizer.setNext( router );
authenticator.setNext( authorizer );
return authenticator;
}
}
My Authorizer:
public class MyAuthorizer extends Authorizer {
#Override
protected boolean authorize( Request request, Response response ) {
//has the security roles and user from verifier and enroler
ClientInfo info = request.getClientInfo();
//get http method
Method method = request.getMethod();
//need to get the route or resource user is attempting to access
//allow or disallow access based on roles and method
}
}
The target resource URI is available via the Request#getResouceRef().getRemainingPart().
I have started using JAX-RS to created a simple restful interface to my web application. Currently, it is only being used (read only) by one internal client which has access to all application data, and I am using http basic authentication for access. I would like to start using it as part of the view layer of my app, and certain operations will only be allowed if a user is logged in via the web app. I am struggling to find a pattern that allows me to use both forms of authentication in an elegant way, without repeating a lot of code. Here is roughly what I have come up with:
First a util class for loading an application session, which is stored in the database.
public class RestUtil {
public static AppSession getAuthenticatedSession(HttpServletRequest request) {
AppSession session;
String remoteUser = request.getRemoteUser();
if (remoteUser != null) {
session = SessionRepository.loadSessionByRemoteUser(remoteUser);
} else {
session = SessionRepository.loadSessionById(request.getSession().getId());
}
return session;
}
}
Here's our resource, with one method that is only accessible to an authenticated user, or our http basic auth client:
#Path("/protected/resource")
public class ProtectedResource {
#GET
#Produces(MediaType.TEXT_JSON)
#Path("{userId}")
public String getProtectedResourceJson(#Context HttpServletRequest request, #PathParam("userId") Integer userId) {
// Return Charity List XML
AppSession session = RestUtil.getAuthenticatedSession(request);
if (session.canAccessUser(userId)) //get Json...
}
}
Here's the most basic view of the AppSession, for the purpose of this question:
public class AppSession {
User authenticatedUser;
String remoteUser;
public boolean canAccessUser(Integer userId) {
if (remoteUser != null) {
//this client has access to all users
return true;
} else if (authenticatedUser.getId().equals(userId)) {
//this is local client, calling the service from a view
//only has access to authenticatedUser
return true;
} else {
return false;
}
}
}
Furthermore, for services that do not require any sort of authentication, how do I prevent unauthorized third parties from just pointing at the url, and grabbing the data at their leisure?
You're getting to the point when it is worth looking into using aspect-oriented programming to split the security side of things from your business logic. If you're already using Spring to assemble the pieces of your app (which I recommend for complex servers) then it's just a matter of adding in Spring AOP to inject the security logic. Otherwise, use AspectJ directly. The actual logic to handle the multiple login modes will probably have to be custom, but at least you can keep it quarantined.
If using Spring, consider using Spring Security; that builds on top of Spring AOP and supplies you with much more of the solution.