I have been trying to add authorization in requests that I try from swagger-ui, but in the request, the authorization header is always coming as null.
This is what I have done.
private ApiKey apiKey() {
return new ApiKey("apiKey", "Authorization",
"header"); //`apiKey` is the name of the APIKey, `Authorization` is the key in the request header
}
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select().apis(RequestHandlerSelectors.basePackage("com.example.app"))
.paths(PathSelectors.any()).build().apiInfo(apiInfo()).securitySchemes(Arrays.asList(apiKey()));
}
Can anyone please give some pointers? Thanks.
You can try this SwaggerConfig
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.any())
.build().securitySchemes(Lists.newArrayList(apiKey()));
}
private ApiKey apiKey() {
return new ApiKey("AUTHORIZATION", "api_key", "header");
}
}
Related
I run the following configuration to enable basic authentication for my (non-spring boot) project
#Configuration
#EnableOpenApi
#EnableWebMvc
public class SpringFoxConfig implements WebMvcConfigurer {
#Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(PublicAPI.class))
.paths(PathSelectors.any())
.build()
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(securityScheme())));
}
private SecurityScheme securityScheme() {
return new HttpAuthenticationBuilder()
.name("basic")
.scheme("basic")
.build();
}
private SecurityContext securityContext() {
return SecurityContext
.builder()
.securityReferences(securityReferences())
.operationSelector(operationContext -> true)
.build();
}
private List<SecurityReference> securityReferences() {
return singletonList(new SecurityReference("Authorization", new AuthorizationScope[] {new AuthorizationScope("global", "global")}));
}
}
this allows me to authorize my requests
but when testing the call, the authorization header isn't built nor sent with the request:
curl -X GET "https://localhost:8443/foo/rest/ws/info/get-master/code/awd" -H "accept: application/json"
Security reference name was different from the one defined in the securityScheme
private SecurityScheme securityScheme() {
return new HttpAuthenticationBuilder()
.name("basic")
.scheme("basic")
.build();
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any())
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = new AuthorizationScope("global", "accessEverything");
return singletonList(new SecurityReference("basic", authorizationScopes));
}
I am using spring feign client for making http requests.
Fiegn configuration class
DefaultConfig.class
public class DefaultConfig {
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder();
}
#Bean
public Encoder feignEncoder() {
return new JacksonEncoder();
}
#Bean
public RequestInterceptor requestInterceptor() {
return template -> {
template.header(Authorization, apiKey);
};
}
}
My client interface:
#FeignClient(name = "default", url = "${base-url}",
configuration = DefaultConfig.class)
public interface {
#PostMapping(value = "/users/")
Response createUser(#RequestBody Map<String, ?> requestBody);
#GetMapping(value = "/users/{id}")
Response getUserDetails(#PathVariable String id);
}
The thing is now I need to use different authorization key for GET and POST request. In the configuration class I need to differentiate the request by url called. How can we get the url the requester interceptor or any other way we can achieve this. We can create separate interceptor for this but I try to use the same interceptor for both cases.
Everything you need seems to be in the template object.
#Bean
public RequestInterceptor requestInterceptor() {
return template -> {
if ("GET".equals(template.method()) {
template.header(Authorization, apiKey);
} else {
template.header(Authorization, differentApiKey);
}
};
}
Everything you need seems to be in the code below.
#Bean.
public RequestInterceptor requestInterceptor() {
return template -> {
template.request().url() // this is request url which is from feign!
template.requsst().httpMethod() // this is request method!
};
}
I have created a Spring Boot 2 Application, integrated SpringFox Swagger 2.8.0 with Implicit Oauth2 Grant for Authentication and Authorization.
The Code is working fine but when I click Authorize button it redirects to the
http://localhost:8080/oauth/authorize?response_type=token&client_id=test-app-client-id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fwebjars%2Fspringfox-swagger-ui%2Foauth2-redirect.html&scope=read&state=U3VuIE9jdCAxNCAyMDE4IDIwOjQyOjUwIEdNVCswNTMwIChJbmRpYSBTdGFuZGFyZCBUaW1lKQ%3D%3D
but shows Access Denied like as shown below.
My complete project is available in GitHub
MainApplication.java
#EnableSwagger2
#SpringBootApplication
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#RestController
public class MainApplication /*extends WebMvcConfigurerAdapter*/
{
public static void main(String[] args)
{
SpringApplication.run(MainApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#Bean
SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()//<19>
.clientId("test-app-client-id")
.build();
}
#Bean
SecurityScheme oauth() {
List<GrantType> grantTypes = new ArrayList<>();
ImplicitGrant implicitGrant = new ImplicitGrant(new LoginEndpoint("http://localhost:8080/oauth/authorize"),"access_code");
grantTypes.add(implicitGrant);
List<AuthorizationScope> scopes = new ArrayList<>();
scopes.add(new AuthorizationScope("read","Read access on the API"));
return new OAuthBuilder()
.name("SECURITY_SCHEME_OAUTH2")
.grantTypes(grantTypes)
.scopes(scopes)
.build();
}
#Bean
public Docket docket()
{
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage(getClass().getPackage().getName()))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(oauth()))
.apiInfo(generateApiInfo());
}
private ApiInfo generateApiInfo()
{
return new ApiInfo("Sample Service", "This service is to check Sample Service.", "Version 1.0",
"Sample Service", "123#test.com", "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0");
}
}
Update 1
I have added the security and the passwordencoder configure suggested from #AlexanderPetrov. Things are working fine, when I add #EnableResourceServer my login screen is showing Full authentication is required to access this resource like as shown below
Can anyone please help me on this
You need to do the following changes in your code
Form login configuration is necessary for implicit flow.
Also if we use implicit flow token will be generated through authorization url instead of token url. So you need to change "/oauth/token" to "oauth/authorize". configure method below
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/oauth/authorize").authenticated()
.and()
.authorizeRequests().anyRequest().permitAll()
.and()
.formLogin().permitAll()
.and()
.csrf().disable();
}
Add password encoder in SecurityConfig class and invoke it to encode user password in globalUserDetails method. Encoder is necessary because you use in memory passwords. So without password encoder application fails with an error:
Encoded password does not look like BCrypt
Code fragment below
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = passwordEncoder();
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).
withUser("bill").password(passwordEncoder.encode("abc123")).roles("ADMIN").and()
.withUser("$2a$10$TT7USzDvMxMZvf0HUVh9p.er1GGnjNQzlcGivj8CivnaZf9edaz6C")
.password("$2a$10$TT7USzDvMxMZvf0HUVh9p.er1GGnjNQzlcGivj8CivnaZf9edaz6C").roles("USER");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Hope it helps. I've created branch for your project but couldn't push it because of 403. So all necessary code is here in my answer.
When you enable a resource server, you need to configure the check_token URL, so that it can reach the OAuth2 authorization server and validate the given access_token.
You could do something like:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {
#Value("${oauth.url.internal}") // e.g. http://localhost:8082/oauth
private String oauthUrl;
#Value("${oauth.client}")
private String oauthClient;
#Value("${oauth.secret}")
private String oauthSecret;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
#Primary
#Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl(oauthUrl + "/check_token");
tokenService.setClientId(oauthClient);
tokenService.setClientSecret(oauthSecret);
return tokenService;
}
}
Besides this, you may want to ignore Swagger-specific endpoints:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**");
}
}
Just in case, this is the class I implemented for Swagger w/ OAuth2 authorization:
#EnableSwagger2
#Configuration
public class SwaggerConfig implements WebMvcConfigurer {
private static final String BASE_PACKAGE = "com.somepackage.api";
#Value("${oauth.url}") // Make sure this is an external URL, i.e. accessible from Swagger UI
private String oauthUrl;
#Value("${swagger.scopes}")
private String swaggerScopes;
#Value("${swagger.urls}")
private String swaggerUrls; // Your v2/api-docs URL accessible from the UI
#Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage(BASE_PACKAGE))
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext()));
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
authorizationScopeList.add(new AuthorizationScope(swaggerScopes, ""));
List<GrantType> grantTypes = new ArrayList<>();
GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(oauthUrl + "/token");
grantTypes.add(creGrant);
return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant(swaggerUrls)).build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = new AuthorizationScope(swaggerScopes, "");
return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Versions:
springSecurityVersion = '2.0.5.RELEASE'
swaggerVersion = '2.8.0'
springBootVersion = '2.0.5.RELEASE'
I have the AuthorizationServer. Besides standard functions i have controller who let to create user. After successful user creates the method must to return token for this user. The problem is that the method return valid token only at first call. At next calls - following users will get the first user's token. I tried to set scope(request) for restTemplate - but obtained the error: " Scope 'request' is not active for the current thread"
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
...
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
...
}
protected ResourceOwnerPasswordResourceDetails getOwnerPasswordResource(){
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
List scopes = new ArrayList<String>(3);
scopes.add(SCOPE_READ);
scopes.add(SCOPE_WRITE);
scopes.add(SCOPE_TRUST);
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(CLIENT_ID);
resource.setClientSecret(CLIENT_SECRET_UNCODED);
resource.setGrantType(GRANT_TYPE_PASSWORD);
resource.setScope(scopes);
return resource;
}
}
Here the OAuth2Client:
#EnableOAuth2Client
#Configuration
public class ClientConfig {
#Autowired
AuthorizationServerConfig authorizationServerConfig;
#Bean
//#Scope("request")
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(authorizationServerConfig.getOwnerPasswordResource(), new DefaultOAuth2ClientContext(atr));
}
}
And my controller:
#RestController
public class UserRestController {
#Autowired
private OAuth2RestOperations restTemplate;
#PostMapping("/user")
public OAuth2AccessToken createUserCredential(#RequestBody UserCredential user) {
user.validate();
userCredentialService.checkAndSaveUser(user, getClientIp(request));
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", user.getLogin());
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", user.getPassword);
return restTemplate.getAccessToken();
}
}
May be exists more correct way to obtain token inside of AuthorizationServer ?
I thought have some special way.. but not found it. And solved problem on following way
#EnableOAuth2Client
#Configuration
public class OAuthClientConfig {
#Autowired
AuthorizationServerConfig authorizationServerConfig;
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(authorizationServerConfig.getOwnerPasswordResource(), new DefaultOAuth2ClientContext(atr));
}
}
And my controller:
#RestController
public class UserRestController {
#Autowired
private OAuthClientConfig oAuthClientConfig;
#PostMapping("/user")
public OAuth2AccessToken createUserCredential(#RequestBody UserCredential user) {
user.validate();
userCredentialService.checkAndSaveUser(user, getClientIp(request));
OAuth2RestOperations restTemplate = oAuthClientConfig.restTemplate();
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", user.getLogin());
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", user.getPassword);
return restTemplate.getAccessToken();
}
}
May be it will help to someone
I was facing the same issue I found this other way to make it work
#Bean
#Primary
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext context,
OAuth2ProtectedResourceDetails details) {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(atr));
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList(
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
And then I just did the injection
private final OAuth2RestTemplate oauth2RestTemplate;
#GetMapping(path = "/token")
public String token(Credentials credentials) {
oauth2RestTemplate.getOAuth2ClientContext()
.getAccessTokenRequest().add("username", credentials.getEmail());
oauth2RestTemplate.getOAuth2ClientContext()
.getAccessTokenRequest().add("password", credentials.getPass());
final OAuth2AccessToken accessToken = oauth2RestTemplate.getAccessToken();
final String accessTokenAsString = accessToken.getValue();
return accessTokenAsString ;
}
EDIT: I'm using Swagger UI 2.5.0 and attempting to configure it to use oauth authentication. From what I understand from looking at the petstore example and other pieces I've read if I include a security schema & context in my Docket it should automatically display the button - is this the case? If so what else am I missing here?
My API's appear fine in the swagger UI - it's just that the authorize button (and therefore any means of authorization) is missing
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("api")
.select()
.apis(RequestHandlerSelectors.basePackage("com.example"))
.paths(PathSelectors.regex("/api.*"))
.build()
.securitySchemes(newArrayList(securitySchema()))
.securityContexts(newArrayList(securityContext()));
}
public static final String securitySchemaOAuth2 = "oauth2schema";
public static final String authorizationScopeGlobal = "global";
public static final String authorizationScopeGlobalDesc ="accessEverything";
private OAuth securitySchema() {
AuthorizationScope authorizationScope = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobal);
LoginEndpoint loginEndpoint = new LoginEndpoint("http://localhost:9999/sso/login");
GrantType grantType = new ImplicitGrant(loginEndpoint, "access_token");
return new OAuth(securitySchemaOAuth2, newArrayList(authorizationScope), newArrayList(grantType));
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("/api.*"))
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope
= new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return newArrayList(
new SecurityReference(securitySchemaOAuth2, authorizationScopes));
}