I try to setup an OAuth2RestTemplate in a spring boot project. I tried to follow a custom setup, but for some reason I always get authorization_request_not_found error when trying to access a secured resource.
My AppConfig:
#EnableOAuth2Client
#SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
#Bean
RestTemplate restTemplate(OAuth2ProtectedResourceDetails oauth2RemoteResource) {
return new OAuth2RestTemplate(oauth2RemoteResource);
}
}
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/", "/login/**").permitAll()
.mvcMatchers("/secure").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login()
.and()
.oauth2Client();
}
}
Controller:
#GetMapping("/secure")
public String secure() {
Object result = restTemplate.getForObject("https://esi.evetech.net/latest/alliances", Object.class);
return "/";
}
application.yml:
security:
oauth2:
client:
id: eve
client-id: clientId
client-secret: secret
grant-type: authorization_code
user-authorization-uri: https://login.eveonline.com/v2/oauth/authorize
access-token-uri: https://login.eveonline.com/v2/oauth/token
pre-established-redirect-uri: http://localhost:8080/login/oauth2/code/eve
use-current-uri: false
scope:
- esi-characters.read_blueprints.v1
resource:
user-info-uri: https://login.eveonline.com/oauth/verify
spring:
security:
oauth2:
client:
registration:
eve:
authorization-grant-type: authorization_code
client-id: clientId
client-secret: secret
redirect-uri: http://localhost:8080/login/oauth2/code/eve
client-authentication-method: basic
scope:
- esi-characters.read_blueprints.v1
provider:
eve:
authorization-uri: https://login.eveonline.com/v2/oauth/authorize
token-uri: https://login.eveonline.com/v2/oauth/token
user-info-uri: https://login.eveonline.com/oauth/verify
user-name-attribute: CharacterID
Now I find this also interesting since I am not sure why I have to declare these properties twice.
I mean the OAuth2RestTemplate takes an AuthorizationCodeResourceDetails instance in the constructor in AppConfig which reads the security.oatuh2.client properties, while the DefaultOAuth2ClientContext inside the OAuth2RestTemplate constructor reads the other, I just don't understand why is it like that? Is there a simple setup?
Anyway going back to the original problem, when I access /secure it redirects me to the login page, where I login successfully which then redirects me back to the /secure page, so far so good.
Just before calling the restTemplate, I have this in the log:
2020-05-06 22:55:31.528 DEBUG 29177 --- [nio-8080-exec-4] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /secure; Attributes: [authenticated]
2020-05-06 22:55:31.528 DEBUG 29177 --- [nio-8080-exec-4] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken#3bb07c9: Principal: Name: [<ID>], Granted Authorities: [[ROLE_USER, SCOPE_esi-characters.read_blueprints.v1]], User Attributes: [{CharacterID=<ID>, CharacterName=<Name>, ExpiresOn=2020-05-06T22:15:31, Scopes=esi-characters.read_blueprints.v1, TokenType=Character, CharacterOwnerHash=<Hash>, IntellectualProperty=EVE}]; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 472A39D41FE34AC90781A80E39E4A52D; Granted Authorities: ROLE_USER, SCOPE_esi-characters.read_blueprints.v1
2020-05-06 22:55:31.529 DEBUG 29177 --- [nio-8080-exec-4] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#47a65378, returned: 1
But after the restTemplate is called:
2020-05-06 23:00:31.482 DEBUG 29177 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval
2020-05-06 23:00:31.483 DEBUG 29177 --- [nio-8080-exec-4] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#205366de
2020-05-06 23:00:31.483 DEBUG 29177 --- [nio-8080-exec-4] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2020-05-06 23:00:31.484 DEBUG 29177 --- [nio-8080-exec-4] o.s.s.web.DefaultRedirectStrategy : Redirecting to 'https://login.eveonline.com/v2/oauth/authorize?client_id=<clientId>&redirect_uri=http://localhost:8080/login/oauth2/code/eve&response_type=code&scope=esi-characters.read_blueprints.v1&state=<state>'
As you can see it takes me back to the login page, so I login back again and this time I get the error: [authorization_request_not_found]
log:
2020-05-06 23:02:27.285 DEBUG 29177 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy : /login/oauth2/code/eve?code=<code>&state=<state> at position 8 of 17 in additional filter chain; firing Filter: 'OAuth2LoginAuthenticationFilter'
2020-05-06 23:02:27.285 DEBUG 29177 --- [nio-8080-exec-6] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login/oauth2/code/eve'; against '/login/oauth2/code/*'
2020-05-06 23:02:27.285 DEBUG 29177 --- [nio-8080-exec-6] .s.o.c.w.OAuth2LoginAuthenticationFilter : Request is to process authentication
2020-05-06 23:02:27.287 DEBUG 29177 --- [nio-8080-exec-6] .s.o.c.w.OAuth2LoginAuthenticationFilter : Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [authorization_request_not_found]
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [authorization_request_not_found]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:163) ~[spring-security-oauth2-client-5.2.2.RELEASE.jar:5.2.2.RELEASE]
I am lost at this point, not sure why I am getting this.
I also uploaded the project to github in case it's easier to check out what's wrong with it.
Thanks
Related
App stack: Spring boot / Spring Doc / Keycloak
I am trying to integrate the above stack together, everything works well till I am using keycloak policy enforcement.
At the bringing the app is running and I can access the Swagger UI served thanks to spring doc, but after like 20 or 30 seconds, I got HTTP ERROR 401 while I am trying to access the exact link of swagger and keycloak refuse all access to any URL or rest API
Console Error:
2022-05-22 23:31:55.650 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.650 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : checkCorsPreflight http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] .k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler : Origin: null uri: http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.AuthenticatedActionsHandler : cors validation not needed as we are not a secure session or origin header was null: http://localhost:8080/swagger-ui/index.html
2022-05-22 23:31:55.651 DEBUG 92263 --- [io-8080-exec-10] o.k.a.authorization.PolicyEnforcer : Policy enforcement is enabled. Enforcing policy decisions for path [http://localhost:8080/swagger-ui/index.html].
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.a.authorization.PolicyEnforcer : Policy enforcement result for path [http://localhost:8080/swagger-ui/index.html] is : DENIED
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.a.authorization.PolicyEnforcer : Returning authorization context with permissions:
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : Securing GET /error
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8080/error
2022-05-22 23:31:55.917 DEBUG 92263 --- [io-8080-exec-10] o.k.adapters.PreAuthActionsHandler : checkCorsPreflight http://localhost:8080/error
2022-05-22 23:31:55.917 TRACE 92263 --- [io-8080-exec-10] f.KeycloakAuthenticationProcessingFilter : Did not match request to Or [Ant [pattern='/sso/login'], RequestHeaderRequestMatcher [expectedHeaderName=Authorization, expectedHeaderValue=null], org.keycloak.adapters.springsecurity.filter.QueryParamPresenceRequestMatcher#1eab2ee7, org.keycloak.adapters.springsecurity.filter.AdapterStateCookieRequestMatcher#2ad76210]
2022-05-22 23:31:55.918 DEBUG 92263 --- [io-8080-exec-10] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2022-05-22 23:31:55.918 DEBUG 92263 --- [io-8080-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /error] with attributes [authenticated]
2022-05-22 23:31:55.918 DEBUG 92263 --- [io-8080-exec-10] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
My policy enforcement to swagger links here is my keycloak YAML file
keycloak:
realm: phelix
auth-server-url: https://keycloak-server-url/auth
ssl-required: none
resource: orders
use-resource-role-mappings: true
bearer-only: true
cors-exposed-headers: X-Total-Count
cors: true
principal-attribute: preferred_username
credentials:
secret: 111-111-111.....
policy-enforcer-config:
enforcement-mode: ENFORCING
lazy-load-paths: true
paths:
- path: /swagger-ui/*.html
enforcement-mode: DISABLED
- path: /actuator/*
enforcement-mode: DISABLED
- path: /swagger-ui/*
enforcement-mode: DISABLED
- path: /swagger-ui.html
enforcement-mode: DISABLED
- path: /v3/api-docs/*
enforcement-mode: DISABLED
Security config class which extends keycloak adapter
#KeycloakConfiguration
#ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "true", matchIfMissing = true)
#ConfigurationProperties(prefix = "keycloak")
#PropertySource(value = "classpath:keycloak-configs.yml", factory = YamlPropertySourceFactory.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private static final String[] WHITELIST_URLS = {
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/actuator/**",
};
public KeycloakClientRequestFactory keycloakClientRequestFactory;
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable()
.authorizeRequests()
.antMatchers(WHITELIST_URLS).permitAll()
.anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setPrefix("");
authenticationProvider.setGrantedAuthoritiesMapper(mapper);
auth.authenticationProvider(authenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy(); //for bearer-only services
}
#Bean
public KeycloakDeployment keycloakDeploymentBuilder(KeycloakSpringBootProperties configuration) {
return KeycloakDeploymentBuilder.build(configuration);
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public KeycloakRestTemplate keycloakRestTemplate() {
return new KeycloakRestTemplate(keycloakClientRequestFactory);
}
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public AccessToken getAccessToken() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication.getPrincipal() instanceof KeycloakPrincipal) {
return ((KeycloakPrincipal) authentication.getPrincipal()).getKeycloakSecurityContext().getToken();
} else {
return new AccessToken();
}
}
}
Dependencies:
'springboot': '2.7.0'
'keycloakVersion': '16.1.0'
'springdoc-openapi-ui': '1.6.8'
Any thoughts ?
I'm trying to make an authenticated GET request on one of the resources:
http://user:psw#localhost:8090/devices
This works fine from the browser. But from National Instrument GWeb I keep getting Code 401 (Unauthorized).
SecurityConfiguration.java:
#Configuration
#EnableWebSecurity
class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final DatabaseUserDetailsService databaseUserDetailsService;
public SecurityConfiguration(DatabaseUserDetailsService databaseUserDetailsService) {
super();
this.databaseUserDetailsService = databaseUserDetailsService;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Bean
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(this.databaseUserDetailsService);
return provider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://rog-valerio", "http://localhost:8090"));
configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
From the configure() method:
httpSecurity.cors().and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
I'm I am not wrong this should mean that any request should be able to authenticate.
By enabling spring security debug, when I try to make the authenticated request I get the following:
2022-03-09 10:37:00.520 DEBUG 27408 --- [nio-8090-exec-5] o.s.security.web.FilterChainProxy : Securing GET /devices
2022-03-09 10:37:00.520 DEBUG 27408 --- [nio-8090-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-03-09 10:37:00.521 DEBUG 27408 --- [nio-8090-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2022-03-09 10:37:00.521 DEBUG 27408 --- [nio-8090-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /devices] with attributes [authenticated]
2022-03-09 10:37:00.522 DEBUG 27408 --- [nio-8090-exec-5] o.s.s.w.s.HttpSessionRequestCache : Saved request http://localhost:8090/devices to session
2022-03-09 10:37:00.523 DEBUG 27408 --- [nio-8090-exec-5] s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2022-03-09 10:37:00.523 DEBUG 27408 --- [nio-8090-exec-5] s.w.a.DelegatingAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint#30dfc62d
2022-03-09 10:37:00.523 DEBUG 27408 --- [nio-8090-exec-5] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-03-09 10:37:00.523 DEBUG 27408 --- [nio-8090-exec-5] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-03-09 10:37:00.523 DEBUG 27408 --- [nio-8090-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2022-03-09 10:37:00.523 DEBUG 27408 --- [nio-8090-exec-5] o.s.security.web.FilterChainProxy : Securing GET /error
2022-03-09 10:37:00.524 DEBUG 27408 --- [nio-8090-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-03-09 10:37:00.524 DEBUG 27408 --- [nio-8090-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
2022-03-09 10:37:00.524 DEBUG 27408 --- [nio-8090-exec-5] o.s.security.web.FilterChainProxy : Secured GET /error
2022-03-09 10:37:00.525 DEBUG 27408 --- [nio-8090-exec-5] a.DefaultWebInvocationPrivilegeEvaluator : filter invocation [/error] denied for AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=05282221D24CA222616679CE3049C092], Granted Authorities=[ROLE_ANONYMOUS]]
org.springframework.security.access.AccessDeniedException: Access is denied
And access is denied. Username and password are correct. Why am I getting the request rejected? Maybe there is some configuration that I am missing?
I found the answer, configuration was fine. But, as stated here https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization#examples , I added "Authorization" header with base64 encoded username and password. Now it works.
I'll not delete the question because maybe it'll be useful to somebody
I made a simple rest service and I wanted to add a simple security on the api.
So I created an WebSecurityConfigurerAdapter :
package org.test.subscription.webservice.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("test1").password("test1").roles("superAdminRole").and()
.withUser("test2").password("test2").roles("superAdminRole");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest().hasRole("webserviceReadRole").and()
.csrf().disable();
}
}
And this is my main runner:
package org.test.subscription.webservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
But when I call something inside my API,I'm not authenticate at all, and I get my "hello world" response. So he never check my role. Any ideas why?
Your example should work as you expect it (tested in boot 1.5.7 and also 2.0.0 M3). So I would recommend that you try the following. First verify some trivial things:
That you do not have anything that disables security checks in your application.properties or the corresponding yaml (e.g. something like security.ignored and Co.).
That you do not have another WebSecurityConfigurerAdapter in your application.
That your classes are scanned correctly by Spring.
That you do not have some strange cookies in the browser. E.g. try the same request by starting the browser in private mode or use curl or something similar.
If it still does not work enable the Spring Security debugger for a deeper insight on what is going on under the hood and why you see unexpected behavior. This may be achieved in the following way:
#EnableWebSecurity(debug = true)
This will hint Spring to print lots of extra details which will help you find out what is wrong. In your setup you should see something like the following in the logs when you issue an request.
First, the request itself with the proper headers. Example with the most important parts:
Request received for GET '/path/to/your/api':
org.apache.catalina.connector.RequestFacade#58a4ad1c
authorization: Basic dGVzdDE6dGVzdDE=
cookie: JSESSIONID=9E4EBB889BB178E05446104EF2787C2F
Then you will see the filter chain managed by the FilterChainProxy and matched with your request (note that there might be other filter chains depending on the setup of your app - the log shows the matched chain which may not be the one that you expect):
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
Then lots of DEBUG messages will follow in the log. Please pay special attention to the messages created around the BasicAuthenticationFilter:
2017-10-07 14:42:21.644 DEBUG 56071 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : /pat/to/your/api at position 6 of 12 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2017-10-07 14:42:21.645 DEBUG 56071 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'test1'
2017-10-07 14:42:21.645 DEBUG 56071 --- [nio-8080-exec-2] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2017-10-07 14:42:21.647 DEBUG 56071 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication success: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#8fc16c08: Principal: org.springframework.security.core.userdetails.User#6924ddf: Username: test1; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_superAdminRole; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 9E4EBB889BB178E05446104EF2787C2F; Granted Authorities: ROLE_superAdminRole
2
And also to the FilterSecurityInterceptor which should output the successful authorization message:
2017-10-07 14:42:21.649 DEBUG 56071 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#8fc16c08: Principal: org.springframework.security.core.userdetails.User#6924ddf: Username: test1; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_superAdminRole; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 9E4EBB889BB178E05446104EF2787C2F; Granted Authorities: ROLE_superAdminRole
2017-10-07 14:42:21.649 DEBUG 56071 --- [nio-8080-exec-2] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#1ca8b2d, returned: 1
2017-10-07 14:42:21.649 DEBUG 56071 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
Armed with all these details approach SO further :-)
I am trying to use spring boot oauth2 to accomplish stateless authentication and authorisation. However, I am struggling to it working.
Here is my code:
#EnableAutoConfiguration
#ComponentScan
//#EnableEurekaClient
//#EnableZuulProxy
#Configuration
public class AuthServiceApp {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApp.class, args);
}
}
Authorisation Config:
#Configuration
#EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager auth;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailService;
#Autowired
private ClientDetailsService clientDetailsService;
#Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// #OFF
endpoints
.authorizationCodeServices(authorizationCodeServices())
.authenticationManager(auth)
.userDetailsService(userDetailService)
.tokenStore(tokenStore());
// #ON
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #OFF
clients.jdbc(dataSource)
.withClient("client")
.secret("secret")
.authorizedGrantTypes("password","refresh_token", "client_credentials")
.authorities("USER")
.scopes("read", "write")
.autoApprove(true)
.accessTokenValiditySeconds(60)
.refreshTokenValiditySeconds(300);
// #ON
}
}
Resource Server Config:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
// #OFF
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
// .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/identity/**").authenticated();
// #ON
}
}
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #OFF
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
// #ON
}
}
Controller:
#RestController
#RequestMapping("/")
public class AuthController {
#PreAuthorize("#oauth2.hasScope('read')")
#GetMapping("/user")
public Principal getUser(Principal user) {
return user;
}
}
I can get the access token using POSTMAN. I am using the same access token in the header to get the user details as http://localhost:8082/identity/user before it gets expired. However, I get login page html response with following log on console:
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 301C6EDD36372CF9C553FCFCD4AA47E3; Granted Authorities: ROLE_ANONYMOUS'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.security.web.FilterChainProxy : /user at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.security.web.FilterChainProxy : /user at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.security.web.FilterChainProxy : /user at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/login'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /user; Attributes: [authenticated]
2017-05-24 22:55:16.071 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 301C6EDD36372CF9C553FCFCD4AA47E3; Granted Authorities: ROLE_ANONYMOUS
2017-05-24 22:55:16.071 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#55b4f25d, returned: -1
2017-05-24 22:55:16.071 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.2.2.RELEASE.jar:4.2.2.RELEASE]
But it seems like I have been authenticated when making first call to get the access token to oauth/token:
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated]
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#50c8f5e8: Principal: org.springframework.security.core.userdetails.User#af12f3cb: Username: client; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#21a2c: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 2F070B741A55BD1E47933621D9127780; Granted Authorities: USER
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#61f8721f, returned: 1
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.security.web.FilterChainProxy : /oauth/token reached end of additional filter chain; proceeding with original chain
2017-05-24 22:54:35.967 DEBUG 16899 --- [nio-8082-exec-6] .s.o.p.e.FrameworkEndpointHandlerMapping : Looking up handler method for path /oauth/token
2017-05-24 22:54:35.968 DEBUG 16899 --- [nio-8082-exec-6] .s.o.p.e.FrameworkEndpointHandlerMapping : Returning handler method [public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException]
2017-05-24 22:54:35.975 DEBUG 16899 --- [nio-8082-exec-6] .o.p.p.ResourceOwnerPasswordTokenGranter : Getting access token for: client
2017-05-24 22:54:35.975 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
Hibernate: select user0_.id as id1_1_, user0_.enabled as enabled2_1_, user0_.name as name3_1_, user0_.password as password4_1_, user0_.username as username5_1_ from user user0_ where user0_.username=?
Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.role_id as role_id2_2_0_, role1_.id as id1_0_1_, role1_.role as role2_0_1_ from user_role roles0_ inner join role role1_ on roles0_.role_id=role1_.id where roles0_.user_id=?
2017-05-24 22:54:36.125 INFO 16899 --- [nio-8082-exec-6] o.s.s.o.p.token.store.JdbcTokenStore : Failed to find access token for token 180c2528-b712-4088-9cce-71e9cc7ccb94
2017-05-24 22:54:36.232 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2017-05-24 22:54:36.232 DEBUG 16899 --- [nio-8082-exec-6] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
May be I am configuring something wrong. What am I missing here?
I had a similar issue and found that the OAuth2AuthenticationProcessingFilter was not getting invoked by the filter chain and because of which, user was not getting authenticated and hence treated as anonymous.
Am using Spring-boot 1.5.3 version and I added below line in application.yml to fix the ordering.
security.oauth2.resource.filter-order=3
There must be a log statement present which shows that its getting invoked
DEBUG 34386 --- [nio-8082-exec-1] o.s.security.web.FilterChainProxy : /foo at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
Reference - https://github.com/spring-projects/spring-security-oauth/issues/993
To use
.authorizedGrantTypes("password","refresh_token", "client_credentials")
you need to open access to
/auth/token
in security configuration of your Authorization Server
#Override
protected void configure(HttpSecurity http) throws Exception {
code...
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.anyRequest().authenticated()
code...
}
I was facing the same problem and managed to fix it by explicitly disabling some spring autoconfiguration:
#EnableAutoConfiguration(exclude = [OAuth2AutoConfiguration::class,
SecurityAutoConfiguration::class, SecurityFilterAutoConfiguration::class])
Also check if you are using multiple OAuth2 filters coming from multiple auth-related dependices/libraries.
In my case I was using
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
Along with another custom OAuth2 library used in my organization, which caused the spring library to attempt to authenticate before the custom (correct) one, and with no credentials fed to it of course throwing this exact exception.
My service calls worked once I removed the Spring OAuth2 dependency.
I have a Spring Boot app with POST endpoint, #PreAuthorize role definition and a custom validator for the request body:
#PreAuthorize("hasAnyRole('ROLE_OTHER')")
#RequestMapping(value = "post", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public String post(#RequestBody #Valid DummyDTO dummyDTO) {
...
}
1) When I call the endpoint with wrong role and non-valid request body the custom validation code i.e. ConstraintValidator.isValid() is executed and I got 400 Bad Request.
2) When I call the endpoint with wrong role and valid request body the custom validation code i.e. ConstraintValidator.isValid() is executed and I got 403 Forbidden.
Why is the custom validation code executed before the #PreAuthorize check? I would expect to get 403 in both cases.
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor : Successfully Authenticated: org.springframework.security.authentication.TestingAuthenticationToken#bbd5887f: Principal: key; Credentials: [PROTECTED]; Authenticated: false; Details: null; Granted Authorities: ROLE_USER
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#41ba74ef, returned: 1
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
DEBUG 12776 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : /post reached end of additional filter chain; proceeding with original chain
INFO 12776 --- [nio-8080-exec-3] com.example.DummyValidator : > isValid(): [valid]
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.a.i.a.MethodSecurityInterceptor : Secure object: ReflectiveMethodInvocation: public java.lang.String com.example.MyController.post(com.example.DummyDTO); target is of class [com.example.MyController]; Attributes: [[authorize: 'hasAnyRole('ROLE_OTHER')', filter: 'null', filterTarget: 'null']]
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.TestingAuthenticationProvider
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.a.i.a.MethodSecurityInterceptor : Successfully Authenticated: org.springframework.security.authentication.TestingAuthenticationToken#bbd5887f: Principal: key; Credentials: [PROTECTED]; Authenticated: false; Details: null; Granted Authorities: ROLE_USER
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#356a19a2, returned: -1
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter#7290b3e0, returned: 0
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter#16f3e525, returned: 0
DEBUG 12776 --- [nio-8080-exec-3] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is not anonymous); delegating to AccessDeniedHandler
org.springframework.security.access.AccessDeniedException: Access is denied
...
My DTO:
public class DummyDTO {
#DummyConstraint
private String name;
...
}
constraint:
#Documented
#Retention(RUNTIME)
#Target({TYPE, FIELD, PARAMETER, ANNOTATION_TYPE})
#Constraint(validatedBy = {DummyValidator.class})
public #interface DummyConstraint {
...
}
and validator:
public class DummyValidator implements ConstraintValidator<DummyConstraint, String> {
private static final Logger LOGGER = LoggerFactory.getLogger(DummyValidator.class);
#Override
public void initialize(DummyConstraint constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
LOGGER.info("> isValid(): [{}]", value);
return "valid".equalsIgnoreCase(value);
}
}