My app works well with normal non-SSL OAuth2 Servers. But I dont know how to config client to work with the SSL equivalents. I always get 401 when oauth2 server redirects back to http://localhost:8051/app1/login after success authentication and granting permissions. My App code likes below:
application.yml:
server:
port: 8051
servlet:
contextPath: /app1
security:
oauth2:
client:
clientId: sampleclient
clientSecret: samplepw
accessTokenUri: https://localhost:8668/sso-server/oauth/token
userAuthorizationUri: https://localhost:8668/sso-server/oauth/authorize
tokenName: oauth_token
resource:
userInfoUri: https://localhost:8688/api/me
Application.java
#SpringBootApplication
#EnableOAuth2Sso
public class App1 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login", "/error**", "/webjars/**").permitAll()
.anyRequest().authenticated()
.and().logout()
.logoutUrl("/logout")
.logoutSuccessUrl("https://localhost:8668/sso-server/logout")
.invalidateHttpSession(true)
.deleteCookies("client-session", "JSESSIONID")
.permitAll()
.and().csrf().disable()
;
}
public static void main(String[] args) {
SpringApplication.run(App1.class, args);
}
}
OAuth2 Servers work as expected and I can call all oauth APIs that clients need with https schema on Postman, so I think we have nothing to do with them and don't post the code here.
Related
I am currently in the process of setting up a Swagger UI interface for one of the projects I am working on and I am experiencing various issues.
My project uses Spring security to secure the API calls using bearer token authentication, so I need to provide a way of enabling the input dialog so that users can input their bearer token. I have tried everything mentioned in the documentation of OpenAPI regarding this but nothing seems to work in rendering the dialog correctly.
Secondly the project does CSRF checks and even though my application properties include springdoc.swagger-ui.csrf.enabled=true the check fails constantly. I have a dead end and I have no idea how to resolve both problems. For reference my security configuration is the following:
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity security) {
if (securityProperties.isEnabled()) {
return security
.securityMatcher(new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers(securityProperties.getIgnoredPaths())))
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(entryPoint)
.and()
.cors()
.and()
.authorizeExchange(spec -> spec.anyExchange().authenticated())
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
.build();
}
return security
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/**"))
.authorizeExchange(spec -> spec.anyExchange().permitAll())
.csrf()
.disable()
.build();
}
We fixed it with our multi-provider (OAuth2 Keycloak for API and Basic Auth for Swagger UI) Webflux security configuration by adding this to every application.yaml:
springdoc:
api-docs:
enabled: true
swagger-ui:
oauth:
client-id: dev
client-secret: 123
scopes: [openid]
csrf:
enabled: false
Key point here is csrf.enabled: false.
Our Keycloak security configuration:
// Keycloak-based JWT authorization for #RestControllers
#Order(1)
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class JwtSecurityConfig {
#Bean("jwt")
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange()
.pathMatchers("/api/**")
.authenticated()
.and()
.csrf()
.disable()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
return http.build();
}
private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>>
grantedAuthoritiesExtractor() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new GrantedAuthoritiesExtractor());
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
}
My goal is to add SSO way in addition to login based into spring boot app.
I use Okta as identity provider and got stuck into misunderstanding conceptions:
When I follow single sign on url from Okta, it goes to localhost endpoint and falls with 415 response. My websecurity config:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers(SECURITY_WHITELIST)
.permitAll()
.and()
.httpBasic()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
/* configuring saml*/
.and()
.saml2Login(Customizer.withDefaults())
.saml2Logout(Customizer.withDefaults())
// .addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class).antMatcher("/**/auth-saml") //
.authorizeRequests()
.anyRequest().authenticated();
http.addFilterBefore(tokenAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
}
and controller's endpoint signature with console exception log:
#PostMapping(value = BASE_NAME,consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE )
ResponseEntity<AuthenticationDTO> login ( #RequestBody MultiValueMap body);
https://pastebin.com/UREcv7CW
Do I get it correctly that 415 response occurs due to token based configuration and it cant retrieve it from the header? Also any advice to configure both ways of authentication are welcome. Thanks!
ps: spring security config -
spring:
security:
saml2:
relyingparty:
registration:
okta:
identityprovider:
entity-id: https://www.okta.com/************
verification.credentials:
- certificate-location: classpath:credentials/okta.cert
singlesignon.url: https://trial-8410773.okta.com/app/trial-********/sso/saml
singlesignon.sign-request: false
I am trying to implement Microservices architecture backend using Spring Boot 1.5.6.RELEASE and Spring Cloud Dalston.SR3 that would be consumed by mobile/web endpoints.
API Gateway application
#SpringBootApplicatio
#EnableEurekaClient
#EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
API security
#Configuration
#EnableWebSecurity
#Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
#EnableOAuth2Sso
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/sign-up", "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf()
.ignoringAntMatchers("/sign-up", "/login")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// #formatter:on
}
}
Gradle security related dependencies
// Spring OAuth2 security
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.security.oauth:spring-security-oauth2")
compile("org.springframework.cloud:spring-cloud-starter-oauth2")
compile("org.springframework.security:spring-security-jwt")
Zuul routes
zuul:
ignoredServices: '*'
routes:
user-service:
path: /user-service/**
stripPrefix: false
serviceId: user-webservice
sensitiveHeaders:
task-service:
path: /task-service/**
stripPrefix: false
serviceId: task-webservice
sensitiveHeaders:
user:
path: /userauth/**
stripPrefix: false
serviceId: auth-server
sensitiveHeaders:
I am able to get the access token from the authorization server(stateless sessions - no JSESSIONID cookie)
curl -D - --request POST -u acme:acmesecret
"http://localhost:8899/userauth/oauth/token?grant_type=password&username=<...>&password=<...>"
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MDQ3ODg4NzgsInVzZXJfbmFtZSI6IjcyMTk2MTk2NDEiLCJhdXRob3JpdGllcyI6WyJST0xFX1BBVElFTlQiXSwianRpIjoiZThhMzBjNmQtZjA2MS00MWEzLWEyZGItYTZiN2ZjYTI5ODk1IiwiY2xpZW50X2lkIjoiYWNtZSIsInNjb3BlIjpbIm9wZW5pZCJdfQ.AhF_kqfsRYM1t1HVT........
I can use the access token to request data from the authorization server or another resource
curl -D - --request GET -H "Authorization: Bearer
eyJhbGciOiJSUzI1...." http://localhost:8899/userauth/me
{"authorities":[{"authority":"ROLE_P.........}
curl -D - --request GET -H "Authorization: Bearer
eyJhbGciOiJSUzI1NiIsInR5......." http://localhost:8081/user-service/
[{"firstName":"Anil".....}]
However for the same requests routed through the API gateway, it fails at the Gateway itself and is filtered as AnonymousAuthenticationToken.
curl -D - --request GET -H "Authorization: Bearer
eyJhbGciOiJSUzI1...." http://localhost:8765/user-service/
HTTP/1.1 302 Set-Cookie:
XSRF-TOKEN=b5a1c34e-e83c-47ea-86a6-13a237c027d4; Path=/ Location:
http://localhost:8765/login
I was assuming that with #EnableZuulProxy and #EnableOAuth2Sso, Zuul would take care to forward the bearer token to the downstream services but that is not happening. I already have a working sample that uses HTTP session and browser redirection to get the API gateway to pass tokens - https://github.com/anilallewar/microservices-basics-spring-boot
But I am struggling to get it to work with Stateless sessions, any pointers what might be missing on the Zuul API gateway side?
Zuul considers Authorization header as a sensitive header by default and does not pass it to downstream requests. To override this, you can modify sensitiveHeaders in Zuul configuration either globally (for all routes):
zuul:
# exclude Authorization from sensitive headers
sensitiveHeaders: Cookie,Set-Cookie
ignoredServices: '*'
Or for a specific route:
zuul:
ignoredServices: '*'
routes:
user-service:
path: /user-service/**
stripPrefix: false
serviceId: user-webservice
# exclude Authorization from sensitive headers
sensitiveHeaders: Cookie,Set-Cookie
To find more about the problem, check this question:
Authorization header not passed by ZuulProxy starting with Brixton.RC1
I was assuming that with #EnableZuulProxy and #EnableOAuth2Sso, Zuul would take care to forward the bearer token to the downstream services but that is not happening.
I assumed the same thing, but in my (painful) experience, #EnableOAuth2Sso secures all endpoints with SSO and blocks even the requests with a Bearer token from getting to downstream services. I had to change my gateway to disable authentication on the routes that lead to my resources, so that the request with a Bearer token could get through.
Try adding /user-service/** and /task-service/** to your permitAll() matcher:
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/sign-up", "/login", "/task-service/**", "/user-service/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf()
.ignoringAntMatchers("/sign-up", "/login")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// #formatter:on
}
I have a Spring Boot application with the following config:
#SpringBootApplication
#EnableOAuth2Sso
#Configuration
public class DemoApplication extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/","/appConfig", "/login/**", "/webjars/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and().logout()
.logoutSuccessUrl("/").permitAll()
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
...
}
In the resources, I have an application.yml, which contains the client configuration:
security:
oauth2:
client:
clientId: #keycloak.client.id#
clientSecret: #keycloak.client.secret#
accessTokenUri: https://#keycloak.host#:#keycloak.port#/auth/realms/#keycloak.realm#/protocol/openid-connect/token
userAuthorizationUri: https://#keycloak.host#:#keycloak.port#/auth/realms/#keycloak.realm#/protocol/openid-connect/auth
authenticationScheme: header
clientAuthenticationScheme: header
resource:
userInfoUri: https://#keycloak.host#:#keycloak.port#/auth/realms/#keycloak.realm#/protocol/openid-connect/userinfo
So far it works fine, but I have to set the clientSecret (and maybe also other properties) programatically at startup of the application as the client is registered with the OpenId server also at startup, so the secret is only known after the registration is done.
I did some experimentation with the AuthorizationServerConfigurerAdapter to create an inMemory client, but if it's also added to the filter chain, the application does not start at all:
- Bean method 'userInfoRestTemplateFactory' not loaded because #ConditionalOnMissingBean (types: org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration; SearchStrategy: all) found bean 'org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration'
Any ideas how to set the clientSecret at startup from code instead of hardcoding it in the application.yml?
These properties go to a ResourceServerProperties object. What if you create it yourself:
#Bean
public ResourceServerProperties ouathResource() {
// read from somewhere
return new ResourceServerProperties( id, secret)
}
The presence of this bean could suppress the automatic creation of other beans, in which case they will have to be created manually too.
I searched the web for solution of this problem but didn't find any working solution. I'm trying to setup basic Spring Boot OAuth2 Authorization Provider and Client.
I followed official Spring Boot instructions and created single sign on with Facebook and Github. Then i followed instructions to create Secure Spring Boot Web application.
I wanted to create my own Authorization Server so I added #EnableAuthorizationServer annotation to Secure Web Application as explained here. I also added details of an OAuth2 client as described in a link. I followed further instructions and created a OAuth2 Client.
I start both applications, visit 127.0.0.1:9999 to open a Client, client redirects me to localhost:8080/login, I enter user details and Authentication Provider redirects me to 127.0.0.1:9999/login and I get an error message:
Authentication Failed: Could not obtain user details from token
This is what gets logged:
INFO 2800 --- [nio-9999-exec-3] o.s.b.a.s.o.r.UserInfoTokenServices : Getting user info from: http:// localhost:8080/me
DEBUG 2800 --- [nio-9999-exec-3] o.s.s.oauth2.client.OAuth2RestTemplate : Created GET request for http:// localhost:8080/me
DEBUG 2800 --- [nio-9999-exec-3] o.s.s.oauth2.client.OAuth2RestTemplate : Setting request Accept header to [application/json, application/*+json]
DEBUG 2800 --- [nio-9999-exec-3] o.s.s.oauth2.client.OAuth2RestTemplate : GET request for http:// localhost:8080/me resulted in 200 (OK)
INFO 2800 --- [nio-9999-exec-3] o.s.b.a.s.o.r.UserInfoTokenServices : Could not fetch user details: class org.springframework.web.client.RestClientException, Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/html;charset=UTF-8]]
This is my Client application:
#EnableAutoConfiguration
#Configuration
#EnableOAuth2Sso
#RestController
public class ClientApplication {
#RequestMapping("/")
public String home(Principal user) {
return "Hello " + user.getName();
}
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
This is client application YML:
server:
port: 9999
security:
oauth2:
client:
client-id: acme
client-secret: acmesecret
access-token-uri: http://localhost:8080/oauth/token
user-authorization-uri: http://localhost:8080/oauth/authorize
resource:
user-info-uri: http://localhost:8080/me
This is my Authorization Provider application:
#SpringBootApplication
public class SecurityApp {
public static void main(String[] args) {
SpringApplication.run(SecurityApp.class, args);
}
}
#Configuration
#EnableWebSecurity
#EnableAuthorizationServer
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
#RestController
public class Controller {
#RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
}
This is Application Provider YML:
security:
oauth2:
client:
client-id: acme
client-secret: acmesecret
scope: read,write
auto-approve-scopes: '.*'
I solved the issue! I was missing the Resource Server which handles the requests for user endpoint (user-info-uri). To the Authorization Provider application I added this class:
#Configuration
#EnableResourceServer
public class ResourceServer
extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/me")
.authorizeRequests().anyRequest().authenticated();
}
}
The user-info-uri should be in authorization server because all the accounts/users are in it.