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.
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 trying to learn spring and to achieve that i'm building a REST application from scratch. I'm confused where should I check constraints in my application: Controller layer vs. Service layer.
For example, in create user method I want to check if there is any other user with the same email, since email is unique in my database. I also want to check if password matches(password and "confirm password" fields) etc.
Currently, in my implementation, all this things are verified in Controller layer so I can return a ResponseEntity for every approach.
#PostMapping("/signUp")
public ResponseEntity<Object> createUser(#RequestBody RegisterUserDto user) {
if (userService.getUserByEmail(user.getEmailAddress()) != null) {
return ResponseEntity.badRequest().body("email already exists");
}
if (!user.getPassword().equals(user.getConfirmPassword())) {
return ResponseEntity.badRequest().body("passwords are not the same");
}
User savedUser = null;
try {
savedUser = userService.createUser(userDtoConversions.convertToEntityRegister(user));
} catch (ParseException e) {
e.printStackTrace();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
Create user method in Service layer:
#Override
#Transactional
public User createUser(User newUser) {
newUser.setDateCreated(new Date());
return userRepository.save(newUser);
}
So which approach is better? If I checks constraints and validations in Service layer, what should I return so I would know in my controller why create user fails?
In my mind the best place to handle exceptions is the service layer. For my a REST controller method should, at most, handle the request and pass it over to a service method.
With this approach you have very clearly defined layers that do a very clearly defined job. For example your service layer will handle the validation of the request, the persisting action and also will provide (if needed) a return object to the controller, which then will wrap into the appropriate response object (ResponseEntity in you case).
With that in mind, there is nothing stopping you to throw any kind of exceptions in the service layer and have translated into proper responses. Spring has a very neat and powerful mechanism that does precisely that which is called an exception handler.
So in your case for the password checking action you could do something like:
if (!user.getPassword().equals(user.getConfirmPassword())) {
throw new PasswordMismatchException("Passwords are not the same for user:: " + user.getName());
}
Where the PasswordMismatchException is a RuntimeException. With something like that, you can then go ahead and setup an ExceptionHandler along with the appropriate method to intercept this and translate it into a response. A simple example would be:
#RestControllerAdvice
public class ApplicationExceptionHandler {
#ExceptionHandler(PasswordMismatchException.class)
public ResponseEntity<String> handleBadPasswords(PasswordMismatchException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
You can read up for more on this on Spring's documentation:
Spring ExceptionHandler
Exception Handling in Spring
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 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
I am using JBoss AS and JAX-RS for creating REST endpoints.
Lets say my class looks like
#Path("/users")
public class UserResource {
#GET
public Response getAccount() {
return "hello";
}
}
Now getAccount is not authenticated at the moment
Wanted
- I would like to add authentication so that when code hits getAccount the user is authenticated
- I would like the authentication to be driven by annotations instead of XML configurations, if at all possible
- I would like to do the database comparison to see if the user is valid
Problem
- I have never done that so I have no idea how to implement it
- I have googled around a lot and found Jersey examples
UPDATE
- I would like to send authentication credentials with each request and not creating any session
Please guide me with one simple working example and I would try to extend from there
You need is a Stateless Spring Security configuration in front of your JAX RS end points.
I have addressed exact problem you are trying to solve but I don't have my own code to share..
Here is one project which has done the exact thing you are asking, Some wise man has done it all for you ;)
https://github.com/philipsorst/angular-rest-springsecurity
What is the magic ?
You have one unprotected URL which does the Authentication, and set the user roles as well..
Then you return some kind of Token, put it some where in cache which will be expected on every subsequent call..
Upon new request on other protected resources, you will check if the Token is present in your cache/session store ( you need some mechanism to keep track of valid tokens )
If token is resent and valid, you do the programmatic Log-in in Spring Security which ensures that you can use all the Security features spring provides, ( Annotations, JSTL Tags etc.. ) !
Once passed token validation you will get the logged in user details in your controllers ( aka JAX RS resources ) to deal with security further..
If the token was not valid or not present , it would be trapped by failure end point which would return appropriate response ( 401 )
Refer Following Link To Understand How Stateless Spring Security is configured..,
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/resources/context.xml
See how a user is validated for the first time and a token is generated..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/resources/UserResource.java
Here is the class where programmatic login is performed on every request after token
check..
https://github.com/philipsorst/angular-rest-springsecurity/blob/master/src/main/java/net/dontdrinkandroot/example/angularrestspringsecurity/rest/AuthenticationTokenProcessingFilter.java
I solved this with following code.
note Token mechanism will be updated once I do that
I have solved this by modifying the interceptor I have, the following is code
Annotation
#Inherited
#InterceptorBinding
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface SecurityChecked {
}
Resource Class
public class SecureResource {
#GET
#SecurityChecked
public Response getUser() {
return Response.ok("authenticated successfully!").build();
}
}
Interceptor class
#Interceptor
#Provider
#ServerInterceptor
#SecurityChecked
public class SecurityCheckInterceptor implements PreProcessInterceptor, AcceptedByMethod {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityCheckInterceptor.class);
#Nullable
#Override
public ServerResponse preProcess(final HttpRequest request, final ResourceMethod method) throws Failure, WebApplicationException {
final List<String> authToken = request.getHttpHeaders().getRequestHeader("X-AUTH");
if (authToken == null || !isValidToken(authToken.get(0))) {
final ServerResponse serverResponse = new ServerResponse();
serverResponse.setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
return serverResponse;
}
return null;
}
private static boolean isValidToken(#Nonnull final String authToken) {
LOGGER.info("validating token: " + authToken);
return true;
}
#SuppressWarnings("rawtypes")
#Override
public boolean accept(final Class declaring, final Method method) {
// return declaring.isAnnotationPresent(SecurityChecked.class); // if annotation on class
return method.isAnnotationPresent(SecurityChecked.class);
}
}
and then I run my Integration tests by deploying the resource class in JBoss and issuing following commands on command-line
curl --header 'X-AUTH: 1a629d035831feadOOO4uFReLyEW8aTmrCS' http://localhost:8080/market-1.0-SNAPSHOT/rest/login
curl --header 'InvalidHeader: InvalidHeaderValue' http://localhost:8080/market-1.0-SNAPSHOT/rest/login