How to generate authorization information per API endpoint in Swagger Doc based on Spring-Security annotations - spring-boot

How to reuse Spring-Security annotations like #PreAuthorize or #Secured in Springfox-swagger to avoid maintaining the authorization information twice?
Currently the only options I found to populate security information in the swagger.json is duplicating the role information with swagger-core annotations like
#Secured(ROLE_USER)
#ApiOperation(value = "Get the model", authorizations = {
#Authorization(value = ROLE_USER) })
#GetMapping(value = "model/**")
or specifying globally in the SwaggerConfig::
private SecurityContext apiSecurityContext() {
AuthorizationScope[] authorizationScopes = new AuthorizationScope[] { new AuthorizationScope(Roles.ROLE_USER, "access limited"),
new AuthorizationScope(Roles.ROLE_ADMIN, "access Everything") };
return SecurityContext
.builder()
.securityReferences(newArrayList(new SecurityReference("basic", authorizationScopes)))
.forPaths(PathSelectors.regex("/api.*")) // (PathSelectors.any())
.build();
}
Both ways seems not to be a good solution since I need to maintain the information multiple times or are not precise.
So what do I need to configure in order to let swagger depict the authorization roles a requester needs to access a certain rest resource from the spring-security annotations?

Related

Validate the api with user defined roles in spring boot oauth resource server

I am currently working on resource server implemented by the spring boot o auth 2.0. Spring boot version would be 3.0.0. Need to authrorize the api with user-authorities. I tried below samples.
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/opaque-token.html. But did n't get the user-authorities attribute. Can anyone sugget me the approach to to validate api with user roles in #PreAuthorize anotations.
Sample token format :
{
"sub": "admin#support.com",
"aud": "client",
"user-authorities": [
"READ_PRIVILEGE",
"WRITE_PRIVILEGE"
],
"azp": "client",
"iss": "http://localhost:8080",
"exp": 1676210945,
"iat": 1676209145
}
Two options to override default authorities mapping in case of token introspection (read user-authorities claim in your case instead of scope).
override the all OpaqueTokenIntrospector, as explained in the doc you linked
override just the OpaqueTokenAuthenticationConverter to which the default introspector delegates the conversion from successful introspection result to an Authentication instance
The second option is less intrusive as you don't take responsibility of how introspection is made (which REST client is used, how client authentication is performed, query built, response parsed, exceptions handled, etc.).
Sample usage:
#Bean
OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter() {
return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> {
// double check the claim name to extract authorities from and if a prefix should be added:
// in the sample provided in your question, it is "user-authorities" claim
// but in the sample linked in your comment, it is "scope" claim and "SCOPE_" prefix should be added for "hasAuthority("SCOPE_openid")" to be matched
#SuppressWarnings("unchecked")
final var rolesClaim = Optional.ofNullable((List<String>) authenticatedPrincipal.getAttribute("user-authorities")).orElse(List.of());
return new BearerTokenAuthentication(
authenticatedPrincipal,
new OAuth2AccessToken(
TokenType.BEARER,
introspectedToken,
authenticatedPrincipal.getAttribute(IdTokenClaimNames.IAT),
authenticatedPrincipal.getAttribute(IdTokenClaimNames.EXP)),
// You should insert a ".map()" step to insert a prefix if access-control rules expect one (that is not visible in the access token)
rolesClaim.stream().map(SimpleGrantedAuthority::new).toList());
};
}
#Bean
SecurityFilterChain securityFilterChain(
HttpSecurity http,
OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter)
throws Exception {
http.oauth2ResourceServer().opaqueToken().authenticationConverter(introspectionAuthenticationConverter);
...
return http.build();
}
Complete sample there with one of "my" starters which make things much simpler (check security conf and properties file against what you have in yours).

Invalid JWToken: kid is a required JOSE Header

I am trying to implement an Oauth2 Authorization Server with SpringBoot using this guide as a reference.
My keystore has a single key. I have successfully managed to create a JWToken (I can check it at jwt.io).
I have also a test Resource Server. When I try to access any endpoint I receive the following message:
{
"error": "invalid_token",
"error_description": "Invalid JWT/JWS: kid is a required JOSE Header"
}
The token really does not have a kid header but I can not figure out how to add it. I can only add data to its payload, using a TokenEnchancer. It also seems that I am not the first one with this issue.
Is there any way to add this header or, at least, ignore it at the resource server?
I've been working on an article that might help you out here:
https://www.baeldung.com/spring-security-oauth2-jws-jwk
So, to configure a Spring Security OAuth Authorization Server to add a JWT kid header, you can follow the steps of section 4.9:
create a new class extending the JwtAccessTokenConverter
In the constructor:
configure the parent class using the same approach you've been using
obtain a Signer object using the signing key you're using
override the encode method. The implementation will be the same as the parent one, with the only difference that you’ll also pass the custom headers when creating the String token
public class JwtCustomHeadersAccessTokenConverter extends JwtAccessTokenConverter {
private JsonParser objectMapper = JsonParserFactory.create();
final RsaSigner signer;
public JwtCustomHeadersAccessTokenConverter(KeyPair keyPair) {
super();
super.setKeyPair(keyPair);
this.signer = new RsaSigner((RSAPrivateKey) keyPair.getPrivate());
}
#Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication));
} catch (Exception ex) {
throw new IllegalStateException("Cannot convert access token to JSON", ex);
}
Map<String, String> customHeaders = Collections.singletonMap("kid", "my_kid");
String token = JwtHelper.encode(content, this.signer, this.customHeaders)
.getEncoded();
return token;
}
}
Then, of course, create a bean using this converter:
#Bean
public JwtAccessTokenConverter accessTokenConverter(KeyPair keyPair) {
return new JwtCustomHeadersAccessTokenConverter(keyPair);
}
Here I used a KeyPair instance to obtain the signing key and configure the converter (based on the example of the article), but you might adapt that to your configuration.
In the article I also explain the relevant endpoints provided by the Spring Security OAuth Authentication Server.
Also, regarding #Ortomala Lokni's comment, I wouldn't expect Spring Security OAuth to add any new features at this point. As an alternative, you probably can wait to have a look at Spring Security's Authorization Server features, planned to be released in 5.3.0
I managed to solve it by changing the parameter used to identify the URL where the clients will retrieve the pubkey.
On application.properties, instead of:
security.oauth2.resource.jwk.key-set-uri=http://{auth_server}/.well-known/jwks.json
I used:
security.oauth2.resource.jwt.key-uri=http://{auth_server}/oauth/token_key
If I understood correctly, the key-set-uri config points to an endpoint that presents a set of keys and there is the need for a kid. On the other side key-uri config points to an endpoint with a single key.

How to validate facebook authorization token and sign up user using Spring(java)

I am developing an app whose frontend is written using React.js and the backend REST API is written using the Spring framework. I wanted to add social logins to my website, so after days of googling and research, I understood that OAuth2 is the solution. I came to know that the frontend should handle getting the authorization token from the Resource Server(Facebook here) and my backend(java) should validate that token and connect with Facebook to get an access token. Then that access token should be stored in my database along with the user details(e.g email).
Here is my requirement, once the user clicks on the "Continue with Facebook" button, my app should create there account in my own database using details - email and Name(the signup feature). And later whenever they click on this button again, they will be logged in not sign up. The way other websites handle it.
As of now, I have the button working in my app, which brings me the authorization token from Facebook.
Can someone please guide me the path I should follow here.
Also, any special attention to some error handling I should follow.
Here's the general approach using Spring Boot as a REST API backed by Spring Data JPA and Spring Security that works for iOS and ember.js together. There's probably libraries and what not that you can use but I'm just going to outline the fundamental flow.
Your user object needs a one to one mapping to a facebook account. Best practice would involve encrypting the authToken before storing in the DB
#Entity
class FacebookAccount {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
Long id
String facebookUserId
String authToken
#OneToOne
#JoinColumn(name="user_id")
User user
}
#Entity
class User{
...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
FacebookAccount facebookAccount
}
Use the facebook Javascript SDK to get a User Access Token and the User's Facebook User ID. You'll get a response back from facebook in your react app that looks like this in the successful case:
{
status: 'connected',
authResponse: {
accessToken: '...',
expiresIn:'...',
reauthorize_required_in:'...'
signedRequest:'...',
userID:'...'
}
}
Hit some login endpoint with the info received in step 2 like /login/facebook. I cannot predict how your app is structured. In my app, this code is handled by my Authentication Filter that implements GenericFilterBean. I pass a header X-Auth-Facebook with the token.
Verify the token. I'm doing this in a class that implements AuthenticationProvider within the Authentication authenticate(Authentication authentication) throws AuthenticationException method. This class will need your App's Access Token accessToken and the user's Token userAccessToken:
URIBuilder builder = URIBuilder.fromUri(String.format("%s/debug_token", "https://graph.facebook.com"))
builder.queryParam("access_token", accessToken)
builder.queryParam("input_token", userAccessToken)
URI uri = builder.build()
RestTemplate restTemplate = new RestTemplate()
JsonNode resp = null
try {
resp = restTemplate.getForObject(uri, JsonNode.class)
} catch (HttpClientErrorException e) {
throw new AuthenticationServiceException("Error requesting facebook debug_token", e)
}
Boolean isValid = resp.path("data").findValue("is_valid").asBoolean()
if (!isValid)
throw new BadCredentialsException("Token not valid")
String fbookUserId = resp.path("data").findValue("user_id").textValue()
if (!fbookUserId)
throw new AuthenticationServiceException("Unable to read user_id from facebook debug_token response")
// spring data repository that finds the FacebookAccount by facebook user id
FacebookAccount fbookAcct = facebookAccountRepository.findByFacebookUserId(fbookUserId)
if(!fbookAcct){
// create your user here
// save the facebook account as well
} else{
// update the existing users token
fbookAcct.authToken = userAccessToken
facebookAccountRepository.save(fbookAcct)
}
// finish the necessary steps in creating a valid Authentication
I, personally, then create a token that my client's use when accessing my API (rather than have them continue to pass the facebook token with all requests).
I also need more user provided information to create the user (a chosen username, agreeing to terms and conditions, etc). So my actual implementation throws an EntityNotFoundException instead of creating the user, which my clients then use to pop up a registration form that provides only the fields I cannot get from facebook. On submit of this from the client, I hit my /signup/facebook endpoint with the facebook token and what's needed to create my user. I fetch the profile from facebook and create the user (automatically logging them in the process).
Edit: If you want to use Spring 0Auth, you could follow the example for creating a Spring 2 Oauth Rest Template
#Bean
public OAuth2ProtectedResourceDetails facebook() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("facebook");
details.setClientId("233668646673605");
details.setClientSecret("33b17e044ee6a4fa383f46ec6e28ea1d");
details.setAccessTokenUri("https://graph.facebook.com/oauth/access_token");
details.setUserAuthorizationUri("https://www.facebook.com/dialog/oauth");
details.setTokenName("oauth_token");
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate facebookRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.valueOf("text/javascript")));
template.setMessageConverters(Arrays.<HttpMessageConverter<?>> asList(converter));
return template;
}
and then in use:
public String photos(Model model) throws Exception {
ObjectNode result = facebookRestTemplate
.getForObject("https://graph.facebook.com/me/friends", ObjectNode.class);
ArrayNode data = (ArrayNode) result.get("data");
ArrayList<String> friends = new ArrayList<String>();
for (JsonNode dataNode : data) {
friends.add(dataNode.get("name").asText());
}
model.addAttribute("friends", friends);
return "facebook";
}
I took the above request for friends from the project. it shouldn't be hard to tailor the above code I showed with debug_token to use the Spring OAuth rest template. Hope this helps :)

SpringBoot Security ldap auth against multiple separate AD domains

Is it possible to have SpringBoot use multiple AD authentication providers against different domains?
So, like I have two separate AD controllers
URL: ldap://ad.region1.company.com
baseDN: dc=region1,dc=company,dc=com
and
URL: ldap://ad.region2.company.com
baseDN: dc=region2,dc=company,dc=com
And some code like:
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapdomain, ldapurl);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Where ldapdomain and ldapurl are set to the "region1" values. I also want to be able to authenticate "region2" users. Is there some way to provide both endpoints and have it try both? Or a way to provide a hint in the login on which one to use?
Defile like below mention. Space should be mandatory between two urls.
String ldapdomain = "ldap://ad.region1.company.com ldap://ad.region2.company.com"

How to test Rest API call with swagger if needs authentication

I have springfox-swagger2 (version 2.6.1) and springfox-swagger-ui (version 2.6.1) in spring application.
How to I can configure authorization token for calls which needs authorized for continue (how to set X-AUTH-TOKEN for swagger).
Thanks!
Define the following API key as the security scheme. In your cause a header called X-AUTH-TOKEN. We refer to this scheme using the key mykey.
private ApiKey apiKey() {
return new ApiKey("mykey", "X-AUTH-TOKEN", "header");
}
Setup the security context. This just means you're setting up what the authorization scope is for a given path in your API. For e.g. for /anyPath/customers we may require a scope of accessEverything.
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("/anyPath.*"))
.build();
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope
= new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return newArrayList(
new SecurityReference("myKey", authorizationScopes));
}
Then in your docket associate the newly created security context and security schemes.
new Docket(...)
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
Now to enable swagger UI you need to supply the following bean configuration
#Bean
SecurityConfiguration security() {
return new SecurityConfiguration(
"test-app-client-id",
"test-app-client-secret",
"test-app-realm",
"test-app",
"YOUR_API_AUTH_TOKEN",
ApiKeyVehicle.HEADER,
"X-AUTH-TOKEN",
"," /*scope separator*/);
}
This tells the swagger-ui that you're going to use the api key and provide an api auth token at build time (perhaps using an encrypted configuration property.
NOTE: The swagger-ui is limited in its configurability. It serves the 80% of uses cases. Additional customization might mean you won't be able to use the bundled swagger-ui.

Resources