Different credentials in Spring Boot for app authentication and management authentication? - spring

I want to use http basic authentication for my Spring Boot application with one set of credentials and at the same time I want to configure actuator to use a different set of credentials for the management resources (health, env etc). I've read the Actucator documentation where it says that you should be able to set the username and password using the security.user.name and security.user.password properties. However when I add my custom WebSecurityConfigurerAdapter it no longer seems to be applied. My WebSecurityConfigurerAdapter looks like this:
#Configuration
#EnableWebMvcSecurity
#Order(Ordered.LOWEST_PRECEDENCE - 11)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String API_USER = "API";
private static final String ADMIN_USER = "ADMIN";
#NotNull
#Value("${security.user.name}")
private String managementUsername;
#NotNull
#Value("${security.user.password}")
private String managementPassword;
#NotNull
#Value("${management.context-path}")
private String managementContextPath;
public ApplicationSecurityConfig() {
super(true);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.securityContext().and()
.requestCache().and()
.servletApi().and()
.authorizeRequests()
.antMatchers(managementContextPath+"/**").hasRole(ADMIN_USER)
.antMatchers("/**").hasRole(API_USER)
.and()
.httpBasic();
// #formatter:on
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("apiUsername").password("apiPassword").roles(API_USER).
and().withUser(managementUsername).password(managementPassword).roles(ADMIN_USER);
}
}
I've also tried setting management.security.enabled to false but then the management resources seem to be open to all despite my effort to protect it above.
Does anyone know what I'm doing wrong and how to go about?
Update
I see that three events are emitted by Spring from my app:
2015-06-10 20:04:37.076 INFO 44081 --- [nio-8083-exec-1] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Wed Jun 10 20:04:37 CEST 2015, principal=<unknown>, type=AUTHENTICATION_FAILURE, data={type=org.springframework.security.authentication.AuthenticationCredentialsNotFoundException, message=An Authentication object was not found in the SecurityContext}]
2015-06-10 20:04:39.564 INFO 44081 --- [nio-8083-exec-2] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Wed Jun 10 20:04:39 CEST 2015, principal=admin, type=AUTHENTICATION_SUCCESS, data={details=org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null}]
2015-06-10 20:04:39.569 INFO 44081 --- [nio-8083-exec-2] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Wed Jun 10 20:04:39 CEST 2015, principal=admin, type=AUTHORIZATION_FAILURE, data={type=org.springframework.security.access.AccessDeniedException, message=Access is denied}]
But there's only two from hyness sample app:
2015-06-10 19:34:10.851 INFO 42714 --- [nio-8083-exec-1] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Wed Jun 10 19:34:10 CEST 2015, principal=anonymousUser, type=AUTHORIZATION_FAILURE, data={type=org.springframework.security.access.AccessDeniedException, message=Access is denied}]
2015-06-10 19:34:17.139 INFO 42714 --- [nio-8083-exec-2] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Wed Jun 10 19:34:17 CEST 2015, principal=manage, type=AUTHENTICATION_SUCCESS, data={details=org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null}]

I guess that you would like to have different configurations for different URLs? The Multiple HttpSecurity chapter in the Spring Security reference docs suggests that you should create a security config that has multiple WebSecurityConfigurerAdapter beans (simplified snippet based on your problem and the example in the reference docs):
#Configuration
#EnableWebSecurity
public class MultiHttpSecurityConfig {
// variables omitted...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("apiUsername").password("apiPassword")
.roles(API_USER).and()
.withUser(managementUsername).password(managementPassword)
.roles(ADMIN_USER);
}
#Configuration
#Order(1)
public static class ManagementWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher(managementContextPath+"/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN_USER")
.and()
.httpBasic();
}
}
#Configuration
public static class DefaultWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("API_USER")
.and()
.httpBasic()
}
}
}
Please read the reference docs for details.

I changed the precedence and changed the management username and password property names and it works for me. The management context is only accessible to the management user and the rest of the secured paths are only accessible to the apiUsername. The problem is there no basic logout functionality. You either need to close the browser window or use a private tab to switch users.
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String API_USER = "API";
private static final String ADMIN_USER = "ADMIN";
#NotNull
#Value("${management.user.name}")
private String managementUsername;
#NotNull
#Value("${management.user.password}")
private String managementPassword;
#NotNull
#Value("${management.context-path}")
private String managementContextPath;
public ApplicationSecurityConfig() {
super(true);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and().headers().and().sessionManagement()
.sessionCreationPolicy(STATELESS).and().securityContext().and()
.requestCache().and().servletApi().and().authorizeRequests()
.antMatchers(managementContextPath + "/**").hasRole(ADMIN_USER)
.antMatchers("/**").hasRole(API_USER).and().httpBasic();
// #formatter:on
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("apiUsername")
.password("apiPassword").roles(API_USER).and()
.withUser(managementUsername).password(managementPassword)
.roles(ADMIN_USER);
}
}

hyness answer worked if I changed:
..
.antMatchers(managementContextPath + "/**").hasRole(ADMIN_USER)
.antMatchers("/**").hasRole(API_USER)
to
..
.requestMatchers(request -> !request.getContextPath().startsWith(managementContextPath)).hasRole(API)
.antMatchers("/**").not().hasRole(API)
.antMatchers(managementContextPath + "/**").hasRole(ADMIN)

Related

spring boot actuator endpoints with Keycloak security

we have a spring boot project (2.3.0.RELEASE) with actuator endpoints and we are introducing keycloak to the project with KeycloakWebSecurityConfigurerAdapter how can I prevent actuator endpoints being secured by the keycloak filter chain.
We would like to have the "/actuator/**" endpoints secured by basic auth.
Currently we have a custom WebSecurityConfigurerAdapter with #Order(1) where we apply the basic auth to "/actuator/**" and then we have with #Order(2) antotated the KeycloakWebSecurityConfigurerAdapter
so 2 filter chains gets registered and when I call the actuator endpoints the second filter chain fails as unauthorised 401
is it possible to prevent handling the "/actuator/**" resorce path on the second filter chain?
First actuator security configuration.
#Configuration
#Order(1)
public class ActuatorWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final String username;
private final String password;
private final PasswordEncoder encoder;
public ActuatorWebSecurityConfig(
#Value("${spring.security.user.name}") String username,
#Value("${spring.security.user.password}") String password,
Optional<PasswordEncoder> encoder) {
this.username = username;
this.password = password;
this.encoder = encoder.orElseGet(PasswordEncoderFactories::createDelegatingPasswordEncoder);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(username)
.password(encoder.encode(password))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/actuator/**")
.authorizeRequests(authorize -> authorize.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
}
}
second keycloak securoty configuration
#Order(2)
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private final String swaggerUrl;
private final CorsFilter corsFilter;
private final CustomSecurityConfig customSecurityConfig;
#Autowired
public SecurityConfig(
#Value("${springdoc.swagger-ui.url:#{null}}") String swaggerUrl,
CorsFilter corsFilter,
CustomSecurityConfig customSecurityConfig) {
this.swaggerUrl = swaggerUrl;
this.corsFilter = corsFilter;
this.customSecurityConfig = customSecurityConfig;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakProvider = keycloakAuthenticationProvider();
keycloakProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/actuator/**")));
.headers().frameOptions().disable()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/public/**", "/resources/**", "/resources/public/**").permitAll()
.antMatchers(OPTIONS, "/**").permitAll();
.authorizeRequests()
.antMatchers("/**")
.authenticated();
}
}
I have tried with on keycloak config
.antMatchers("/actuator/**").permitAll();
and with
http.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/actuator/**")));
but nothing works I receive unauthorised 401 for actuator
the registered filter chains :
2022-01-18 17:38:44,688 INFO org.springframework.security.web.DefaultSecurityFilterChain [main] Creating filter chain: Ant [pattern='/actuator/**'], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#25c6a9de, org.springframework.security.web.context.SecurityContextPersistenceFilter#56f3f9da, org.springframework.security.web.header.HeaderWriterFilter#33dcbdc2, org.springframework.security.web.csrf.CsrfFilter#522fdf0c, org.springframework.security.web.authentication.logout.LogoutFilter#365ad794, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#23df16cf, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#227cba85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#b38dc7d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#142422a4, org.springframework.security.web.session.SessionManagementFilter#2f0b7b6d, org.springframework.security.web.access.ExceptionTranslationFilter#74bca236, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#30587737]
2022-01-18 17:38:44,691 INFO org.springframework.security.web.DefaultSecurityFilterChain [main] Creating filter chain: NegatedRequestMatcher [requestMatcher=Ant [pattern='/actuator/**']], [com.betex.auth.filters.CorsFilter#20a9f5fb, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#10e28d97, org.springframework.security.web.context.SecurityContextPersistenceFilter#c6b08a5, org.springframework.security.web.header.HeaderWriterFilter#5f05cd7e, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter#2a54c92e, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter#55b62db8, org.springframework.security.web.authentication.logout.LogoutFilter#274f51ad, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#54980154, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#25874884, org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter#8cb7185, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter#4dac40b, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#37d43b9b, org.springframework.security.web.session.SessionManagementFilter#11e8e183, org.springframework.security.web.access.ExceptionTranslationFilter#56f1db5f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#78543f0d]
When you extend KeycloakWebSecurityConfigurerAdapter, the adapter register a Bean of type KeycloakAuthenticationProcessingFilter. This filter is registered in the Spring Security's SecurityFilterChain, and because it's a Bean, it is also automatically registered by Spring Boot in the original chain, therefore even if Spring Security doesn't apply it, it will be applied later on in original the filter chain.
Try disabling this filter from being registered by Spring Boot, like so:
#Bean
public FilterRegistrationBean registration(KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
In addition, if you are using OAuth 2, you may consider using spring-security-oauth2-resource-server and simplifying your Resource Server's configuration. Take a look at the documentation. This way you don't need to extend the custom adapter, just rely on the out-of-the-box configuration from Spring Security.

Spring Security not authorizing

I've been following a guide but I can't get Spring Security to work.
It looks like it is authenticating but not authorizing or viceversa, or not redirecting to the login successful page. Maybe it is a stupid mistake but I can't see it.
My spring security config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private UserSecurityService userSecurityService;
private static final String[] PUBLIC_MATCHERS = {
"/webjars/**",
"/css/**",
"/js/**",
"/images/**",
"/",
"/about/**",
"/contact/**",
"/error/**/*",
"/h2-console/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
List<String> activeProfiles = Arrays.asList(env.getActiveProfiles());
// Required by h2 console to work
if(activeProfiles.contains("dev")) {
http.csrf().disable();
http.headers().frameOptions().disable();
}
http
.authorizeRequests()
.antMatchers(PUBLIC_MATCHERS).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/payload")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService);
}
}
The application-dev.properties
spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
hibernate.dialect=org.hibernate.dialect.H2Dialect
The logs:
DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#2dafa81d: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 0D60174BBA25377F65443D95DB72F713; Granted Authorities: ROLE_ANONYMOUS
DEBUG o.s.s.access.vote.AffirmativeBased - Voter: org.springframework.security.web.access.expression.WebExpressionVoter#7a27baf6, returned: 1
DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Authorization successful
DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - RunAsManager did not change Authentication object
DEBUG o.s.security.web.FilterChainProxy - /js/scripts.js reached end of additional filter chain; proceeding with original chain
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
DEBUG o.s.s.w.a.ExceptionTranslationFilter - Chain processed normally
DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
DEBUG o.s.s.w.a.ExceptionTranslationFilter - Chain processed normally
DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
During authentication the application throws the following error:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
This exception is thrown, because the (plain text) password provided is missing the password-encoder {id}-prefix. Spring Security 5 now stores passwords using the following format (this was not the case for previous versions of spring security):
{id}encodedPassword
So that means for plain-text passwords, the {noop} id tells spring to match passwords using a NoOpPasswordEncoder (which basically handles passwords as plain-text).
However, storing plain-text passwords is highly discouraged (although it might be useful for automated testing).
Use a password encoder instead
Use of a BCryptPasswordEncoder, Pbkdf2PasswordEncoder or SCryptPasswordEncoder is highly recommended.
BCryptPasswordEncoder
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
#Configuration
class Config {
#Bean
public PasswordEncoder passwordEncoder() {
// Create an encoder with strength 31
// values from 4 .. 31 are valid; the higher the value, the more work has to be done to calculate the hash
return new BCryptPasswordEncoder(12);
}
}
Security Config
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
PasswordEncoder passwordEncoder;
...
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userSecurityService)
.passwordEncoder(passwordEncoder);
}
}
Encoding the password
#Service
class UserService implements UserDetailsService {
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
User createUser(String username, String password) {
// encrypt the plain-text password
String encodedPassword = passwordEncoder.encode(password);
User user = new User(username, encodedPassword));
//...
return userRepository.save(user);
}
}
Supporting more than one encoder
To support more than one encoder, one might want to look at the DelegatingPasswordEncoder and PasswordEncoderFactories.
For further details have a look at https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-format

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

Spring Boot OAuth2 - Could not obtain user details from token

I searched the web for solution of this problem but didn't find any working solution. I'm trying to setup basic Spring Boot OAuth2 Authorization Provider and Client.
I followed official Spring Boot instructions and created single sign on with Facebook and Github. Then i followed instructions to create Secure Spring Boot Web application.
I wanted to create my own Authorization Server so I added #EnableAuthorizationServer annotation to Secure Web Application as explained here. I also added details of an OAuth2 client as described in a link. I followed further instructions and created a OAuth2 Client.
I start both applications, visit 127.0.0.1:9999 to open a Client, client redirects me to localhost:8080/login, I enter user details and Authentication Provider redirects me to 127.0.0.1:9999/login and I get an error message:
Authentication Failed: Could not obtain user details from token
This is what gets logged:
INFO 2800 --- [nio-9999-exec-3] o.s.b.a.s.o.r.UserInfoTokenServices : Getting user info from: http:// localhost:8080/me
DEBUG 2800 --- [nio-9999-exec-3] o.s.s.oauth2.client.OAuth2RestTemplate : Created GET request for http:// localhost:8080/me
DEBUG 2800 --- [nio-9999-exec-3] o.s.s.oauth2.client.OAuth2RestTemplate : Setting request Accept header to [application/json, application/*+json]
DEBUG 2800 --- [nio-9999-exec-3] o.s.s.oauth2.client.OAuth2RestTemplate : GET request for http:// localhost:8080/me resulted in 200 (OK)
INFO 2800 --- [nio-9999-exec-3] o.s.b.a.s.o.r.UserInfoTokenServices : Could not fetch user details: class org.springframework.web.client.RestClientException, Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.Map] and content type [text/html;charset=UTF-8]]
This is my Client application:
#EnableAutoConfiguration
#Configuration
#EnableOAuth2Sso
#RestController
public class ClientApplication {
#RequestMapping("/")
public String home(Principal user) {
return "Hello " + user.getName();
}
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
}
This is client application YML:
server:
port: 9999
security:
oauth2:
client:
client-id: acme
client-secret: acmesecret
access-token-uri: http://localhost:8080/oauth/token
user-authorization-uri: http://localhost:8080/oauth/authorize
resource:
user-info-uri: http://localhost:8080/me
This is my Authorization Provider application:
#SpringBootApplication
public class SecurityApp {
public static void main(String[] args) {
SpringApplication.run(SecurityApp.class, args);
}
}
#Configuration
#EnableWebSecurity
#EnableAuthorizationServer
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
#RestController
public class Controller {
#RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
}
This is Application Provider YML:
security:
oauth2:
client:
client-id: acme
client-secret: acmesecret
scope: read,write
auto-approve-scopes: '.*'
I solved the issue! I was missing the Resource Server which handles the requests for user endpoint (user-info-uri). To the Authorization Provider application I added this class:
#Configuration
#EnableResourceServer
public class ResourceServer
extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/me")
.authorizeRequests().anyRequest().authenticated();
}
}
The user-info-uri should be in authorization server because all the accounts/users are in it.

Spring Boot Not Using UserDetailsService

I am trying to configure a Spring Boot 1.2.5 application for JPA authentication using annotations and it appears to be always using the in-memory provider.
The application:
#EnableWebMvc
#ComponentScan
#EnableAutoConfiguration
#SpringBootApplication
public class ClubBooksApplication {
protected final Logger logger = LoggerFactory.getLogger(getClass());
public static void main(String[] args) {
SpringApplication.run(ClubBooksApplication.class, args);
}
}
The WebSecurityConfigurerAdapter. I have played around with the order but it always seems to configure the in-memory provider. I feel like I could be missing a piece of configuration but this pattern matches the samples I found in my searches.
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
//#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) // After in memory
//#Order(SecurityProperties.IGNORED_ORDER) // Before in memory
//#Order(SecurityProperties.BASIC_AUTH_ORDER) // Not unique
#Order(SecurityProperties.BASIC_AUTH_ORDER - 50) // Before in memory
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private PasswordEncoder passwordEncoder;
protected final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
logger.info(String.format("configure AuthenticationManagerBuilder: %s", userDetailsService));
super.configure(auth);
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
Here is the log output. You can see it is configuring the UserDetailsService before displaying the generated password. Based on my digging into the code, it appears to only configure the in-memory provider if no other provider is configured but setting the UserDetailsService configures a DAO provider.
2015-08-20 11:19:24.187 INFO 42332 --- [ost-startStop-1] yConfig$$EnhancerBySpringCGLIB$$c647e8e8 : configure AuthenticationManagerBuilder: com.wstrater.server.clubBooks.server.service.impl.UserLoginDetailServiceImpl#46f0f40a
2015-08-20 11:19:24.226 INFO 42332 --- [ost-startStop-1] yConfig$$EnhancerBySpringCGLIB$$c647e8e8 : passwordEncoder
2015-08-20 11:19:24.410 INFO 42332 --- [ost-startStop-1] b.a.s.AuthenticationManagerConfiguration :
Using default security password: 838a7ab0-3bd0-4e87-94ca-de2dfd34b965
2015-08-20 11:19:24.526 INFO 42332 --- [ost-startStop-1] yConfig$$EnhancerBySpringCGLIB$$c647e8e8 : configure HttpSecurity
I have included Actuator in the app and when I try to access http://localhost:8080/mappings, I am prompted with BasicAuth despite configuring form based authentication. The user/generated password works for BasicAuth. My UserDetailsService implementation is not called.
Configuring HttpSecurity. This method is called after the in-memory provider is created and the generated password is displayed so I doubt it impacts the provider configuration. The one thing I find interesting is that I get prompted for BasicAuth despite specifying formLogin(). I bring this up since I am also having issues with mapping my controllers. I think it is unrelated but what do I know.
#Override
protected void configure(HttpSecurity http) throws Exception {
logger.info("configure HttpSecurity");
super.configure(http);
http.authorizeRequests()
.antMatchers("/", "/public/**")
.permitAll()
.antMatchers("/rest/**")
.authenticated()
.antMatchers("/web/**")
.authenticated()
.anyRequest()
.fullyAuthenticated();
http.formLogin()
.loginPage("/login")
.usernameParameter("userName")
.passwordParameter("password")
.failureUrl("/login?error")
.defaultSuccessUrl("/web/")
.permitAll()
.and().logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.permitAll()
.and().rememberMe();
}
I can see my controllers are being loaded by Spring since they are listed using http://localhost:8080/beans but I do not see the mappings in http://localhost:8080/mappings.
My login controller is rather simple.
#Controller
#Path("/login")
public class LoginWebController {
#GET
public ModelAndView getLoginPage(#RequestParam(required = false) String error) {
return new ModelAndView("login", "error", error);
}
}
Thanks, Wes.
I eventually solved this after working around and/or avoiding the problem so I may have done several things to solve the issue but I believe it is as simple as removing the call to super.configure(http);. This sets up basic auth.
Wes.

Resources