Spring OAuth2.0 : Spring Authorization Server 1.0 and Resource server in the same boot application - spring-security

I'm struggling to use the very same Spring Boot 3.0 application as both authentication server and resource server, but until now, I've not been able to make the whole thing working.
First, I defined a very simple RestController:
#RestController
#RequestMapping("api")
public class PublicAPI {
#GetMapping("/apitest")
public String test(Principal principal) {
return " This is a test ==>";
}
}
Then, essentially following the code found in a Sample project of Spring, I managed to setup my boot app as Spring Authorization Server. I'm able to use Postman to get the authentication token using Oauth2 flow: I'm redirected to Spring's standard login page, I log in with credentials, and I get the Token.
Problem is, if I try to GET http://localhost:9000/api/apitest` using provided token, I get a 401 response from Spring boot.
This is my Security Configuration:
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, CorsConfiguration configCors) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
http
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
);
http.cors().configurationSource(request -> configCors);
return http.build();
}
#Bean
#Order(2)
SecurityFilterChain apiFilter(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests()
.requestMatchers("/api/**").authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
#Bean
#Order(3)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CorsConfiguration configCors) throws Exception {
http
.securityMatcher("/oauth2/**", "/login")
.authorizeHttpRequests()
.requestMatchers("/login", "/oauth2/**")
.authenticated()
.and()
.formLogin(Customizer.withDefaults());
http.cors().configurationSource(request -> configCors);
return http.build();
}
#Bean
public CorsConfiguration corsConfiguration() throws Exception {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("*"));
configuration.setAllowedHeaders(List.of("*"));
return configuration;
}
If I try to access another Spring API in a different Spring Boot application which uses the first one as Authentication Server I get no errors.
Pretty sure that there's something wrong my configuration... any hint will be greatly appreciated !

At the very end, it turned out that another filter has been configured:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class LoopbackIpRedirectFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getServerName().equals("localhost") && request.getHeader("host") != null) {
UriComponents uri = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request))
.host("127.0.0.1").build();
response.sendRedirect(uri.toUriString());
return;
}
filterChain.doFilter(request, response);
}
}
Removing the LoopbackIpRedirectFilter problem was fixed

Related

Spring cloud gateway returns 403 for all post requests when security enabled

I have the following services:
Eureka server
Auth server
Spring Gateway
This will pass the login request to the login service
All other requests will be authenticated(using jwt token and secret key) and passed to other services
Login Service
To validate login and issue jwt token
Below is my config. The flow is not even reaching to this code..
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private SecurityContextRepository securityContextRepository;
#Bean(value="org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter")
public SecurityWebFilterChain springSecurityWebFilterChainFilter(ServerHttpSecurity http) {
LOGGER.info("In the securiry config..................");
return http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((swe, e) -> {
return Mono.fromRunnable(() -> {
swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
});
}).accessDeniedHandler((swe, e) -> {
return Mono.fromRunnable(() -> {
swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
});
})
.and()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.pathMatchers("/login-service/api/login").permitAll()
.anyExchange().authenticated()
.and().build();
}
}
I always get 403 with An expected CSRF token cannot be found this error even though I have disabled csrf.
Can anyone help what can be wrong here?
Have you tried adding your role:
.authorizeExchange()
.pathMatchers("/login-service/api/login").hasAuthority("ROLE_ADMIN")
.anyExchange().authenticated()
.and().build();

How to get role on Spring Cloud Gateway

I have a Spring Cloud Gateway and one Microservice
I am using OAuth2 with Keycloak
API Gateway is my OAuth2 Client and it serves secure pages and redirect request to microservice
I need to authorize access to renderer one page "/backoffice", but when i use hasRole method to authorize the user a receive Access Denied.
It occurs because keycloak generates access token with the pattern:
Access Token from Keycloak
It is my Gateway SecurityWebFilterChain
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf().disable();
http.
authorizeExchange()
.pathMatchers("/backoffice/**").hasRole("approver")
.anyExchange().permitAll()
.and()
.oauth2Login()
.and()
.oauth2Client();
return http.build();
}
}
I tried to use the same approach used by Microservice but I receive 401 Unauthorized... I tried to use #Order to have two filter but not works
// Microservice's SecurityWebFilterChain
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.authorizeExchange(exchanges ->
exchanges
.pathMatchers("/{id}/decision").hasRole("ROLE_user")
.anyExchange().permitAll())
.oauth2ResourceServer(oauth2 ->
oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor())));
return http.build();
}
Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor() {
JwtAuthenticationConverter jwtAuthenticationConverter =
new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new GrantedAuthoritiesExtractor());
return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}
static class GrantedAuthoritiesExtractor implements Converter<Jwt, Collection<GrantedAuthority>> {
#Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
var realm_access = (Map<String, List<String>>) jwt.getClaims().getOrDefault("realm_access", Collections.emptyMap());
return realm_access.get("roles").stream()
.map(role -> "ROLE_".concat(role))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
Can I have both in same application, is there conflict?

JWT : why do I get a new token after clearing localstorage?

I have an Angular 7 app with a spring API. I use spring security, JWT tokens, and https://github.com/manfredsteyer/angular-oauth2-oidc to handle login.
I'm able to login, but when I logout and clic on login once again, I immediately get a new token, without having to connect.
I'm new to spring security and JWT tokens, but from what I know, I cannot properly logout from the back, the classic way is to remove token from the front. That's what the library do, and to be sure I tried by emptying manualy the localStorage (where token is stored), but even with localStorage cleared, when I try to login, I get a new token immediately.
Any idea about what happengin and how can I logout?
Authorization server config :
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfigJwt extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("sampleClientId")
.authorizedGrantTypes("implicit")
.scopes("read", "write", "foo", "bar")
.autoApprove(true)/*.accessTokenValiditySeconds(3600)*/
.redirectUris("http://localhost:4200");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
}
Edit :
It seems that this was due to the lack of
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
But now that I've added it, I'm stuck on the login form, seems that there's still something missing. Here my config :
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/user").permitAll()
.antMatchers("/registrationConfirm").permitAll()
.antMatchers("/user/confirm/**").permitAll()
.antMatchers("/user/registration/**").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/tokens/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll();
}

Standalone Spring OAuth2 JWT Authorization Server + CORS

So I have the following Authorization Server condensed from this example from Dave Syer
#SpringBootApplication
public class AuthserverApplication {
public static void main(String[] args) {
SpringApplication.run(AuthserverApplication.class, args);
}
/* added later
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
protected static class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http //.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
}
}*/
#Configuration
#EnableAuthorizationServer
protected static class OAuth2AuthorizationConfig extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")
//.secret("acmesecret")
.authorizedGrantTypes(//"authorization_code", "refresh_token",
"password").scopes("openid");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager).accessTokenConverter(
jwtAccessTokenConverter());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
}
}
when I run it and test it with curl
curl acme#localhost:8110/oauth/token -d grant_type=password -d client_id=acme -d username=user -d password=password
I get a JWT as respons, but as soon as I try to access the AuthServer from my Frontend (Angular JS on a different port) I get CORS error. Not becauce of missing Headers, but because the OPTION request is rejected and is missing the credentials.
Request URL:http://localhost:8110/oauth/token
Request Method:OPTIONS
Status Code:401 Unauthorized
WWW-Authenticate:Bearer realm="oauth", error="unauthorized", error_description="Full authentication is required to access this resource"
I already knew that I have to add a CorsFilter and additionally found this post where I used the the snippet for the first Answer to let the OPTIONS request access /oauth/token without credentials:
#Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
}
}
After that I got with curl the following error:
{"timestamp":1433370068120,"status":403,"error":"Forbidden","message":"Expected CSRF token not found. Has your session expired?","path":"/oauth/token"}
So to make it simple I just added http.csrf().disable() to the configure method of MyWebSecurity class, which solves the Problem with the OPTION request, but therefore the POST request isn't working anymore and I get There is no client authentication. Try adding an appropriate authentication filter. (also with curl).
I tried to find out if I have to somehow connect MyWebSecurity class and the AuthServer, but without any luck. The original example (link in the beginning) injects as well the authenticationManager, but this changed nothing for me.
Found the reason for my Problem!
I just needed to end the filterchain and return the result immediatly if a OPTIONS request is processed by the CorsFilter!
SimpleCorsFilter.java
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
public SimpleCorsFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
After that I could ignore the OPTIONS preflight request in my AuthServer =D
So the Server works as in the snipped above and you can ignore the block comment with MyWebSecurity class in the beginning.
I found a solution using the solution for the question. But I have another way to describe the solution:
#Configuration
public class WebSecurityGlobalConfig extends WebSecurityConfigurerAdapter {
....
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS);
}
...
}
I came across similar issue using following
Backend Spring Boot 1.5.8.RELEASE
Spring OAuth2 Spring OAuth 2.2.0.RELEASE w
Vuejs app using axios ajax request library
With postman everything works! When I started making request from Vuejs app then I got the following errors
OPTIONS http://localhost:8080/springboot/oauth/token 401 ()
and
XMLHttpRequest cannot load http://localhost:8080/springboot/oauth/token. Response for preflight has invalid HTTP status code 401
After reading a bit, I found out that I can instruct my Spring OAuth to ignore the OPTIONS request by overriding configure in my WebSecurityConfigurerAdapter implementation class as follow
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS);
}
Addition of the above helped but then, I came across the CORS specific error
OPTIONS http://localhost:8080/springboot/oauth/token 403 ()
and
XMLHttpRequest cannot load http://localhost:8080/springboot/oauth/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. The response had HTTP status code 403.
And solved the above issue with the help of a CorsConfig as shown below
#Configuration
public class CorsConfig {
#Bean
public FilterRegistrationBean corsFilterRegistrationBean() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setExposedHeaders(Arrays.asList("content-length"));
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
After addition of the above class, it works as expected. Before I go prod I will research consequences of using
web.ignoring().antMatchers(HttpMethod.OPTIONS);
as well as best practices for above Cors configuration. For now * does the job but, definitely not secure for production.
Cyril's answer helped me partially and then I came across the CorsConfig idea in this Github issue.
well, you're right! that's a solution, and it worked also for me (I had the same issue)
But let me sussgest to use a smarter CORS Filter implementation for Java:
http://software.dzhuvinov.com/cors-filter.html
This is very complete solution for Java applications.
Actually, you can see here how your point is resolved.
Using Spring Boot 2 here.
I had to do this in my AuthorizationServerConfigurerAdapter
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
Map<String, CorsConfiguration> corsConfigMap = new HashMap<>();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
//TODO: Make configurable
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedHeaders(Collections.singletonList("*"));
corsConfigMap.put("/oauth/token", config);
endpoints.getFrameworkEndpointHandlerMapping()
.setCorsConfigurations(corsConfigMap);
//additional settings...
}
I tried different things to solve this issue. I would say that the below was fixed this issue on my side (Using Spring Boot 2)
1-Add the below method to the below method class that extends WebSecurityConfigurerAdapter:
// CORS settings
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS);
}
2-Add the below to my class that extends AuthorizationServerConfigurerAdapter
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// enable cors for "/oauth/token"
Map<String, CorsConfiguration> corsConfigMap = new HashMap<>();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowedHeaders(Collections.singletonList("*"));
corsConfigMap.put("/oauth/token", config);
endpoints.getFrameworkEndpointHandlerMapping()
.setCorsConfigurations(corsConfigMap);
// add the other configuration
}

Spring Security OAuth2 Java Config for Google Login

I am migrating working XML configuration to Java configuration for Spring Security OAuth2 and using Google as the OAuth provider.
This is how my java configuration looks:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final List<String> scope;
static {
// Permissions to access email and profile
scope = new ArrayList<>(3);
scope.add("openid");
scope.add("email");
scope.add("profile");
}
#Autowired(required = true)
private UserService userService;
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
authorizeRequests()
.antMatchers(HttpMethod.GET, "/","/public/**", "/resources/**","/resources/public/**").permitAll()
//.antMatchers("/google_oauth2_login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/home")
.and()
.csrf().disable()
.logout()
.logoutSuccessUrl("/")
.logoutUrl("/logout")
.and()
.requiresChannel().anyRequest().requiresSecure()
.and()
.addFilterAfter(oAuth2ClientContextFilter(),ExceptionTranslationFilter.class)
.addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
.userDetailsService(userService);
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
// #formatter:off
auth
.authenticationProvider(googleOauth2AuthProvider())
.userDetailsService(userService);
// #formatter:on
}
#Bean
public GoogleOAuth2Filter googleOAuth2Filter() throws Exception {
GoogleOAuth2Filter filter = new GoogleOAuth2Filter(
"/google_oauth2_login",
"https://accounts.google.com/o/oauth2/auth",
oAuth2RestTemplate(auth2ProtectedResourceDetails()));
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
#Bean
public GoogleOauth2AuthProvider googleOauth2AuthProvider() {
GoogleOauth2AuthProvider authProvider = new GoogleOauth2AuthProvider();
return authProvider;
}
#Bean
public OAuth2ProtectedResourceDetails auth2ProtectedResourceDetails() {
AuthorizationCodeResourceDetails auth2ProtectedResourceDetails = new AuthorizationCodeResourceDetails();
auth2ProtectedResourceDetails
.setClientAuthenticationScheme(AuthenticationScheme.form);
auth2ProtectedResourceDetails
.setAuthenticationScheme(AuthenticationScheme.form);
auth2ProtectedResourceDetails.setGrantType("authorization_code");
auth2ProtectedResourceDetails
.setClientId("the-client-id");
auth2ProtectedResourceDetails
.setClientSecret("the-client-secret");
auth2ProtectedResourceDetails
.setAccessTokenUri("https://accounts.google.com/o/oauth2/token");
auth2ProtectedResourceDetails.setScope(scope);
auth2ProtectedResourceDetails
.setUserAuthorizationUri("https://accounts.google.com/o/oauth2/auth");
auth2ProtectedResourceDetails.setUseCurrentUri(false);
auth2ProtectedResourceDetails
.setPreEstablishedRedirectUri("https://localhost/google_oauth2_login");
return auth2ProtectedResourceDetails;
}
#Bean
public OAuth2RestTemplate oAuth2RestTemplate(
OAuth2ProtectedResourceDetails resource) {
OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource);
return oAuth2RestTemplate;
}
#Bean
public OAuth2ClientContextFilter oAuth2ClientContextFilter() {
OAuth2ClientContextFilter oAuth2ClientContextFilter = new OAuth2ClientContextFilter();
return oAuth2ClientContextFilter;
}
}
Note that I have disabled CSRF.
From my login page the user do gets redirected to Google login page
Problem:-
Google Permission Page just asks for "Have offline access".'Email/Profile' access request is missing.
The equivalent 'scope' attibute XML configuration :-
<oauth2:resource id="googleOauth2Resource" type="authorization_code"
client-id="the-client-id
client-secret="the-client-secret"
user-authorization-uri="https://accounts.google.com/o/oauth2/auth"
scope="openid email profile" use-current-uri="false"
client-authentication-scheme="form" pre-established-redirect-uri="https://localhost/google_oauth2_login" />
do correctly asks for email and profile permissions. Why?
Continuing anyway with the 'Have offline access' results in this exception:-
org.springframework.web.client.HttpClientErrorException: 400 Bad Request
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:243)
at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:592)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:550)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:514)
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAuthorizationCode(AuthorizationCodeAccessTokenProvider.java:145)
at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:196)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:142)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)
while trying to get user profile using this code block:
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
logger.info("Google Oauth Filter Triggered!!");
SecurityContext context = SecurityContextHolder.getContext();
// auth null or not authenticated.
String code = request.getParameter("code");
Authentication dummyAuthentication = null;
if (StringUtils.isEmpty(code)) {
// Google authentication in progress. will return null.
logger.debug("Will set dummy user in context ");
dummyAuthentication = getDummyAuthenticationToken();
context.setAuthentication(dummyAuthentication);
// trigger google oauth2.
oauth2RestTemplate.postForEntity(authURI, null, Object.class);
return null;
} else {
// response from google received !!.
// remove dummy authentication from context.
//SecurityContextHolder.clearContext();
logger.debug("Response from Google Recieved !!");
// get user profile and prepare the authentication token object.
ResponseEntity<Object> forEntity = oauth2RestTemplate.getForEntity(
HTTPS_WWW_GOOGLEAPIS_COM_PLUS_V1_PEOPLE_ME_OPEN_ID_CONNECT,
Object.class);
#SuppressWarnings("unchecked")
Map<String, String> profile = (Map<String, String>) forEntity
.getBody();
CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(
profile.get(EMAIL), profile.get(NAME));
authenticationToken.setAuthenticated(false);
return getAuthenticationManager().authenticate(authenticationToken);
}
}
Spring RestTemplate showing this in logs:
o.s.web.client.RestTemplate : POST request for "https://accounts.google.com/o/oauth2/auth" resulted in 400 (Bad Request); invoking error handler
2014-09-05 21:51:46.870 WARN 5836 --- [ qtp25546756-15] o.eclipse.jetty.servlet.ServletHandler : /google_oauth2_login
This same piece of code works while using with XML configuration.
UPDATE 1
I was able to fix the 'Offline Access' problem by changing scope to 'https://www.googleapis.com/auth/plus.profile.emails.read' & 'https://www.googleapis.com/auth/plus.login'.
Still getting bad request error while trying to get user profile
Please find source code for the problem here -
git clone https://kumarsambhavjain#bitbucket.org/kumarsambhavjain/spring-oauth2-login.git
Have you tried change profile URL to
https://www.googleapis.com/plus/v1/people/me/openIdConnect
See more: https://developers.google.com/+/api/openidconnect/getOpenIdConnect
I used your code to create a OAuth2 Spring Boot sample, quite similar, and I had same issue once I replaced profile URL to:
https://www.googleapis.com/plus/v1/people/me/openIdConnect
I resolved it by enabling Google+ API in Google Console:
Visit the Google API Console here:https://code.google.com/apis/console/?api=plus
Under the Services panel, make sure the Google+ API is turned "on".
In the APIs console, click API Access in the left menu.
Copy the API key presented towards the bottom. Include this API key in your HTTP request.
This process is explained in this question:
How to call https://www.googleapis.com/plus/v1/people/me at google

Resources