I'm running a quarkus setup with keycloak. To start of I am trying to implement the tutorial into my code. I have to note here that i didn't copy the realm that is used. The reason for this is that we received a realm from our tutors.
This is the code I am trying to implement. The problem with is that it doesn't return the user.
#Path("/api/users")
public class UserResource {
#Inject
SecurityIdentity identity;
#GET
#Path("/me")
#NoCache
public User me() {
return new User(identity);
}
public static class User {
private final String userName;
User(SecurityIdentity identity) {
this.userName = identity.getPrincipal().getName();
}
public String getUserName() {
return userName;
}
}
}
When I enabled policy enforcer, I wasn't even able to login, all request became 403 or 401.
quarkus.keycloak.policy-enforcer.enable=true
How can we solve this issue?
Another question would be if it is possible to retrieve some sort of userId from keycloak?
Related
I am very confused about this architecture. I am not even sure is it possible.
I have more than 10 microservises and a API Gateway. I want to add authentication and authorization to this system. One of this services is authentication-server and it has
an endpoint which is /signin
#PostMapping(value = "/signin")
public UserLoginResponse login(#Valid #RequestBody UserLoginRequest userLoginRequest) {
return authService.login(userLoginRequest);
}
public class UserLoginResponse {
private String accessToken; //accessToken is jwt token and it has ROLE field.
}
public class UserLoginRequest {
private String username;
private String password;
}
Here is the confusing part for me: Right now gateway creates code duplication. When I add to a new endpoint, I need to add almost same controller/service/models to API Gateway.
For example:
Lets say microservice A has /product endpoint, these are (veeery roughly) the classes I should have
// Controller
class ProductController {
#GetMapping("/product/{id}")
public ProductResponse getProduct(#PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
public getProduct(String id){
return productRepository.get(id);
}
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
Our team also has implemented classes in the gateway.
//Controller has authorization with #PreAuthorize annotation.
#RestController
#PreAuthorize("hasAnyRole('USER', 'ADMIN')")
class ProductController {
#GetMapping("/product/{id}")
public ProductResponse getProduct(PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
private final ClientApi productClientApi;
public ProductService(ClientApi productClientApi) {
this.productClientApi = productClientApi;
}
public getProduct(String id){
return productClientApi.getProduct(id);
}
}
//This is feign client. It makes http requests to product-api
#FeignClient(
"product-api",
url = "\${product-api.base-url}",
configuration = [FeignClientConfiguration::class]
)
interface ClientApi(){
#GetMapping( value = {"product/{id}"}, consumes = {"application/json"} )
ProductResponse getProduct(#PathVariable String id);
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
When a request comes to /product its jwt token controlled here and if it has proper permission, it goes to the service layer,
Service layer makes request to product-api(which is microservice A)
returns the response
Question: There should be easier way. Every new endpoint in the services costs us code duplication in Gateway. I think I want just routing. Whatever comes to gateway directly should be routed to services and it should still has the authentication/authorization responsibility. I know that I can do that as below with spring-cloud-gateway but I couldn't figure out how can i do that with authentication and authorization. Can anyone explain me that am i thinking wrong ?
spring:
application:
name: "API-GATEWAY"
cloud:
gateway:
routes:
- id: product-service
uri: 'http://product-url:8083'
predicates:
- Path=/product/**
I have a rest api which has role based access to its http POST method. It uses spring-security to access api only for authorized users.
I have 2 questions,
how external client can pass request body and user object (#AuthenticationPrincipal) to make this api call
How can I write junit to test below piece of code,
#PreAuthorize("hasAuthority('ADMIN')")
#PostMapping("/api/access/submit")
public ResponseEntity<OrderAdminResponse> create(#RequestBody OrderAdminRequest orderAdminSubmitRequest,#AuthenticationPrincipal UserObject user)
{
return ResponseEntity.accepted().body(orderService.submit(orderAdminSubmitRequest));
}
My User Object is below,
<code>
public class UserObject {
private final String name;
private final String id;
private final String email;
private UserObject(String name, int id, String email){
this.name = name; this.id = id; this.email = email
}
public Collection<String> getRoles() {
return
(Collection)this.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList());
}
public boolean isUserInRole(String role) {
return this.getAuthorities().stream().anyMatch((a) -> {
return a.getAuthority().equals(role);
})
}
}
</code>
The controller will automatically populate the Authentication Principal, when you make a request with an authenticated user.
For example, if you are using HTTP basic authentication to secure your endpoint, then the principal will be populated from the Authorization header. Your request body remains the same regardless of whether you are extracting the principal or not.
An easy way to test your controller is to use the support provided by Spring Security.
If you are using MockMvc, one option is to use a post processor to call your endpoint with different types of users.
this.mvc.perform(post("/api/access/submit")
.content("...")
.with(user("user").roles("USER")))
.andExpect(status().isUnauthorized());
this.mvc.perform(post("/api/access/submit")
.content("...")
.with(user("admin").roles("ADMIN")))
.andExpect(status().isOk());
You can learn more about Spring Security test support in the documentation here.
TLDR
There is a need in the Java Web Application I develop, to implement user distinction over the REST services. I know that there are some annotations (#RolesAllowed, #PermitAll, #DenyAll), that can describe which role can use the service. My question is, how can I match the roles from #RolesAllowed with user role enumaration, that user is a persisted object stored in a DB??
Many tutorials explain the #RolesAllowed, but I found none to match those roles, with already created ones
Explanation
How to validate a user, by checking his role (found by session ID) automatically? I know Jersey do this already, by registering RolesAllowedDynamicFeature.class. I already managed to check that this RolesAllowedDynamicFeature.class works, by putting the #DenyAll annotation into a method, and returned a 403 error.
First of all, let's start with what is a user. Users are DB Entities, using Ebean to persist them from and to Java objects. Here is a sample from a User.class:
/*
* A sample that describes the fields that I ask for and to understand the concept
*/
#Entity
#Table(name = "users")
public class User extends Model
{
#Id
#GeneratedValue
#Column(name = "id")
private long id;
#Enumerated(EnumType.STRING)
#Column(name = "role", nullable = false)
private UserRole role;
// contructors getters setters helper methods etc
/**
* Fetch a user from DB
*
* #param id the id to search for
* #return a Person.class object or may return null
*/
public static User getUserById(Long id)
{
return Ebean.find(User.class, id);
}
/* and here is the UserRole enum that define the roles every user can have */
public static enum UserRole
{
Administrator, User, Manager;
}
}
All above code, works fine, user are stored correctly and I can fetch them easily.
Every User, at login, is authenticated with a similar service and a ConnectedUser object is created with a unique session ID (using UUID) for each one.
After each service call, there is an authentication running, that Authenticates if a user can use this service by searching, if there is a connected user entry for this session (which is stored as a cookie):
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter
{
#Context
private HttpServletRequest request;
/**
* Authenticates a user's access with every request that is made via a token.
*
* #param requestContext The request that is sent to the server.
* #throws IOException
*/
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
boolean isValidated;
Cookie sessionCookie = null;
Cookie[] cookies = request.getCookies();
if (cookies.length != 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("CookieName")) {
sessionCookie = cookie;
}
}
}
if (sessionCookie != null) {
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser(sessionCookie.getValue(), request.getRemoteAddr());
}
else {
MultivaluedMap pathParameters = requestContext.getUriInfo().getQueryParameters();
// UserValidationHandler checks if user is in connected_users table
isValidated = UserValidationHandler.validateUser((String)pathParameters.getFirst("token"), request.getRemoteAddr());
}
if (!isValidated) {
LOGGER.warn("[Authorization filter] Unauthorized user.");
URI indexURI = URI.create("http://login.jsp");
requestContext.setRequestUri(indexURI);
}
}
}
Notes:
Note1: Most implementations suggest to apply the roles into web.xml file. This is not doable though I think, for my case.
Note2: Also, where is the right place to authorize a user to use a service? I found that I can create ContainerRequestFilter class, with #Priority(Priorities.AUTHORIZATION). Which is better to do?
I'm kinda lost here. I read many of the Q/As or samples out there, but nothing explain it thorough.
You should separate the concerns and do the authentication in one filter and authorization in one filter. Generally how this is accomplished is by setting the SecurityContext from inside the authentication filter, and then retrieving it from inside the authorization filter.
The SecurityContext has a isUserInRole method that you override. This method should be called in the authorization filter. Generally you will have the roles as a member of the SecurityContext, and just iterate the roles
static class MySecurityContext implements SecurityContext {
private final String[] userRoles;
public MySecurityContext(String[] roles, String user) {
this.userRoles = roles;
}
#Override
public Principal getUser() {
return new Principal() {
#Override
public String getName() {
return name;
}
}
}
public boolean isUserInRole(String role) {
for (String userRole: userRoles) {
if (role.equals(userRole) {
return true;
}
}
return false;
}
// more methods to override
}
In the authentication filter, you just call the requestContext.setSecurityContext method passing in the new SecurityContext.
In the authorization filter, you would get the #RolesAllowed annotation by using the ResourceInfo. For example
class AuthorizationFilter implement ContainerRequestContext {
#Context
private ResourceInfo info;
#Override
public void filter(ContainerRequestContext request) {
SecurityContext sc = request.getSecurityContext();
RolesAllowed anno = info.getResourceMethod().getAnnotation(RolesAllowed.class);
String rolesAllowed = anno.value();
for (role: rolesAllowed) {
if (sc.isUserInRole(role)) {
return;
}
}
request.abortWith(Response.status(403).build());
}
}
Or something along those lines.
If you are using Jersey2 though, you don't need to implement the authorization yourself. This is already implemented in the RolesAllowedDynamicFeature. You just need to register the feature with your application. It works the same as I mentioned previously; A SecurityContext is expected to be filled prior to reaching the authorization filter, the the filter will check the roles and either authorize or reject.
My implementation is different from that of peeskillet but it has worked for me.
The code implementing security might look something like this
public class ServiceFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext req) throws IOException {
//user name and password are obtained from the header
String auth = req.getHeaderString(HttpHeaders.AUTHORIZATION);
if(auth == null) {
throw new WebApplicationException(Status.UNAUTHORIZED);
}
//user name and password
String[] credentials = auth.substring(1, auth.length()-1).split(":");
String user = credentials[0];//user name
String password = credentials[1];//password
if(user == null || password == null)
throw new WebApplicationException(Status.UNAUTHORIZED);
ServiceSecurity ss = null;
//user name and password are hardcoded here but you better put them in a DB or file
if(user.equals("servUser") && password.equals("service"))
ss = new ServiceSecurity(new ServiceUser("servUser"));
else if(user.equals("servAdmin") && password.equals("admin"))
ss = new ServiceSecurity(new ServiceUser("servAdmin"));
else
throw new WebApplicationException(Status.UNAUTHORIZED);
req.setSecurityContext(ss);
}
}
The security context
import java.security.Principal;
import javax.ws.rs.core.SecurityContext;
public class ServiceSecurity implements SecurityContext {
private ServiceUser sUser;
public ServiceSecurity(ServiceUser sUser){
this.sUser = sUser;
}
#Override
public String getAuthenticationScheme() {
// TODO Auto-generated method stub
return SecurityContext.DIGEST_AUTH;
}
#Override
public Principal getUserPrincipal() {
// TODO Auto-generated method stub
return sUser;
}
#Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean isUserInRole(String role) {
// TODO Auto-generated method stub
return sUser.getRole().equals(role) ? true : false;
}
}
The security context implementation class is initialized with a class implementing Principal (ServiceUser), which is used to get the role for this particular user.
import java.security.Principal;
public class ServiceUser implements Principal {
private String role;
public ServiceUser(String role){
this.role = role;
}
public ServiceUser(){
}
public String getRole(){
return role;
}
public void setRole(String role){
this.role = role;
}
public String getName(){
return "some name";
}
}
In this case, you'd add the following to your server method
#RolesAllowed("servUser")
The credentials (user name and password) should be supplied in the request header (as is evident from the filter method above)
I've just created my own custom authentication on my google app engine Java app. And it wasn't that much of a trouble as is the next thing I'm trying to do.
Authentication works fine but now I'm trying to add some additional fields to the default User object so that I wouldn't have to make so many calls to the server.
So what I've done so far is created a custom class that implements Authenticator. Based on whether the user is authenticated or not the authenticate method returns the User object or null. User object is then accessible to my API endpoints.
To extend my app functionality I've tried extending the default User object, making some new fields, and then passing it to endpoints. However, since the User object accessible by endpoints is not the same kind as the one I extended from I can't get the extra fields.
MyAuthenticator.java
import com.google.api.server.spi.auth.common.User;
public class MyAuthenticator implements Authenticator {
#Override
public User authenticate(HttpServletRequest request) {
// some code
return new AuthUser(...)
}
AuthUser.java
import com.google.api.server.spi.auth.common.User;
public class AuthUser extends User {
private String newToken;
public AuthUser(String email) {
super(email);
}
public AuthUser(String id, String email) {
super(id, email);
}
public AuthUser(String id, String email, String newToken) {
super(id, email);
this.newToken = newToken;
}
public String getNewToken() {
return newToken;
}
}
UserEndpoint.java
import com.google.appengine.api.users.User;
#Api(authenticators = MyAuthenticator.class)
public class UserEndpoint {
#ApiMethod(httpMethod = "GET")
public final Response sth(User user)
throws UnauthorizedException {
EndpointUtil.throwIfNotAuthenticated(user);
// ...
}
Notice different class imports.
I can't use AuthUser in UserEndpoint sth method because then API expects me to post that object with my call to server.
How can I pass extra data from authenticator to my endpoint method?
AppEngine docs say the injected types are the following:
com.google.appengine.api.users.User
javax.servlet.http.HttpServletRequest
javax.servlet.ServletContext
However, it doesn't mention com.google.api.server.spi.auth.common.User, but it works for sure. I just tried with AppEngine Java SDK 1.9.32. I don't know if it's a bug or feature.
So in UserEndpoint.java, you have to import com.google.api.server.spi.auth.common.User, then you can cast it to AuthUser.
import com.google.api.server.spi.auth.common.User;
#Api(authenticators = MyAuthenticator.class)
public class UserEndpoint {
#ApiMethod(httpMethod = "GET")
public final Response sth(User user)
throws UnauthorizedException {
EndpointUtil.throwIfNotAuthenticated(user);
((AuthUser)user).getNewToken();
// ...
}
I have a model for logging in user in my REST API, corresponds to User table (email and password as table columns)
#Entity
public class User {
#Id
#GeneratedValues
private Long id;
private String email;
private String password;
+GET , +SET
}
Then there is #Controller which is making call to above User Entity using JPAService
#Controller
#RequestMapping("/rest/auths")
public class AuthController {
#Autowired
private UserService authService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ResponseEntity<AuthLoginFormResource> login(#RequestBody AuthLoginFormResource sentAuth) {
User user = authService.login(sentAuth.toUser());
AuthLoginFormResource res = new AuthLoginFormResourceAsm().toResource(user);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(res.getLink("self").getHref()));
return new ResponseEntity<AuthLoginFormResource>(res, HttpStatus.OK);
}
}
AuthLoginFormResource : -
public class AuthLoginFormResource extends ResourceSupport {
private String email;
private String password;
private boolean success;
public User toUser() {
User user = new User();
user.setEmail(email);
user.setPassword(password);
//user.setSuccess(false);
return user;
}
+GET, +SET
}
AuthLoginFormResourceAsm : -
public class AuthLoginFormResourceAsm extends ResourceAssemblerSupport<User, AuthLoginFormResource> {
public AuthLoginFormResourceAsm() {
super(User.class, AuthLoginFormResource.class);
}
#Override
public AuthLoginFormResource toResource(User user) {
AuthLoginFormResource res = new AuthLoginFormResource();
res.setEmail(user.getEmail());
res.setPassword(user.getPassword());
//res.setSuccess(user.isSuccess()); // Success is not existing in USER
res.add(linkTo(AuthController.class).withSelfRel());
return res;
}
}
There are 2 issues -
I need to send a success flag as boolean in response for which i have added a boolean success to AuthLoginFormResource. But, AuthLoginFormResource gets set
only from AuthLoginFormResourceAsm.toResource method , which in turn does
it from entity User. As User entity models database where there is
no success column, I am not able to set success at this place.
So, should I add dummy success field to User Entity and set that from service
method , though there is no such field in database or create a new Entity representing Login Form here and return that ?
Same problem with another field that is a token for authentication
which does not exist in database but is part of response.
What is correct place for setting such fields in ResourceSupport object - inside database Entity and return from Service / creating another Form Model entity on top of Domain Model and return from service.
This is basic question I am facing in many places where data model and forms don't match one to one.
I strongly recommend the following;
Modify UserService.login method to return true or false based on successfull authentication instead of retrieved user object from database.
Return only true or false with status OK and FAIL, as part of the response not the entire AuthLoginFormResource. This is a bad practice because you are sending out the username and password as part of the request and response, back and forth in a roundtrip. If someone is evesdropping they can easily figure out what username passwords work and what don't.
Or
Consider using Basic Authorization, Digest Authorization or OAuth if you fancy than this custom Authentication Implementation. Using Spring Security you can achieve any of the aforementioned really easily.