How to fix 403 when using keycloak JWT token with spring security - spring

I have configured a Spring Boot project with Spring Security and Keycloak
I am getting the token from Keycloak server
When I am calling an endpoint in the app I am getting 403 with the token
Code as bellow:
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
class SecurityConfig {
#Bean
fun reactiveJwtDecoder() = NimbusReactiveJwtDecoder {
val claimsSet = it.jwtClaimsSet
println(Gson().toJson(claimsSet))
Mono.just(claimsSet)
}
#Bean
fun configure(
http: ServerHttpSecurity,
authenticationConverter: KeycloakReactiveJwtAuthenticationConverter
): SecurityWebFilterChain = http.apply {
headers().frameOptions().disable()
.and().csrf().disable()
.authorizeExchange()
.pathMatchers("/v/*").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(authenticationConverter)
}.build()
#Bean
fun clientRegistration(): ReactiveClientRegistrationRepository =
InMemoryReactiveClientRegistrationRepository(
ClientRegistrations.fromOidcIssuerLocation("http://localhost:8080/auth/realms/demo")
.clientId("demo")
.clientSecret("2de854c0-57a7-42f1-8a07-01773301646f")
.build()
)
}
#Component
class KeycloakReactiveJwtAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> {
override fun convert(jwt: Jwt): Mono<AbstractAuthenticationToken>? {
return Mono.just(AuthenticationToken(jwt))
}
}
fun convert(jwt: Jwt): Collection<GrantedAuthority>? {
#Suppress("UNCHECKED_CAST")
val realmAccess = jwt.claims["realm_access"] as? Map<String, List<String>> ?: emptyMap()
val roles = realmAccess.getOrDefault("roles", listOf()).map { "ROLE_$it" }
return AuthorityUtils.createAuthorityList(*roles.toTypedArray())
}
data class AuthenticationToken(
val jwt: Jwt
) : AbstractAuthenticationToken(convert(jwt)) {
override fun getCredentials(): Any = ClientInfo(
clientId = jwt.getClaimAsString("client_id")
)
override fun getPrincipal(): Any = ClientInfo(
clientId = jwt.getClaimAsString("client_id")
)
}
data class ClientInfo(
val clientId: String
)
Json that being passed
{"claims":{"sub":"ba95843a-4ba8-4247-b2b9-4db993de7371","resource_access":{"demo":{"roles":["uma_protection"]},"account":{"roles":["manage-account","manage-account-links","view-profile"]}},"clientId":"demo","email_verified":false,"clientHost":"127.0.0.1","iss":"http://localhost:8080/auth/realms/demo","typ":"Bearer","preferred_username":"service-account-demo","clientAddress":"127.0.0.1","client_id":"demo","aud":["account"],"acr":"1","realm_access":{"roles":["offline_access","uma_authorization","USER"]},"azp":"demo","scope":"profile email","exp":"Jan 27, 2021, 5:15:29 PM","iat":"Jan 27, 2021, 5:10:29 PM","jti":"51831211-7831-4bdd-9e31-d0ba2aa7f733"}}
Debug Log
2021-01-27 23:41:47.896 DEBUG 14484 --- [oundedElastic-1] o.s.w.s.s.DefaultWebSessionManager : Created new WebSession.
2021-01-27 23:41:47.899 DEBUG 14484 --- [oundedElastic-1] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=POST}
2021-01-27 23:41:47.900 DEBUG 14484 --- [oundedElastic-1] athPatternParserServerWebExchangeMatcher : Request 'GET /demo' doesn't match 'POST /logout'
2021-01-27 23:41:47.900 DEBUG 14484 --- [oundedElastic-1] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : No matches found
2021-01-27 23:41:47.901 DEBUG 14484 --- [oundedElastic-1] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/demo' using org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager#6d47f864
2021-01-27 23:41:47.903 DEBUG 14484 --- [oundedElastic-1] o.s.s.w.s.a.AuthorizationWebFilter : Authorization failed: Access Denied
2021-01-27 23:41:47.911 DEBUG 14484 --- [oundedElastic-1] o.s.w.s.adapter.HttpWebHandlerAdapter : [53903720-1] Completed 403 FORBIDDEN
What is wrong here?

https://stackoverflow.com/questions/61345957/spring-security-returns-403-with-valid-jwt
First answer here provided the solution
My exact code
class CustomJwtAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> {
private val jwtGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
override fun convert(source: Jwt): Mono<AbstractAuthenticationToken> {
val authorities = source.getClaimAsString("authorities")?.split(",")?.map { SimpleGrantedAuthority(it) }
return Mono.just(JwtAuthenticationToken(source, authorities.orEmpty()))
}
}

Related

Proccesing Filter does not set authentication

I am new to Spring Security. Now I'm trying to implement a custom filter, for a start I decided to simply create one filled UserPasswordAuthenticationToken object first and pass it to the provider, an object of the same class but with rights is simply created there. When requesting a protected resource, a redirect occurs with an error to the login endpoint and Anonymous is set in the SecurityContextHolder. I don’t know what to do, because apart from the fact that authentication failed, I don’t get anything to solve the problem
Log from server start to request for a protected resource
INFO 7454 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
INFO 7454 --- [ main] c.c.server.TestserveroauthApplication : Started
TestserveroauthApplication in 1.853 seconds (process running for 2.142)
INFO 7454 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 7454 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO 7454 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
DEBUG 7454 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Securing GET /
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /] with attributes [authenticated]
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.s.HttpSessionRequestCache : Saved request http://localhost:8080/?continue to session
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
DEBUG 7454 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : Securing GET /error
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to anonymous SecurityContext
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /error] with attributes [authenticated]
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.s.HttpSessionRequestCache : Saved request http://localhost:8080/error?continue to session
DEBUG 7454 --- [nio-8080-exec-2] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
My authentication filter
public class CustomAuthFilter extends AbstractAuthenticationProcessingFilter {
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
"GET");
public CustomAuthFilter() {
super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("testuser", "testpass");
setDetails(request, authRequest);
return getAuthenticationManager().authenticate(authRequest);
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
}
My authentication provider
public class CustomAuthProvider implements AuthenticationProvider{
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken authRequest = (UsernamePasswordAuthenticationToken) authentication;
if(authRequest.getCredentials() == null || authRequest.getPrincipal() == null) {
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
UsernamePasswordAuthenticationToken authResponse = new UsernamePasswordAuthenticationToken(
authRequest.getPrincipal(),
authRequest.getCredentials(),
grantedAuthorities
);
authResponse.setDetails(authRequest.getDetails());
return authResponse;
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
My configuration
#Configuration
public class SecurityConfig {
#Bean
public CodeFilter customFilter(){
CodeFilter authenticationFilter = new CodeFilter();
authenticationFilter.setAuthenticationManager(new SimpleAuthenticationManager(new CodeProvider()));
return authenticationFilter;
}
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
)
.addFilterBefore(customFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
My authentication manager (just a wrapper over the authentication provider)
public class SimpleAuthenticationManager implements AuthenticationManager{
private AuthenticationProvider authenticationProvider;
public SimpleAuthenticationManager(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return authenticationProvider.authenticate(authentication);
}
}

hasAuthoritiy() work on one place but not on another....Spring Security problem

#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/worker/login/").permitAll()
.and().authorizeRequests().antMatchers("/item/").hasAuthority("USER")
.and().authorizeRequests().antMatchers("/worker/getUserDetails/").hasAuthority("USER")
.anyRequest().authenticated().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().cors();
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
It works for path "/worker/getUserDetails/", it works only if user is logged. But after logging path "/item/ is always 403(forbidden). Here is my controller for getUserDetails:
#GetMapping("/getUserDetails")
public ResponseEntity<Worker> getUsername(#RequestHeader(value="Authorization") String jwtHeader){
String username=jwtUtil.extractUsername(jwtHeader.substring(7));
return ResponseEntity.ok(workerService.findByUsername(username));
}
And also controller for /item/ path:
#GetMapping("/")
public ResponseEntity<List<Item>> getAllItems(){
return new ResponseEntity<>(itemService.noSale(), HttpStatus.OK);
}
And MyUserDetials where i set actual role:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority(worker.getRole()));
}
Edit: Here is what security logs say2021-10-19 13:26:17.450 DEBUG 7288 --- [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Failed to authorize filter invocation [GET /item/noSale/] with attributes [hasAuthority('USER')] 2021-10-19 13:26:17.451 DEBUG 7288 --- [nio-8080-exec-5] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access 2021-10-19 13:26:17.453 DEBUG 7288 --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request 2021-10-19 13:26:17.460 DEBUG 7288 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Securing GET /error
On postman it is working, only on react side it doesn't work, so problem is with cors probably
#Bean
public CorsFilter corsFilter() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}

Failed to authenticate with NimbusJwtDecoder withJwkSetUri

I am trying to setup Resource Server to validate jwt tokens with Authentication server by using NimbusJwtDecoder.withJwkSetUri
Following is my configuration in Resource server
#Configuration
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.oauth2ResourceServer(c -> {
c.jwt(j -> {
j.decoder(jwtDecoder());
});
});
http.authorizeRequests().anyRequest().authenticated();
}
#Bean
public JwtDecoder jwtDecoder(){
RestTemplate rest = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = rest.getInterceptors();
interceptors.add(new BasicAuthenticationInterceptor("client1","secret1"));
interceptors.add(new LoggingInterceptor());
rest.setInterceptors(interceptors);
return NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/oauth/token_key").restOperations(rest).build();
}
}
And I have simple endpoint in Resource server to test
#RestController
public class HelloController {
#GetMapping("/hello")
public String hello(){
return "Hello";
}
}
However when I access "/hello" with access token already got from auth server then I get unauthorised response and observe following logs in Resource server.
Response body: {"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8ieQxTVHq4jBSM3JpO7UcFOa5UrorX5KhRbMqEtT746yGTqqv+t1EW6l8G31bGc6G/IHy7032vpKNxAgLVcoCrdoOakbGLb1y2+ElB9QmEEEplARWLQ43t47ywd0UA7MhF9WIbud1Z6kqySrsrBTzjPu+fwCElzUFveyaiPsZDlrEAU6yMLQ23nEP3bBCgDtGMVs1a7RsmAzfUsruelqNaAQQamobkjEMWB8ewZWjtsriIldNjGEAUznw4bcJ963ExtmgfMAHS7XhuWqu58yIzdBopxhZvt/falc5cyp7OCP1ZPEjkHJ5TikJksqOgDgWhiIVtr/3cUjd8vnX4y4QIDAQAB\n-----END PUBLIC KEY-----"}
2021-05-15 11:54:47.468 DEBUG 40223 --- [nio-9090-exec-3] o.s.web.client.RestTemplate : Response 200 OK
2021-05-15 11:54:47.468 DEBUG 40223 --- [nio-9090-exec-3] o.s.s.o.s.r.a.JwtAuthenticationProvider : Failed to authenticate since the JWT was invalid
2021-05-15 11:54:47.469 DEBUG 40223 --- [nio-9090-exec-3] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2021-05-15 11:54:47.469 DEBUG 40223 --- [nio-9090-exec-3] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
It seems auth server was able to provide public key successfully but resource server could not use this public key to validate provided jwt token.
Any help is highly appreciated.
Hi please add class #EnableAuthorizationServer anotation but not working please read this Outh2 documentation https://projects.spring.io/spring-security-oauth/docs/oauth2.html

Problem calling a "bearer-only" keycloak endpoint from a springboot (client app) to a also spring boot (bearer only app)

Basically I'm trying to access a bearer-only endpoint from a client app which is using a "KeycloakRestTemplate".
I did follow this guidelines 1:1 (it is in German) : https://blog.codecentric.de/2017/09/keycloak-und-spring-security-teil-1-einrichtung-frontend/
My problem is that when I see the logs, the authentication on the side of the bearer only endpoint seems successful, as shown bellow:
Found [1] values in authorization header, selecting the first value for Bearer.
o.k.a.BearerTokenRequestAuthenticator : Verifying access_token
o.k.a.BearerTokenRequestAuthenticator : access_token: [LONG TOKEN HERE]
o.k.a.RefreshableKeycloakSecurityContext : checking whether to refresh.
org.keycloak.adapters.AdapterUtils : use realm role mappings
org.keycloak.adapters.AdapterUtils : Setting roles:
org.keycloak.adapters.AdapterUtils : role: create_vouchers
org.keycloak.adapters.AdapterUtils : role: public_realm_access
org.keycloak.adapters.AdapterUtils : role: overview_orders
org.keycloak.adapters.AdapterUtils : role: uma_authorization
User 'c1500da2-855f-4306-ab65-662160558101' invoking 'http://localhost:8082/articles' on client 'articlesBearerOnlyService'
o.k.adapters.RequestAuthenticator : Bearer AUTHENTICATED
.k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /articles
o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8082/articles
cors validation not needed as were not a secure session or origin header was null: {0}
o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
but then directly afterwards on the logs comes this:
o.k.adapters.PreAuthActionsHandler : adminRequest http://localhost:8082/login
o.k.adapters.PreAuthActionsHandler : checkCorsPreflight http://localhost:8082/login
.k.a.t.AbstractAuthenticatedActionsValve : AuthenticatedActionsValve.invoke /login
o.k.a.AuthenticatedActionsHandler : AuthenticatedActionsValve.invoke http://localhost:8082/login
o.k.a.AuthenticatedActionsHandler : Origin: null uri: http://localhost:8082/login
o.k.a.AuthenticatedActionsHandler : cors validation not needed as were not a secure session or origin header was null: {0}
o.k.a.AuthenticatedActionsHandler : Policy enforcement is disabled.
so, it tries to redirect to adminRequest http://localhost:8082/login? why, and how could this be solved?
I did also also tried with postman (getting the acces-token from the token end-point) and pasting it on the Authorization header of this "bearer-only" endpoint, and similarly by seeing the logs, the user seems authorized exacltly like in the first log block above, the diference is that is doesn't try to redirect anywhere but I receive a 401.
{
"timestamp": "2019-09-05T11:18:51.347+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/articles" }
Could somebody please provide some guidance into a possible solution?
Thanks in advance!
----------------------------------------
EDITED
----------------------------------------
here is the application properties file:
server.port = 8082
spring.application.name = articleBearerOnlyService
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=[REALM]
keycloak.resource=articlesBearerOnlyService
keycloak.bearer-only=true
keycloak.cors=true
keycloak.credentials.secret=[SECRET]
keycloak.ssl-required = external
# access controlled through spring security
#keycloak.security-constraints[0].auth-roles[0]=overview_orders
#keycloak.security-constraints[0].security-collections[0].patterns[0]=/articles
logging.level.org.keycloak=TRACE
and here the SecurityConfig :
#KeycloakConfiguration
#EnableWebSecurity
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final KeycloakClientRequestFactory keycloakClientRequestFactory;
public SecurityConfig(KeycloakClientRequestFactory keycloakClientRequestFactory) {
this.keycloakClientRequestFactory = keycloakClientRequestFactory;
//to use principal and authentication together with #async
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
/* remove default spring "ROLE_" prefix appending to keycloak's roles*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
// NullAuthenticatedSessionStrategy() for bearer-only services
return new NullAuthenticatedSessionStrategy();
}
/* configure cors & requests handling behaviour*/
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors()
.and()
.csrf()
.disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.and()
.authorizeRequests()
.antMatchers("/articles").hasRole("overview_orders")
.anyRequest().permitAll();
}
// Spring boot integration
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
// *************************** Avoid Bean redefinition ********************************
#Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
#Override
#ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
}
The #SpringBootApplication annotation is a composite of these three annotations: #EnableAutoConfiguration, #ComponentScan and #Configuration. Annotating a class e.g. com.example.demo.DemoApplication with #SpringBootApplication, results in Spring looking for other components, configurations, and services inside com.example.demo and all of its sub-packages.
A class like com.example.config.DemoConfig therefore cannot be found by Spring automatically. If you want, you can give hints to Spring where to look for components via #ComponentScan(basePackages = "com.some.package"). Check out this article if you like to know more.
In this particular case, my #KeycloakConfiguration class SecurityConfig{...}, was completely ignored, and thus the application behaved as if none security config was provided at all.
Now, why was the SecurityConfig ignored?
- it turned out to be (I almost feel shame) path location of the class; I usually would place such a class under:
com.[company].[domain].configuration
In my case (since I'm only prototyping with keycloak + spring and not particularly concerned with class location right now). I did place my SecurityConfig class under:
com.[company].configuration
This made spring boot completely ignore this class.
Follow up question: I'm new to Sprint boot, is it 100% necessary to place all code under "com.[company].[domain].configuration", without modifying the pom (just having a newly created vanilla springboot project via the initializr)?

Access issue with restTemplatebuider

I use spring boot and spring security.
In my rest controller, i have one method
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
#EnableWebSecurity
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/rest/**").authenticated();
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutUrl("/logout");
http.logout().logoutSuccessUrl("/");
// CSRF tokens handling
//http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
}
#RequestMapping(value = "/rest")
#RestController
public class MemberController {
#GetMapping(value = "/members/card")
public boolean hasCardIdValid(#RequestBody String cardId) {
return memberService.hasCardIdValid(cardId);
}
}
In another spring boot application, i try to call hasCreditCard method
#Autowired
public GlobalScan(RestTemplateBuilder restTemplateBuilder, #Value("${main.server.url}") String mainServerUrl, #Value("${commerce.username}") String commerceUsername, #Value("${commerce.password}")String commercePassword) {
this.restTemplate = restTemplateBuilder.basicAuthorization(commerceUsername, commercePassword).rootUri(mainServerUrl).build();
}
I do a call with this code
Map<String, String> vars = new HashMap<String, String>();
vars.put("cardId", cardId);
boolean accessAllowed = restTemplate.getForObject("/rest/members/card/" , Boolean.class, vars);
i get this message
2016-11-02 16:20:50.601 DEBUG 7139 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/rest/members/card/'; against '/login'
2016-11-02 16:20:50.601 DEBUG 7139 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/rest/members/card/'; against '/rest/**'
2016-11-02 16:20:50.601 DEBUG 7139 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /rest/members/card/; Attributes: [authenticated]
2016-11-02 16:20:50.601 DEBUG 7139 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2016-11-02 16:20:50.602 DEBUG 7139 --- [nio-8080-exec-1] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#3d300693, returned: -1
2016-11-02 16:20:50.602 TRACE 7139 --- [nio-8080-exec-1] ationConfigEmbeddedWebApplicationContext : Publishing event in org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext#2bdd8394: org.springframework.security.access.event.AuthorizationFailureEvent[source=FilterInvocation: URL: /rest/members/card/]
2016-11-02 16:20:50.606 DEBUG 7139 --- [nio-8080-exec-1] 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.1.1.RELEASE.jar:4.1.1.RELEASE]
On my main app, i use a form login to connect to the app, like you can see in the spring security config.
From my other app how to call a ws without form login?
tried to call ws with this
final RequestConfig config = RequestConfig.custom().setConnectTimeout(timeout * 1000).setConnectionRequestTimeout(timeout * 1000).setSocketTimeout(timeout * 1000).build();
final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope("http://localhost", 8080, AuthScope.ANY_REALM), new UsernamePasswordCredentials("bob", "smith"));
final CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(config).setDefaultCredentialsProvider(credentialsProvider).build();
final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ResponseEntity<MemberDto> member = restTemplate.getForEntity("http://localhost:8080/rest/members/1", MemberDto.class);
result: http://pastebin.com/psNKPUtM
The default password in spring security is configured by the following property: security.user.password=YOUR_PASSWORD
This should be done in your main app where you have security configuration and which you are trying to call.
You can change the password by providing a security.user.password.
This and other useful properties are externalized via
SecurityProperties (properties prefix "security").
So, if you didn't update the property to match the password in commerce.password spring will reject your authorization and you will get 401. By default it uses some random generated password it prints to the console during the start. documentation
You are configuring formLogin() but you try to use an http Basic Auth in your RestTemplate.
For requests via http REST I suggest that you change your configuration to use basic auth:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/rest/**").authenticated();
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.httpBasic();
http.logout().logoutUrl("/logout");
http.logout().logoutSuccessUrl("/");
// CSRF tokens handling
//http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
If you need both I think you can configure both.
Add BASIC auth to your existing configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http
....
.and()
.formLogin() // <------ Keep this
....
.and()
.httpBasic() // <------ Add BASIC Auth
.and()
.....;
}
Write a simple client using RestTemplate
public static void main(String[] args) {
RestTemplate rest = new RestTemplate(new ArrayList(Arrays.asList(new MappingJackson2HttpMessageConverter())));
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic YOUR_BASE64_ENCODED_CREDENTIALS");
MediaType applicationJson = new MediaType("application","json");
headers.setContentType(applicationJson);
headers.setAccept(Collections.singletonList(applicationJson));
ResponseEntity<YourResponseObject> resp = rest.exchange("http://URL/rest/yourendpoint", HttpMethod.GET, new HttpEntity<String>("parameters", headers), YourResponseObject.class);
System.out.println(resp.getBody());
}
YOUR_BASE64_ENCODED_CREDENTIALS => If use use Java 8 you can use java.util.Base64, otherwise use commons-codec to do that or something else.
Update:
Spring boot reference: http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#jc-httpsecurity

Resources