I have a Netflix Zuul server receiving all HTTP requests and forwarding them to my microservice.
The underlying microservice authenticates users through Facebook oauth API. I receive the user data from Facebook API and I authenticate the user programmatically:
public Authentication login(String username, String password) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
Authentication result = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(result);
return result;
}
With Zuul in front this doesn't work. I think it sets up the session in the microservice environment, but never extends it to Zuul. It works fine when requests go straight to microservice.
What I've done:
I cleared the sensitiveHeaders property in my application.properties:
zuul.routes.[SERVICE_NAME].sensitiveHeaders=, but it's meant to work the other way around I think.
How do I setup a session with Zuul?
Edit:
My Zuul configuration:
# Zuul proxy
zuul.add-host-header=true
zuul.routes.httpbin.path=/**
zuul.routes.httpbin.serviceId=httpbin
zuul.routes.httbin.sensitiveHeaders=
httpbin.ribbon.listOfServers=http://localhost:8090
ribbon.eureka.enabled=false
Related
I have a controller method that has an argument OAuth2AuthorizedClient with annotation #RegisteredOAuth2AuthorizedClient.
ResponseEntity<String> getFoo(
#RegisteredOAuth2AuthorizedClient("custom") OAuth2AuthorizedClient client) {
OAuth2AccessToken token = client.getAccessToken();
....
}
In order to resolve this, spring look for OAuth token on OAuth2AuthorizedClientService which actually stores the previously authenticated tokens in memory.
Now, I have a scenario where I obtain JWT token from OAuth server outside of this spring resource server, and trying to authenticate this server by passing token using Authorization header.
But when spring trying to resolve OAuth2AuthorizedClient it is looking for token in-memory with the principle of JWT (which will obviously not found since token is not obtained on this server). Hence send new login redirection for new token.
Overall question would be, is it possible to resolve OAuth2AuthorizedClient with the JWT token (obtained ourside of the server) passed in Authorization header?
I want to implement AWS Cognito server side flow with spring boot. I don't quite understand what the flow should be. Should I use spring oauth along with it ?
Requirement is something like this.
As an admin create user and give access to these created users to use my API from API Gateway (Let's ignore API Gateway part and say we just need access token from cognito for now)
Here is what I think should happen if I use AWS cognito with spring oauth2
user hits localhost:8000/oauth/token - with basic authentication (username and password)
which will do an API call with user credentials. User receives the token and uses it however he/she needs it.
Is this flow secure ? Should I use spring oauth along ?
How to handle respond to auth challenge ? Should user pass new password for first time when calling my application API ?
#RestController
public class Oauth {
#PostMapping(path = "/oauth/token")
public AdminInitiateAuthResult token(#RequestHeader("username") String username, #RequestHeader("password") String password) {
AWSCognitoIdentityProvider provider = AWSCognitoIdentityProviderClientBuilder
.standard()
.withRegion(Regions.US_WEST_2)
.withCredentials(new AWSStaticCredentialsProvider()).build();
Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", username);
authParams.put("PASSWORD", password);
AdminInitiateAuthRequest adminInitiateAuthRequest = new AdminInitiateAuthRequest()
.withClientId("{client-id}")
.withUserPoolId("{user-pool-id}")
.withAuthFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
.withAuthParameters(authParams);
AdminInitiateAuthResult authResult = provider.adminInitiateAuth(adminInitiateAuthRequest);
return authResult.getAuthenticationResult().getIdToken();
}
}
Business requirement is quite simple there needs to be a pool of users (cognito in this case) who can get some kind of a token to access few APIs. I want to achieve this using spring boot, since the API is written using spring boot and also I use AWS Api Gateway
Should I use spring oauth along with it ?
No. Authorization is done by API Gateway.
API clients need to obtain token from Cognito (i.e. authenticate themselves there) before using API. There is no need to do anything on application (Spring) side.
Details are here.
If you want to implement authentication for API clients using Cognito, then see Cognito docs for examples and manuals.
FYI Application Load Balancer can be used to handle all authentication flow for API.
My application consists of:
backend/resource server
UI webapp
keycloak
The UI is talking with the backend server via RESTful API using the keycloak client with authorization code grant flow. This is working fine.
Now, I need the additional possibility to access resource of the backend using a system/service account (with usually more permissions than the user). How would you implement this requirement? I thought the client credentials flow would be useful here.
Is it possible to use the OAuth2 client credentials flow with the keycloak client for Spring Boot? I found examples that used the Spring Security OAuth2 client features to achieve a client credentials flow but that feels weird because I already use the keycloak client for the OAuth thing.
Edit: Solution
Thanks for your answers which helped me a lot. In my UI webapp, I am now able to communicate with the backend either by using the authenticated user OAuth2 token or by using the token from the client credentials flow of my UI service account. Each way has its own RestTemplate, the first is done via the keycloak integration and second is done by Spring Security OAuth2 as explained here.
Yes, you can use OAuth 2.0 Client Credentials flow and Service Accounts.
Keycloak suggest 3 ways to secure SpringBoot REST services:
with Keycloak Spring Boot Adapter
with keycloak Spring Security Adapter
with OAuth2 / OpenID Connect
Here is a good explanation about this with an example in the OAuth2/OIDC way:
Tutorial by Arun B Chandrasekaran
Code sample by Arun B Chandrasekaran
If you follow this example, keep in mind:
Take care to configure your client as:
Access Type: Confidential
Authorization: Enabled
Service Account (OAuth Client Credentials Flow): Enabled
Take care to configure your target service as:
Access Type: Bearer-only
So, caller should be confidential and target service should be bearer-only.
Create your users, roles, mappers... and assign roles to your users.
Check that you have this dependencies in your spring project:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Configure authentication to be used in the REST client (application.properties)
e.g.:
security.oauth2.client.client-id=employee-service
security.oauth2.client.client-secret=68977d81-c59b-49aa-aada-58da9a43a850
security.oauth2.client.user-authorization-uri=${rest.security.issuer-uri}/protocol/openid-connect/auth
security.oauth2.client.access-token-uri=${rest.security.issuer-uri}/protocol/openid-connect/token
security.oauth2.client.scope=openid
security.oauth2.client.grant-type=client_credentials
Implement your JwtAccessTokenCustomizer and SecurityConfigurer (ResourceServerConfigurerAdapter) like Arun's sample.
And finally implement your service Controller:
#RestController
#RequestMapping("/api/v1/employees")
public class EmployeeRestController {
#GetMapping(path = "/username")
#PreAuthorize("hasAnyAuthority('ROLE_USER')")
public ResponseEntity<String> getAuthorizedUserName() {
return ResponseEntity.ok(SecurityContextUtils.getUserName());
}
#GetMapping(path = "/roles")
#PreAuthorize("hasAnyAuthority('ROLE_USER')")
public ResponseEntity<Set<String>> getAuthorizedUserRoles() {
return ResponseEntity.ok(SecurityContextUtils.getUserRoles());
}
}
For a complete tutorial, please read the referred Arun's tutorial.
Hope it helps.
Following #dmitri-algazin you to implement the workflow you have basically two options:
If you want to cover other IdMs besides Keycloak which solves somehow the Single Responsibility principle, I would use RestTemplate. Below you can find the variables:
//Constants
#Value("${keycloak.url}")
private String keycloakUrl;
#Value("${keycloak.realm}")
private String keycloakRealm;
#Value("${keycloak.client_id}")
private String keycloakClientId;
RestTemplate restTemplate = new RestTemplate();
private static final String BEARER = "BEARER ";
First you need to generate the access token:
#Override
public AccessTokenResponse login(KeycloakUser user) throws NotAuthorizedException {
try {
String uri = keycloakUrl + "/realms/" + keycloakRealm +
"/protocol/openid-connect/token";
String data = "grant_type=password&username="+
user.getUsername()+"&password="+user.getPassword()+"&client_id="+
keycloakClientId;
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/x-www-form-urlencoded");
HttpEntity<String> entity = new HttpEntity<String>(data, headers);
ResponseEntity<AccessTokenResponse> response = restTemplate.exchange(uri,
HttpMethod.POST, entity, AccessTokenResponse.class);
if (response.getStatusCode().value() != HttpStatus.SC_OK) {
log.error("Unauthorised access to protected resource", response.getStatusCode().value());
throw new NotAuthorizedException("Unauthorised access to protected resource");
}
return response.getBody();
} catch (Exception ex) {
log.error("Unauthorised access to protected resource", ex);
throw new NotAuthorizedException("Unauthorised access to protected resource");
}
}
And then with the token you can retrieve information from the users:
#Override
public String user(String authToken) throws NotAuthorizedException {
if (! authToken.toUpperCase().startsWith(BEARER)) {
throw new NotAuthorizedException("Invalid OAuth Header. Missing Bearer prefix");
}
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", authToken);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<AccessToken> response = restTemplate.exchange(
keycloakUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/userinfo",
HttpMethod.POST,
entity,
AccessToken.class);
if (response.getStatusCode().value() != HttpStatus.SC_OK) {
log.error("OAuth2 Authentication failure. "
+ "Invalid OAuth Token supplied in Authorization Header on Request. Code {}", response.getStatusCode().value());
throw new NotAuthorizedException("OAuth2 Authentication failure. "
+ "Invalid OAuth Token supplied in Authorization Header on Request.");
}
log.debug("User info: {}", response.getBody().getPreferredUsername());
return response.getBody().getPreferredUsername();
}
You can substitute this URL by the one provided by #dimitri-algazin to retrieve all the users information.
It is possible to use the Keycloak dependencies:
<!-- keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-client</artifactId>
<version>3.4.3.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.1.4.Final</version>
</dependency>
And use the classes to generate the token:
Keycloak keycloak = KeycloakBuilder
.builder()
.serverUrl(keycloakUrl)
.realm(keycloakRealm)
.username(user.getUsername())
.password(user.getPassword())
.clientId(keycloakClientId)
.resteasyClient(new ResteasyClientBuilder().connectionPoolSize(10).build())
.build();
return keycloak.tokenManager().getAccessToken();
The examples are extracted from here. We also uploaded the image to Docker Hub to facilitate the interaction with Keycloak. For this reason we started with option 2). Right now we are in the process to cover other IdMs and we went for option 1) in order to avoid including extra dependencies. Conclusion:
I would go for option 2 if you stick to Keycloak because classes include extra functionalities for Keycloak tool.
I would go for option 1 for further coverage and other OAuth 2.0 tools.
We had similar requirement, get user e-mail by user uuid.
Create service user, make sure user has role "realm-management"->"view-users" (might be query-users as well)
Process is simple: do login to keycloak with service user (keep password and/or user name encoded in the properties file), make request to keycloak with accessToken in authorisation header to
GET http://{yourdomainadress}/auth/admin/realms/{yourrealmname}/users/{userId}
A way to login to keycloak using REST API:
POST http://{yourdomainadress}/auth/realms/{yourrealmname}/protocol/openid-connect/token
Headers:
Content-Type: application/x-www-form-urlencoded
Body x-www-form-urlencoded:
client_id: your-client
username: user-you-are-using
password: password-for-user
grant_type: password
client_secret: 11112222-3333-4444-5555-666666666666 (client secret is required if client "Access Type"="confidential")
Shortly: make sure your service user has right role assigned to do operation,
do login, do query keycloak (check docs to get right query url and params, always challenging)
I'm new to Spring security. I have implemented spring security and have generated the JWT token. Now i need to get the user from the token and set it in the session so that the session for that user maintains until the token expires or logged out. On the other hand i need to access the API's from the controller but the spring security is not allowing to access the API's without the JWT Token. Is it possible to access the API's in my controller by setting the JWT Token globally or in the session for all the requests.
Here is what i tried till now ,
UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(loginRequest.getUserName(), loginRequest.getPassword());
Authentication authentication = this.authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
logger.info("jwt is:"+jwt);
logger.info("authentication:"+authentication.getName());
User user2 = new User();
user2.setUserFirstName(user.getFirstName());
user2.setUserLastName(user.getLastName());
request.getSession().setAttribute("loggedInUser",user2);
request.getSession().setMaxInactiveInterval(60);
request.getSession().setAttribute("menu", MenuUtils.buildMenu(user2));
return "home";
I'm doing this while signing in the user. I'm using Thymeleaf in the frontend.
Thanks for the help in advance !!
You need to add the jwt token to the request headers, when accessing the apis:
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + jwt);
This allows the api's to read the headers, and check the user and his permissions
You don't need to store the user on the session. The token itself has an expiry date, and it should be send from the browser to the server on every request.
What i found while using spring oauth framework is resource server making check_token?token=T_O_K_E_N request to authorisation server, and authorisation server is just returning CheckTokenEndPoint map with authorities something like below.
{
"exp": 1511471427,
"user_name": "idvelu",
"authorities": [
"FUNCTION_GET_USERS",
"FUNCTION_AUTHORITY_1",
"FUNCTION_AUTHORITY_2",
"FUNCTION_AUTHORITY_3",
"FUNCTION_AUTHORITY_4",
"FUNCTION_AUTHORITY_5",
"FUNCTION_AUTHORITY_6",
"FUNCTION_AUTHORITY_7",
],
"client_id": "c1",
"scope": [
"read",
"write"
]
}
Just visualise this with oauth service and resource service is running in two different machines/jvm.
I think now resource server has to authorise the request against configured valid authorities in ResourceServerConfiguration::configure(HttpSecurity) with the authorities from the authorisation server.
#Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous().disable().requestMatchers().antMatchers("/**").and().authorizeRequests()
.antMatchers(HttpMethod.GET, "/myproject/users").hasAnyAuthority("FUNCTION_GET_USERS")
.antMatchers(HttpMethod.POST, "/myproject/users").hasAnyAuthority("FUNCTION_POST_NEW_USER")
.anyRequest().denyAll()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
In this case authorisation server may return all the hundreds of user authorities to resource server. Instead why not authorisation server itself can take the few of the permission required for authorisation as query params check_token?token=T_O_K_E_N&authorities=FUNCTION_GET_USERS,FUNCTION_AUTHORITY_2,.. from the resource server and validate it against the user's functions through DB?
And finally my problem is; i have different services like java, node.js, NGINX... All these have to verify its authentication and authorization against one spring Authorisation server. Because of the above stated problem all my service has to implement the authorisation (resource server) part. Means comparing all the authorities of user against the API acess authorities. Java side this comparison is fine with spring resource server implementation. But all other non-java (resource) services needs authorisation/resourceServer implementation. Instead if my spring authorisation server accepts the authorities and validates then my problem is solved as single point of authorisation/comparison implementations. I just need to pass it as part of check_token.
How to implement this new check_token endpoint along with accepting the authorities?
The authorization server is responsible for authenticating the user/client (depending on the oauth type you use). When this is done, a token is given.
When a user/client presents themselves to the resource server, wanting to consume a service, they must provide the token. Now the resource server has a token and needs to validate that this token was generated by the authorization server. There are two options:
The token itself does not contain any information. The resource server calls the authorization server and asks if the token is valid. The authorization server will respond that the token is valid and gives some additional information (user/client, roles/scopes, etc).
The token does contain the necessary information (JWT tokes for example). This enables the resource server to extract the needed info without contacting the authorization server. In this case, the authorization server has signed the token and the resource server can validate the signature to be sure that it was the autorization server that has issued the token.
At the moment, you are using the first scenario. Every resource server you write, must verify tokens and extract additional info. How the verification is done depends on the authorization server.
Part2:
Your question is not clear to me. I presume you are using the OAuth2 authorization code scheme with non JWT tokens?
In that case, you have an authorization server that is only responsible for authentication, a resource server that is exposing some services and a client that consumes the resource server.
If i'm not mistaking, you have different resource servers (api's)?
You did not share your authorization server configurations, but normaly you use #EnableAuthorizationServer. This will create an endpoint /oauth/check_token. By default this endpoint is not accassible. You need to do something like this:
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception
{
oauthServer.checkTokenAccess("permitAll()"); // authenticated is better
}
You can use this endpoint to validate tokens.
All this is described in the oauth2 developer guide.
Part3:
On the authorization server, you can create an endpoint like this :
#RequestMapping("/user")
public Principal user(Principal principal) {
if(principal instanceof OAuth2Authentication) {
return (OAuth2Authentication) principal;
} else {
return principal;
}
}
You can modify it to your needs.