#EnableResourceServer: Mapping roles - spring-security

How do I map roles in my resource server?
I have created what I believe to be the most simple implementation of a resource server in Spring Boot:
Application.java
#SpringBootApplication
#RestController
#EnableResourceServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#RequestMapping(value = "/user")
public Principal user(Principal principal) {
return principal;
}
}
application.properties
security.oauth2.resource.user-info-uri=myUserInfoUri
security.oauth2.client.client-id=myId
security.oauth2.client.client-secret=mySecret
pom.xml
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
</dependencies>
The app works well. I make a call to my auth server (in this case KeyCloak) and retrieve a token. I then do a http get to http://localhost:8080/user/ with the token passed in as the Authorization header.
The HTTP response contains information from the token however the authorities property is set to ROLE_USER. This does not match the roles in the token.
HTTP response
...
"userAuthentication": {
"authorities": [
{
"authority": "ROLE_USER"
}
],
"details": {
"sub": "93f566bd-df68-4c68-b3c6-0173c476bc02",
"name": "Andrew Neeson",
"preferred_username": "andy",
"given_name": "Andrew",
"family_name": "Neeson",
...
Token (parsed)
...
"realm_access": {
"roles": [
"role_1",
"role_2"
]
},
"resource_access": {
"account": {
"roles": [
"manage-account",
"view-profile"
]
}
},
"name": "Andrew Neeson",
"preferred_username": "andy",
"given_name": "Andrew",
"family_name": "Neeson",
...
How do I extract the desired information from the token?

I've resolved by creating a token mapper in keycloak client.
In my case the mapper has these parameters:
name: authorities
mapper type: User Realm Role
Token Claim Name: authorities
Add to access token: on
Add to userinfo: on
I hope this can help!
Giampiero.

Related

Spring security - using WebClient access a resource that is protected via Oauth2 "Password" grant type

How can I access with WebClient a resource that is protected via the Oauth2 'Password' grant type?
Connecting with Oauth2 'client-credentials' works. In this case I need the password grant type.
I get this error:
401 Unauthorized from GET http://localhost:8086/test2 at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198) ~[spring-webflux-5.3.19.jar:5.3.19]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ 401 from GET http://localhost:8086/test2
I configured the auth server via Keycloack with Access Type 'public'. I checked accessing a token via Postman. You can find more details via this post.
The Websecurity config (working for grant-type client-credentials):
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("*").permitAll();
}
}
The webclient is created as a Bean. It works for the client-credentials grant type.
#Configuration
public class WebClientOAuth2Config {
#Bean("method2")
WebClient webClientGrantPassword( #Qualifier("authclientmgr2") OAuth2AuthorizedClientManager authorizedClientManager2) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientManager2);
oauth2Client2.setDefaultClientRegistrationId("businesspartners");
return WebClient.builder().apply(oauth2Client2.oauth2Configuration()).build();
}
#Bean("authclientmgr2")
public OAuth2AuthorizedClientManager authorizedClientManager2(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
The controller accessing the resource server:
#RestController
public class Test2Controller {
#Autowired
private #Qualifier("method2") WebClient webClient2;
#GetMapping("/test2")
public String test2() {
return webClient2.get().uri("http://localhost:8086/test2")
.attributes(clientRegistrationId("businesspartners"))
.retrieve().bodyToMono(String.class).block();
}
}
The application.yml config is:
server:
port: 8081
spring:
security:
oauth2:
client:
registration:
businesspartners:
client-id: myclient2
authorization-grant-type: password
client-name: johan
client-secret: password
provider:
businesspartners:
issuer-uri: http://localhost:28080/auth/realms/realm2
token-uri: http://localhost:28080/auth/realms/realm2/protocol/openid-connect/token
The maven dependencies include:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Not sure whether it's possible to do it using application.yml but here is how you can configure it in code
private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(
String clientRegistrationId, SecurityConfig config) {
var clientRegistration = ClientRegistration
.withRegistrationId(clientRegistrationId)
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.tokenUri(config.getTokenUri() + "/token")
.clientId(config.getClientId())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build();
var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);
var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
authRepository, authClientService);
var clientAuthProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
authClientManager.setAuthorizedClientProvider(clientAuthProvider);
authClientManager.setContextAttributesMapper(authorizeRequest -> Mono.just(
Map.of(
OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, config.getUsername(),
OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, config.getPassword()
)
));
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultClientRegistrationId(clientRegistrationId);
return oauth;
}
and then use in in WebClient
WebClient webClient = WebClient.builder()
.filter(oauth("businesspartners", securityConfig))
.build();
where SecurityConfig is defined like
#lombok.Value
#lombok.Builder
static class SecurityConfig {
String tokenUri;
String clientId;
String username;
String password;
}
Here is a full test using WireMock
#Slf4j
#SpringBootTest(webEnvironment = NONE)
#AutoConfigureWireMock(port = 0) // random port
class WebClientTest {
#Value("${wiremock.server.port}")
private int wireMockPort;
#Test
void authClientTest() {
String authResponse = """
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "token",
"refresh_token": "token"
}""";
stubFor(post(urlPathMatching("/token"))
.withRequestBody(
containing("client_id=myclient2")
.and(containing("grant_type=password"))
.and(containing("password=password"))
.and(containing("username=username"))
)
.withHeader(HttpHeaders.CONTENT_TYPE, containing(MediaType.APPLICATION_FORM_URLENCODED.toString()))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody(authResponse)
)
);
stubFor(get(urlPathMatching("/test"))
.willReturn(aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.withStatus(200)
.withBody("{}")
)
);
SecurityConfig config = SecurityConfig.builder()
.tokenUri("http://localhost:" + wireMockPort)
.clientId("myclient2")
.username("username")
.password("password")
.build();
WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:" + wireMockPort)
.filter(oauth("test", config))
.build();
Mono<String> request = webClient.get()
.uri("/test")
.retrieve()
.bodyToMono(String.class);
StepVerifier.create(request)
.assertNext(res -> log.info("response: {}", res))
.verifyComplete();
}
private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(
String clientRegistrationId, SecurityConfig config) {
var clientRegistration = ClientRegistration
.withRegistrationId(clientRegistrationId)
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.tokenUri(config.getTokenUri() + "/token")
.clientId(config.getClientId())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build();
var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);
var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
authRepository, authClientService);
var clientAuthProvider = new PasswordReactiveOAuth2AuthorizedClientProvider();
authClientManager.setAuthorizedClientProvider(clientAuthProvider);
authClientManager.setContextAttributesMapper(authorizeRequest -> Mono.just(
Map.of(
OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, config.getUsername(),
OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, config.getPassword()
)
));
var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
oauth.setDefaultClientRegistrationId(clientRegistrationId);
return oauth;
}
#lombok.Value
#lombok.Builder
static class SecurityConfig {
String tokenUri;
String clientId;
String username;
String password;
}
}

Pass user id as a header in downstream request. Spring Cloud Gateway + Oauth2 Resource Server

I want to implement security on my Spring cloud gateway server by making it an oAuth2 resource server. The requests are getting authenticated against my spring security authorization server. For some requests I want to pass the userId of the authenticated user as a request header to my downstream services.
Here's my route:
.route("create-deck", routeSpec -> routeSpec
.path("/decks")
.and()
.method(HttpMethod.POST)
.filters(
fs -> fs.addRequestHeader("userId", "ADD_USER_ID_HEADER_HERE"))
.uri("http://localhost:8082/"))
I have configured my authorization server to return userId as principal name:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = ur.findByUsername(username);
if (user == null) {
log.error("User: {} not found", username);
throw new UsernameNotFoundException("User: " + username + " not found");
}
return new org.springframework.security.core.userdetails.User(String.valueOf(user.getId()), user.getPassword(), authorities);
}
Note: I have a custom domain object User that I store in my database.
The route is getting successfully authenticated and the token contains userId as subject. I just need help on how to get the subject and pass that to my downstream request as a request header.
I am using the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
You could create custom filter that will be applied to all requests. Here is an example when user is a part of the jwt token.
public class UserHeaderFilter implements GlobalFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return ReactiveSecurityContextHolder.getContext()
.filter(c -> c.getAuthentication() != null)
.flatMap(c -> {
JwtAuthenticationToken jwt = (JwtAuthenticationToken) c.getAuthentication();
String user = (String) jwt.getTokenAttributes().get("user");
if (Strings.isNullOrEmpty(user)) {
return Mono.error(
new AccessDeniedException("Invalid token. User is not present in token.")
);
}
ServerHttpRequest request = exchange.getRequest().mutate()
.header("x-user", user).build();
return chain.filter(exchange.mutate().request(request).build());
})
.switchIfEmpty(chain.filter(exchange));
}
}

swagger UI for Spring Boot API : How to add "audience" in request body for authorising "client credentials" flow

I have generated swagger UI documentation from my spring boot API, the API is secured using oauth2 client credentials grant from auth0.
The problem is that:
In the swagger configuration, I am unable to set the "audience" request body parameter while authorisation.
Thus, swagger ui is not authenticating the API.
I am following this documentation:
https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
pom.xml:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
SwaggerConfig.Java:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
String token_endpoint = "xxxx";
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("xxxx.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo())
.useDefaultResponseMessages(false)
.securitySchemes(Arrays.asList(securityScheme()))
.securityContexts(Arrays.asList(securityContext()));
}
private ApiInfo apiInfo() {
return new ApiInfo(
"xxxx API",
"Some description of API.",
"xxxx",
"Terms of service",
new Contact("xx", "xxxx", "xxxx"),
"License of API", "xxxx", Collections.emptyList());
}
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
#Bean
public SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()
.appName("xxxx")
.clientId("")
.clientSecret("")
.build();
}
private SecurityScheme securityScheme() {
GrantType grantType = new ClientCredentialsGrant(token_endpoint);
SecurityScheme oauth = new OAuthBuilder().name("spring_oauth")
.grantTypes(Arrays.asList(grantType))
.build();
return oauth;
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.forPaths(PathSelectors.any())
.build();
}
}
The response is as 403 Forbidden and this is because, I am not able to provide "audience" in the request body during authorization:
"error_description": "Non-global clients are not allowed access to APIv1"
authorization in swagger ui for "application/client credentials grant type

How to custom Principle object with Spring Cloud Security?

I finally got my oauth2 server running.
From command line, if I run
curl -s -u acme:acmesecret -d grant_type=password -d username=myusername -d password=mypassword -H Accept:application/json http://localhost:9999/oauth/token
I got result below,
{
"access_token":"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0Mzk1NDU3ODAsInVzZXJfbmFtZSI6IisxIDQwODUxODIxMTUiLCJhdXRob3JpdGllcyI6WyJVU0VSIiwiQURNSU4iXSwianRpIjoiYmFkMDgyMjctNDExNC00OTZkLWE1NDMtYzBhMjc3YTBhZDkzIiwiY2xpZW50X2lkIjoiYWNtZSIsInNjb3BlIjpbIndlYnNob3AiXX0.CM_0gBHVyecOMmpc2cnKTus48PNv8gfHDyzVOVa5TBDxv4QlnDO93otmUs86IQqPaqaI133tT1NPU0pt2dbV5lrY3FOlPFXB0zZw5ptIXCtpaQLgl3e9hkB1aSfv3YxbHiOV8n3FcvNdz9Ihi9XEQdzqT8YfK7mCeMOjdb1i6Ve9axwjJI9ZHxXzDMcJsnYBcQCKG52G3-rWzgzlaQkPZY6mO7q0eO0jgVWthLfSBumHlDt9QXaBkETH3CRHxSuJqlo4J3TZxP4-1vPLkgh8Ku2rY5A9rT-xOKG8_5s2CJduCZt0qQrXZhz7sk0m2IdxDDwXumPv6zyHyD2J3sjHUA",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX25hbWUiOiIrMSA0MDg1MTgyMTE1Iiwic2NvcGUiOlsid2Vic2hvcCJdLCJhdGkiOiJiYWQwODIyNy00MTE0LTQ5NmQtYTU0My1jMGEyNzdhMGFkOTMiLCJleHAiOjE0NDIwOTQ1ODAsImF1dGhvcml0aWVzIjpbIlVTRVIiLCJBRE1JTiJdLCJqdGkiOiJjYWNkOWEzOC1mOWE5LTQ4NjAtOWZmMi05NWMzMzU4MmY0NDAiLCJjbGllbnRfaWQiOiJhY21lIn0.DhaqIEdYWR2VPkgh72bQ17ZLqcVVfdYtT8DdKibjIcZUTNNjN_atdyKYKNEtdSyEES-ArHL0jCVXUg3EKiut_qtvn8oaLYEAxCNfztHyo_b-RZIxOgr71m82n66vSwRzxQnoKcGltxpZs-PK5p-gmbaEWK4EO63AkJpgN_IrIGV4eVQmidanz53rvq-CBiq-1FFb64OilesUxkSPOVkbb-q-mUmd8EG4khdbf44LD9VhyZwt8lOOi8NnksnnGhogiynU9p7tirAv6w_g8IO7uy06fWaLyn6rAgPga3CYgo9ggFIICWKn-QFipkHgiehq6y_1-xTGlgHnRKXcnPIZcg",
"expires_in": 34996,
"scope": "webshop",
"jti": "bad08227-4114-496d-a543-c0a277a0ad93"
}
With the token returned, I can get user information with a curl command. You can a lot of user information in the response.
curl http://localhost:9999/user -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0Mzk1NDU3ODAsInVzZXJfbmFtZSI6IisxIDQwODUxODIxMTUiLCJhdXRob3JpdGllcyI6WyJVU0VSIiwiQURNSU4iXSwianRpIjoiYmFkMDgyMjctNDExNC00OTZkLWE1NDMtYzBhMjc3YTBhZDkzIiwiY2xpZW50X2lkIjoiYWNtZSIsInNjb3BlIjpbIndlYnNob3AiXX0.CM_0gBHVyecOMmpc2cnKTus48PNv8gfHDyzVOVa5TBDxv4QlnDO93otmUs86IQqPaqaI133tT1NPU0pt2dbV5lrY3FOlPFXB0zZw5ptIXCtpaQLgl3e9hkB1aSfv3YxbHiOV8n3FcvNdz9Ihi9XEQdzqT8YfK7mCeMOjdb1i6Ve9axwjJI9ZHxXzDMcJsnYBcQCKG52G3-rWzgzlaQkPZY6mO7q0eO0jgVWthLfSBumHlDt9QXaBkETH3CRHxSuJqlo4J3TZxP4-1vPLkgh8Ku2rY5A9rT-xOKG8_5s2CJduCZt0qQrXZhz7sk0m2IdxDDwXumPv6zyHyD2J3sjHUA"
{
"details": {
"remoteAddress": "127.0.0.1",
"sessionId": null,
"tokenValue": "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0Mzk1NDU3ODAsInVzZXJfbmFtZSI6IisxIDQwODUxODIxMTUiLCJhdXRob3JpdGllcyI6WyJVU0VSIiwiQURNSU4iXSwianRpIjoiYmFkMDgyMjctNDExNC00OTZkLWE1NDMtYzBhMjc3YTBhZDkzIiwiY2xpZW50X2lkIjoiYWNtZSIsInNjb3BlIjpbIndlYnNob3AiXX0.CM_0gBHVyecOMmpc2cnKTus48PNv8gfHDyzVOVa5TBDxv4QlnDO93otmUs86IQqPaqaI133tT1NPU0pt2dbV5lrY3FOlPFXB0zZw5ptIXCtpaQLgl3e9hkB1aSfv3YxbHiOV8n3FcvNdz9Ihi9XEQdzqT8YfK7mCeMOjdb1i6Ve9axwjJI9ZHxXzDMcJsnYBcQCKG52G3-rWzgzlaQkPZY6mO7q0eO0jgVWthLfSBumHlDt9QXaBkETH3CRHxSuJqlo4J3TZxP4-1vPLkgh8Ku2rY5A9rT-xOKG8_5s2CJduCZt0qQrXZhz7sk0m2IdxDDwXumPv6zyHyD2J3sjHUA",
"tokenType": "Bearer",
"decodedDetails": null
},
"authorities": [
{
"authority": "USER"
},
{
"authority": "ADMIN"
}
],
"authenticated": true,
"userAuthentication": {
"details": {
"grant_type": "password",
"username": "myusername"
},
"authorities": [
{
"authority": "USER"
},
{
"authority": "ADMIN"
}
],
"authenticated": true,
"principal": {
"id": "usr000d11b4c86-13ba-11e5-b905-56847afe9799",
"json": null,
"version": 0,
"created": 1434412879774,
"updated": 1438877901186,
"info": {
"nickName": "Kevin",
"country": "China",
"zipcode": null,
"state": null,
"city": "",
"occupation": "",
"gender": null,
"imgPath": "https://ddbs0erhouflt.cloudfront.net/mcf000ecd36bcb-f33e-4d50-9102-7a8706b45eb8",
"about": "",
"dueDate": 1447312895201,
"birthday": 0
},
"privateInfo": {
"email": "zyj#yahoo.com",
"phone": "myusername",
"password": "f45206ce4247b5d9af350d4600adc85c",
"tempPassword": null,
"tokens": null
},
"settings": null,
"type": "Super",
"status": "Active",
"enabled": true,
"username": "myusername",
"password": "f45206ce4247b5d9af350d4600adc85c",
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"authorities": [
{
"authority": "USER"
},
{
"authority": "ADMIN"
}
]
},
"credentials": null,
"name": "myusername"
},
"credentials": "",
"oauth2Request": {
"clientId": "acme",
"scope": [
"webshop"
],
"requestParameters": {
"grant_type": "password",
"username": "myusername"
},
"resourceIds": [],
"authorities": [],
"approved": true,
"refresh": false,
"redirectUri": null,
"responseTypes": [],
"extensions": {},
"grantType": "password",
"refreshTokenRequest": null
},
"principal": {
"id": "usr000d11b4c86-13ba-11e5-b905-56847afe9799",
"json": null,
"version": 0,
"created": 1434412879774,
"updated": 1438877901186,
"info": {
"nickName": "Kevin",
"country": "China",
"zipcode": null,
"state": null,
"city": "",
"occupation": "",
"gender": null,
"imgPath": "https://ddbs0erhouflt.cloudfront.net/mcf000ecd36bcb-f33e-4d50-9102-7a8706b45eb8",
"about": "",
"dueDate": 1447312895201,
"birthday": 0
},
"privateInfo": {
"email": "zyj#yahoo.com",
"phone": "myusername",
"password": "f45206ce4247b5d9af350d4600adc85c",
"tempPassword": null,
"tokens": null
},
"settings": null,
"type": "Super",
"status": "Active",
"enabled": true,
"username": "myusername",
"password": "f45206ce4247b5d9af350d4600adc85c",
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"authorities": [
{
"authority": "USER"
},
{
"authority": "ADMIN"
}
]
},
"clientOnly": false,
"name": "myusername"
}
I have a Spring boot micro service client. It uses spring-cloud-security. One of the web service was below,
#RequestMapping(value="getsth", method=RequestMethod.GET)
public SomeObject getsth(Principal principal) {
....
}
When method getsth is called, I can see an object of OAuth2Authentication was passed in. However, user information like user id, user phone number are missing.
My question: how can I get all the user information? Is there any way to custom the principal object?
Thanks,
I was struggling with the same issue. I got it to work however.
I'm using JWT so might be slightly different to what you're doing, but the concept is the same.
First of all, I created a custom TokenServices to get the extra information out of the user and added it to the authentication object:
public class TafTokenServices extends DefaultTokenServices {
#Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
final TafUserDetails tafUserDetails = (TafUserDetails)authentication.getPrincipal();
final Map<String, Object> tafInfo = new HashMap<>();
tafInfo.put("EMAIL", tafUserDetails.getEmailAddress());
authentication.setDetails(tafInfo);
return super.createAccessToken(authentication);
}
}
Then configure your auth server to use it:
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).accessTokenConverter(jwtAccessTokenConverter())
.tokenServices(tafTokenServices());
}
#Bean
public AuthorizationServerTokenServices tafTokenServices() {
final TafTokenServices tafTokenServices = new TafTokenServices();
final JwtTokenStore jwtTokenStore = new JwtTokenStore(this.jwtAccessTokenConverter());
tafTokenServices.setTokenStore(jwtTokenStore);
tafTokenServices.setTokenEnhancer(this.jwtAccessTokenConverter());
return tafTokenServices;
}
Also in the auth server you need to then transfer the data out of the authentication object into the token with your own AccessTokenCoverter object:
public class TafJwtAccessTokenConverter extends JwtAccessTokenConverter {
private static final String EMAIL_KEY = "EMAIL";
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
final Map<String, Object> authDetails = (Map<String, Object>)authentication.getDetails();
((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(authDetails);
return super.enhance(accessToken, authentication);
}
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
final OAuth2Authentication authentication = super.extractAuthentication(map);
final Map<String, String> details = new HashMap<>();
details.put(EMAIL_KEY, (String)map.get(EMAIL_KEY));
authentication.setDetails(details);
return authentication;
}
}
NOTE: the enhance() method is called during creation of the token, so you need this in the auth server.
NOTE: extractAuthentication() is called in the downstream services during authentication, so this implementation needs to exist there as well. You need to configure your resource server to use this AccessTokenConverter.
That will get the info into the token to pass downstream. Note I didn't want to keep the data in the custom user object because I don't want to have my other services depend on that object.
The next step is to get the stuff out of the token and use it in your resource server. You do this by overriding extractAuthentication() to get the details from the map and put them into the authentication object. They will now be available in your application by doing something like this:
private String getEmail() {
final OAuth2Authentication auth = this.getAuthentication();
final OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)auth.getDetails();
final Map<String, Object> map = (Map)details.getDecodedDetails();
return "Your email address is " + map.get("EMAIL");
}
private OAuth2Authentication getAuthentication() {
return (OAuth2Authentication)SecurityContextHolder.getContext().getAuthentication();
}
I'm not sure this is right way to go, it's a bit fiddly. I have an issue open here where there is a discussion about it:
https://github.com/spring-cloud/spring-cloud-security/issues/85#issuecomment-165498497
to customize it you have to provide your own UserDetailsService class:
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
where User is your entity with all fields you require:
public class User implements Serializable, UserDetails {...
then you have to configure spring security to actually use your custom user details service. Something similar to:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalAuthentication
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService myUserDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.parentAuthenticationManager(authenticationManager)
.userDetailsService(myUserDetailsService);
}

APIs not getting detected by swagger on a Spring ROO project

I have tried a lot of things but APIs are not getting detected by swagger for some reason. Do i have to specify a package for swagger to scan? or some url include patterns?
My Swager Config :
#Configuration
#EnableSwagger
#EnableWebMvc
public class SwaggerConfiguration {
private final Logger log = LoggerFactory
.getLogger(SwaggerConfiguration.class);
/**
* Swagger Spring MVC configuration.
*/
#Bean
public SwaggerSpringMvcPlugin swaggerSpringMvcPlugin(
SpringSwaggerConfig springSwaggerConfig) {
log.debug("Starting Swagger");
StopWatch watch = new StopWatch();
watch.start();
SwaggerSpringMvcPlugin swaggerSpringMvcPlugin = new SwaggerSpringMvcPlugin(
springSwaggerConfig).apiInfo(apiInfo())
.genericModelSubstitutes(ResponseEntity.class);
swaggerSpringMvcPlugin.build();
watch.stop();
log.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());
return swaggerSpringMvcPlugin;
}
/**
* API Info as it appears on the swagger-ui page.
*/
private ApiInfo apiInfo() {
return new ApiInfo("Title", "Description", "terms of service",
"contact", "license", "licenseUrl");
}
}
Sample Controller
#RequestMapping("/settings")
#Controller
#Api(value = "/settings", description = "Endpoint for settings management")
public class SettingsController {
#ApiOperation(value = "API Operation")
#RequestMapping(value = "/changepassword", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> changePassword(#RequestParam Map<String, String> userProperties,
Model model, HttpServletRequest httpServletRequest, Locale locale) {
Map<String, Object> responseMap = new HashMap<String, Object>();
return responseMap;
}
}
I get an empty response
{
"apiVersion": "1.0",
"swaggerVersion": "1.2",
"apis": [ ],
"authorizations": [ ],
"info":
{
"title": "Title",
"description": "Description",
"termsOfServiceUrl": "terms of service",
"contact": "contact",
"license": "license",
"licenseUrl": "licenseUrl"
}
}
I am using swagger-springmvc version 1.0.2 and spring version 4.1.6.RELEASE
Follow the instructions in the following URL :
http://naddame.blogspot.in/2014/12/spring-roo-mvc-integration-for-swagger.html

Resources