I've followed this Spring Boot OAuth2 tutorial on configuring an OAuth2 client. Unfortunately, once a "user" authenticates with the Idp (Okta) a redirect with a "code" takes place that results in a redirect loop of: /login -> /authorize... -> /login... -> /login
Firefox has detected that the server is redirecting the request for this address in a way that will never complete.
Does anyone know what is or could be the issue and how to solve it? Details follow.
Okta configuration:
Login redirect URIs: http://localhost:8080/auth/login
Logout redirect URIs:
http://localhost:8080/auth/logout
Login initiated by: App only
Initiate login URI: http://localhost:8080/auth/login
The configuration properties are:
okta:
oauth2:
client:
client-id: clientId
client-secret: clientSecret
scope: openid profile email
client-authentication-scheme: form
access-token-uri: https://mydomain.oktapreview.com/oauth2/myapp/v1/token
user-authorization-uri: https://mydomain.oktapreview.com/oauth2/myapp/v1/authorize
resource:
user-info-uri: https://mydomain.oktapreview.com/oauth2/myapp/v1/userinfo
The filter is:
private Filter filter() {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
"/login");
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oktaClient(), oauth2ClientContext);
filter.setRestTemplate(restTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(oktaResource().getUserInfoUri(),
oktaClient().getClientId());
tokenServices.setRestTemplate(restTemplate);
filter.setTokenServices(tokenServices);
return filter;
}
The WebSecurityConfigurerAdapter configure is:
#Configuration
#EnableOAuth2Client
public class WebSecConfig extends WebSecurityConfigurerAdapter {
....
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**", "/logout**", "/v2/api-docs", "/configuration/ui",
"/configuration/security", "/swagger-resources/**", "/swagger-ui.html", "/webjars/**")
.permitAll()
.anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).and().csrf()
.csrfTokenRepository(
CookieCsrfTokenRepository.withHttpOnlyFalse()).and().addFilterBefore(filter(),
BasicAuthenticationFilter.class);
}
....
}
Update:
Solution was to change LoginUrlAuthenticationEntryPoint("/login") to LoginUrlAuthenticationEntryPoint("/") and re-create the authorization server.
You should use the default authorization server, or one you created. If you use the default, it should look something like:
https://mydomain.oktapreview.com/oauth2/default
Related
I've configured a spring oauth2 server following the spring.io get started documentation. When I initiate a login from an Angular client running on port 4200, when the logging is successfull (ie: correct id and password are given), I get redirected to the redirect_uri of the angular client only when I use 127.0.0.1:4200 to access my angular app. If I come from localhost I get redirect to an error page
Spring.io doc
My spring configuration :
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
)
// Accept access tokens for User Info and/or Client Registration
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
#Bean
#Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
return http.build();
}
#Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient registeredClient = RegisteredClient.withId("bakcup-ui")
.clientId("backup-ui")
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri("http://localhost:4200")
.redirectUri("http://127.0.0.1:4200")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
.build();
// Save registered client in db as if in-memory
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(registeredClient);
return registeredClientRepository;
}
My angular side configuration (using angular-oauth2-oidc) :
export const authConfig: AuthConfig = {
clientId: 'backup-ui',
issuer: 'https://localhost:9000',
redirectUri: window.location.origin,
responseType: 'code',
scope: 'openid profile',
strictDiscoveryDocumentValidation: false,
}
The url I get redirected to when coming from localhost:
localhost:9000/error?response_type=code&client_id=backup-ui&state=M09hSHdSdC5KSlRfOGlLNDZWa1NQbE05TDFRamJ6NGtwUDludUk0blM1Y1dO&redirect_uri=http%3A%2F%2Flocalhost%3A4200&scope=openid%20profile&code_challenge=_LU-Ou9Gr_53_LGDKTz36bJddFr2gndknIrZoC03ZJo&code_challenge_method=S256&nonce=M09hSHdSdC5KSlRfOGlLNDZWa1NQbE05TDFRamJ6NGtwUDludUk0blM1Y1dO&continue
Error page
If after that I go back to localhost:4200, I land there:
localhost:9000/oauth2/authorize?response_type=code&client_id=backup-ui&state=SUhKQ1BLejFfeUJTalVwUHlpWEVGYURQUDV1M3lsUG5lZElvMjd3QzNRbWJr&redirect_uri=http%3A%2F%2Flocalhost%3A4200&scope=openid%20profile&code_challenge=Y-V75oU8Zjsf7lctW9otdkn-U0vML-nGv8v1FCbrM5A&code_challenge_method=S256&nonce=SUhKQ1BLejFfeUJTalVwUHlpWEVGYURQUDV1M3lsUG5lZElvMjd3QzNRbWJr
With the same error page (since there is no html in my spring server)
I tried a lot of things but none are working. Even using the sample code from github spring-authrozation-server produces the same problem.
I am trying to debug a spring boot application secured behind AWS cognito.
I have set up the user pools and app config according to these tutorials:
https://www.baeldung.com/spring-security-oauth-cognito
https://www.czetsuyatech.com/2021/01/aws-generate-cognito-access-token.html
https://betterjavacode.com/programming/example-of-spring-boot-application-authentication-with-aws-cognito
I am only interested in programmatic REST access, not spring mvc.
Demo Controller:
#GetMapping(path = "/hello")
public ResponseEntity<String> hello() {
System.out.println("Hello");
return new ResponseEntity<>("Hello", HttpStatus.OK);
}
application.yml
spring:
servlet:
multipart:
max-file-size: 50MB
max-request-size: 50MB
security:
oauth2:
client:
registration:
cognito:
clientId: xxxxxx
clientSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
scope: openid
redirect-uri: http://localhost:8080/login/oauth2/code/cognito
clientName: w3p-app-client
provider:
cognito:
issuerUri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_zcZ0Enk0d
user-name-attribute: cognito:username
Security Config:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.and()
.logout()
.logoutSuccessUrl("/");
}
}
And this works in a browser localhost:8080/hello. I get the cognito login page where I can sign in with a demo user created in the user pool. After that I get the text "Hello" in my browser as expected.
Now I wanted to setup a postman collection for testing my API:
"Get New Access Token" opens the cognito login where I can successfully sign in and obtain the token. However if I now try to acces my /hello, with inherited auth from the parent, I still get redirected to the cognito login every time, even though the token is sent along.
I have saved the collection and request, I have tried with and without bearer, enabled all options in cognito and whatnot. At this point I am out of ideas. Any hints would be appreciated. For some reason cognito does not redirect to my api as in the browser, but send the login page instead. It seems like any auth setting is completely ignored, same behavior with "no auth" selected.
In your case your service is in the role of a resource server. In the context of OAuth2 a resource server is an application, which endpoints are secured via OAuth tokens.
The basic configuration to make this work would be:
Add the following dependencies in your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
In your application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
https://cognito-idp.us-east-1.amazonaws.com/us-east-1_zcZ0Enk0d
Your resource server will use this property to validate the incoming JWT tokens.
This is all you need in order to secure all your endpoints.
If you need to add any custom security configuration here is an example:
#Configuration
#EnableWebSecurity
public class CustomSecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> authorize
.mvcMatchers("/my-public-endpoint").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.build();
}
}
You can find further information in the spring-security documentation
Here is also one tutorial
Your problem is in your SecurityConfiguration. You defined everything for anyRequest. You can use antMatchers to define authentication only for some request based on URL-path. Or you can give free access with permitAll to limited URLs by path with antMatchers. Here is an example how antMatchers can be used and how you can deal with all remaining (anyRequest) URL-paths.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/needToken").authenticated()
.anyRequest().permitAll();
}
We have an API service which has multiple APIs exposed, and there are multiple personas who/which can access our service.
Users - Who needs to have an account in our system -> Needs to be
authenticated with our Identity Provider Service (Keycloak) with JWT
token.
Regulated System - Which needs to be authenticated with central
authority maintained by some party.
Internal service to service communication -> authentication with same
Keycloak.
Temporary JWT token issued by the same service before creating the user
account when the user digitally verified the mobile number.
I was trying to have AuthenticationWebFilter for each authentication type, and configure with Pathmatchers, though it was getting authenticated by the right authentication web filter, the request keeps flowing through the other authentication filter, and ends up resulting as unauthorized.
Snippet of configuration:
public class Configuration {
#Bean
#Order(1)
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity,
#Qualifier("userCreationFilter")
AuthenticationWebFilter userCreationFilter) {
final String[] WHITELISTED_URLS = {"/**.json",
"/users/verify",
"/users/permit",
"/sessions",
"/internal/xxxxx/**",
"/**.html",
"/**.js",
"/**.yaml",
"/**.css",
"/**.png"};
httpSecurity.authorizeExchange().pathMatchers(WHITELISTED_URLS).permitAll();
httpSecurity.addFilterBefore(userCreationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/users")
.authenticated();
httpSecurity.httpBasic().disable().formLogin().disable().csrf().disable().logout().disable();
return httpSecurity.build();
}
#Bean
#Order(2)
public SecurityWebFilterChain securityWebFilterChain2(ServerHttpSecurity httpSecurity,
#Qualifier("managerFilter")
AuthenticationWebFilter managerFilter) {
httpSecurity.addFilterBefore(managerFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.pathMatchers("/xxxxx/**",
"/providers",
"/xxxxx/**/approve",
"/xxxx/**/xxxxx").authenticated();
return httpSecurity.build();
}
}
Right now there are no roles as such we have.
I tried keeping all configuration in single SecurityWebFilterChain Bean, and tried addWebFilterAt, but no luck.
What am I missing? Should I do it different way?
So I have followed the guide here to implement a Spring Authorization Server. I have managed to get both a client and server ready for testing. The reason why I need and authorization server is because I need to be able to authenticate multiple microservices that work together.
I have run both the server and client on my machine. The authorization server at localhost:9999 and the client at localhost:8080/client (setting the client app context-path to /client). The authentication for the client works perfectly in this scenario. To make this easy, I decided to host the authorization server on AWS using Elastic Beanstalk. For secrecy, I will use SERVER_URL as the hosted authorization server's URL.
Once the server is hosted, I run the client using the hosted authorization server with the correct token and authorization URLs. Once I go to localhost:8080, I get redirected to SERVER_URL where the homepage displays a link to Google OAuth login. Clicking on that link takes me to google login and I login with my google account. After the google login, it redirects back to SERVER_URL/login?code=the_code&state=the_state. This is is not what I want. After logging into Google, the user should be redirected back to localhost:8080/login?code=the_code&state=the_state.
I have been researching for days but cannot seem to find a solution to this problem. I need to be able to test multiple microservices on localhost with the hosted server.
My authorization server main application class is the default (I haven't added anything). Here is my security configuration:
#Configuration
#EnableWebSecurity
#EnableOAuth2Client
#EnableAuthorizationServer
#Order(200)
public class OAuthSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final String GOOGLE_LOGIN_FILTER = "/login/google";
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Bean
#ConfigurationProperties("google")
public ClientResources google() {
return new ClientResources();
}
private Filter ssoFilter() {
return ssoFilter(google(), GOOGLE_LOGIN_FILTER);
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter(GOOGLE_LOGIN_FILTER);
OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
googleFilter.setRestTemplate(googleTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(client.getResource().getUserInfoUri(), client.getClient().getClientId());
tokenServices.setRestTemplate(googleTemplate);
googleFilter.setTokenServices(tokenServices);
return googleFilter;
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
/**
* Specify the authorization criteria for request access.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/"))
.and()
.logout()
.logoutSuccessUrl("/")
.permitAll()
.and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.csrf()
.disable();
}
}
class ClientResources {
#NestedConfigurationProperty
private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
#NestedConfigurationProperty
private ResourceServerProperties resource = new ResourceServerProperties();
public AuthorizationCodeResourceDetails getClient() {
return client;
}
public ResourceServerProperties getResource() {
return resource;
}
}
Then I have a separate class for protecting the "/me" path:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/me")
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
And my application.yml for the auth server:
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
server:
port: ${PORT:9999}
error:
whitelabel:
enabled: false
security:
oauth2:
client:
client-id: google client id
client-secret: google client secret
scope: read,write
auto-approve-scopes: '.*'
google:
client:
clientId: google client id
clientSecret: google client secret
accessTokenUri: https://oauth2.googleapis.com/token
userAuthorizationUri: https://accounts.google.com/o/oauth2/auth
clientAuthenticationScheme: form
scope:
- openid
- profile
- email
resource:
userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
preferTokenInfo: true
The controller for "/me" path is trivial, so I am not going to include that here. And yes, I am using the same google client id and secret for Google and the server itself (for now).
Now on my client side, the application.yml:
authorization-server-url: SERVER_URL
security:
oauth2:
client:
clientId: google client id
clientSecret: google client secret
accessTokenUri: ${authorization-server-url}/oauth/token
userAuthorizationUri: ${authorization-server-url}/oauth/authorize
resource:
userInfoUri: ${authorization-server-url}/me
And the security configuration on my client side uses #Configurable, #EnableWebSecurity, and #EnableOAuth2SSO. The security configuration prevents access of unauthenticated users, which is why as soon as I go on localhost:8080, it redirects me to the auth server.
The auth server, when used by itself, successfully logs into google. And if the client application at localhost:8080 is configured to authenticate with Google OAuth directly instead of the custom auth server, then it does that successfully. The problem is the proper redirect after the auth server has logged in with Google.
Just to clarify, I am after the flow localhost:8080 -> auth server -> click link which takes you to SERVER_URL/login/google -> Login with Google -> localhost:8080. But what I'm getting is localhost:8080 -> auth server -> click link which takes you to SERVER_URL/login/google -> Login with Google -> SERVER_URL/login?code=the_code&state=the_state.
Thanks in advance.
EDIT:
Here is my gradle configuration:
buildscript {
ext {
springBootVersion = '2.0.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse-wtp'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'war'
group = this is a secret
version = '0.0.4-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
configurations {
providedRuntime
}
ext {
springCloudVersion = 'Finchley.SR1'
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-security')
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.cloud:spring-cloud-starter-oauth2')
implementation('javax.servlet:jstl')
implementation('org.apache.tomcat.embed:tomcat-embed-jasper')
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
testImplementation('org.springframework.boot:spring-boot-starter-test')
testImplementation('org.springframework.security:spring-security-test')
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
EDIT 2:
The error page I get is at the url http://<SERVER_URL>/login?code=xgYiHr&state=Wp1SUW where <SERVER_URL> is the domain name of the authorization server. And I get the following error page:
And the Network traffic for the error page. According to this, http://<SERVER_URL>/oauth/authorize has the correct redirect_uri which is http://localhost:8080/login, but when it comes to redirecting to it the localhost:8080 url gets replaced with <SERVER_URL>:
Funny thing is, I just tried running the client application through http://127.0.0.1:8080 instead of http://localhost:8080 and it works perfectly! Not sure why localhost:8080 gets replaced by the <SERVER_URL>. I'm not sure if this problem is with AWS or Spring.
It seems like I was right in thinking there is something wrong with my AWS setup. I changed the environment in Elastic Beanstalk to use Nginx as the Proxy server, which solved my issue. Now I can test my client using localhost instead of 127.0.0.1.
Our application is using spring security to secure the application,i just added one rest controller which supporting spring oauth security, for oauth token validation, will be called by some other application following are my controller code
#RestController
#EnableResourceServer
public class Controller extends BaseRestController{
#RequestMapping(value="/api/v1/public/insertData", method=RequestMethod.POST)
ResponseEntity<?> insertTPQueueData(TitleProcurementQueue queue,Authentication authentication) {
return null;
}
}
after adding spring oauth security i am getting following error for my other controller using spring security
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
Please help
When you put security in your project spring implement some filters, like Cors, basic auth etc..
So you need to tell spring how apply security in your resources.
enter link description here
need to create a class with #EnableWebSecurity
and configure like this:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.csrf().disable();
http.headers().frameOptions().sameOrigin();
}