I am trying use a single Auth server to access multiple resource servers from multiple clients.
I am trying to access two resource servers from same auth server and my configuration of resource servers goes as below.
#Bean
#Scope("prototype")
protected ResourceServerConfiguration resource1() {
ResourceServerConfiguration resource = new ResourceServerConfiguration();
resource.setConfigurers(Arrays.<ResourceServerConfigurer> asList(new ResourceServerConfigurerAdapter() {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID1).tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers().antMatchers("/greeting")
.and()
.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN");
}
}
resource.setOrder(4);
return resource;
}
#Bean
#Scope("prototype")
protected ResourceServerConfiguration resource2() {
ResourceServerConfiguration resource = new ResourceServerConfiguration();
resource.setConfigurers(Arrays.<ResourceServerConfigurer> asList(new ResourceServerConfigurerAdapter() {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID2).tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers().antMatchers("/welcome")
.and()
.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN");
}
}
resource.setOrder(5);
return resource;
}
Since default order of WebSecurityConfigurerAdapter is 3,I have configured the Order of Resource servers as 4 and 5 respectively.
But the configured Beans are getting overridden and I can access the resource "/welcome" which has order 5 and if I try to access the resource "/greeting",I am getting the following error,
{ "timestamp": 1444400211270, "status": 403, "error": "Forbidden", "message": "Expected CSRF token not found. Has your session expired?", "path": "/greeting"}
If I interchange the order between the resources,I can access the resource which has the highest value 5.
Note:I have two clients so that one can access RESOURCE1 and another can access RESOURCE2.
Please advice the things I am missing.
From the Javadoc of ResourceServerConfigurer:
Applications may provide multiple instances of this interface, and in
general (like with other Security configurers), if more than one
configures the same property, then the last one wins. The configurers
are sorted by Order before being applied.
So maybe put a permitAll() on the /welcome path in both configuration.
Related
I'm struggling to use the very same Spring Boot 3.0 application as both authentication server and resource server, but until now, I've not been able to make the whole thing working.
First, I defined a very simple RestController:
#RestController
#RequestMapping("api")
public class PublicAPI {
#GetMapping("/apitest")
public String test(Principal principal) {
return " This is a test ==>";
}
}
Then, essentially following the code found in a Sample project of Spring, I managed to setup my boot app as Spring Authorization Server. I'm able to use Postman to get the authentication token using Oauth2 flow: I'm redirected to Spring's standard login page, I log in with credentials, and I get the Token.
Problem is, if I try to GET http://localhost:9000/api/apitest` using provided token, I get a 401 response from Spring boot.
This is my Security Configuration:
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, CorsConfiguration configCors) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());
http
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
);
http.cors().configurationSource(request -> configCors);
return http.build();
}
#Bean
#Order(2)
SecurityFilterChain apiFilter(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests()
.requestMatchers("/api/**").authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
#Bean
#Order(3)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CorsConfiguration configCors) throws Exception {
http
.securityMatcher("/oauth2/**", "/login")
.authorizeHttpRequests()
.requestMatchers("/login", "/oauth2/**")
.authenticated()
.and()
.formLogin(Customizer.withDefaults());
http.cors().configurationSource(request -> configCors);
return http.build();
}
#Bean
public CorsConfiguration corsConfiguration() throws Exception {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.setAllowedOriginPatterns(List.of("*"));
configuration.setAllowedMethods(List.of("*"));
configuration.setAllowedHeaders(List.of("*"));
return configuration;
}
If I try to access another Spring API in a different Spring Boot application which uses the first one as Authentication Server I get no errors.
Pretty sure that there's something wrong my configuration... any hint will be greatly appreciated !
At the very end, it turned out that another filter has been configured:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class LoopbackIpRedirectFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getServerName().equals("localhost") && request.getHeader("host") != null) {
UriComponents uri = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request))
.host("127.0.0.1").build();
response.sendRedirect(uri.toUriString());
return;
}
filterChain.doFilter(request, response);
}
}
Removing the LoopbackIpRedirectFilter problem was fixed
This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 1 year ago.
I have the following configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class SamlConfig extends WebSecurityConfigurerAdapter {
#Value("${enable_csrf}")
private Boolean enableCsrf;
#Autowired
private SamlUserService samlUserService;
public SamlWebSecurityConfig() {
super();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.userDetailsService(samlUserService)
.serviceProvider()
.keyStore()
.storeFilePath("path")
.password("password")
.keyname("alias")
.keyPassword("password")
.and()
.protocol("https")
.hostname(String.format("%s:%s","localhost", "8080"))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath("metadata");
if (!enableCsrf) {
http.csrf().disable();
}
}
}
#Configuration
#Order(2)
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
This works for the SAML, but the basic login returns an error: 403 forbidden.
I modified the BasicConfig with this, and SAML doesn't work anymore but basic authentication works. All the endpoints are for both SAML and basic authentication, just different login page.
public static class BasicConfig extends WebSecurityConfigurerAdapter {
public BasicWebSecurityConfig() {
super();
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
if (!enableCsrf) {
http.csrf().disable();
}
}
}
For some reasons sometimes it works, sometimes not. I also tried to modify the #Order and still not working.
In Spring Security, there are two things that are alike but do things completely differently, requestMatchers().antMatchers() and authorizeRequests().antMatchers().
The requestMatchers tells HttpSecurity to only invoke the SecurityFilterChain if the provided RequestMatcher was matched.
The authorizeRequests allows restricting access based upon the HttpServletRequest using RequestMatcher implementations.
In your case, you have two SecurityFilterChains. But only the one with the highest priority is being invoked, this happens because you did not give any requestMatchers to it, therefore it will match every request. And only one SecurityFilterChain is called per request, thus it will not invoke the next one.
So, you should inform the requestMatchers for your configurations, like so:
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/sso", "/saml/**")
)
.authorizeRequests()
.antMatchers("/secure/sso").permitAll()
.antMatchers("/saml/**").permitAll()
.anyRequest().authenticated()
...
http
.requestMatchers((requests) -> requests
.antMatchers("/secure/basic", "/**")
)
.authorizeRequests()
.antMatchers("/secure/basic").permitAll()
.anyRequest().authenticated();
Tried to implement an OAuth2 in spring. But stuck as to which one would be the correct flow?
One flow I keep #Order(1) in (WebSecurityConfigurerAdapter)
Here on hitting the below I am presented with the default login page and I successfully login. http://localhost:8301/oauth/authorize?client_id=getidfromfacebook&response_type=code&redirect_uri=http://localhost:9191/xyz
Redirected to the authorize page and after acceptance get a code http://localhost:9191/xyz?code=mkuyG4 which helps in getting the access and refresh token by curl http://localhost:8301/oauth/token -H"Content-type: application/x-www-form-urlencoded" -d'grant_type=authorization_code&redirect_uri=http://localhost:9191/xyz&code=LJQef7' -u getidfromfacebook:getit
I am also able to get a fresh access token from the given refresh token via curl --location --request POST 'http://localhost:8301/oauth/token?grant_type=refresh_token&client_id=getidfromfacebook&refresh_token=a045acd6-5d66-4db5-a509-4bdadca065e0' -u getidfromfacebook:getit
The problem I face here is that with the given access token, I am not able to access any of the resources mentioned in
antMatchers("/api/**").authenticated() (ResourceServerConfigurerAdapter).
Like in postman provided a Header with Authorization and value Bearer access-token or like curl -H"Authorization: Bearer 1738520f-9f9c-43ef-8f7f-f5886075a7aa" http://localhost:8301/api/users/all/.
Note, I am able to get access-tokens for other grant_types as well and also refresh it. But no access to resources via the token. Point to note is if I hit the resource url, I am presented with the default login and able to access it.
The other flow I remove #Order(1). When I try to go through the authorization code flow, the system complains about user needs to be logged in for a request of (auth)code. So not able to proceed as am not presented with the default login page.
However, I am able to proceed with the password grant type curl http://localhost:8301/oauth/token -d"grant_type=password&username=username&password=userpassword" -H"Content-type:application/x-www-form-urlencoded; charset=utf-8" -u getidfromfacebook:getit
I am also able to access the resources via the access token.
Which one is the correct approach?
Why am I not able to access the resources in former approach.
#Configuration
#EnableAuthorizationServer
#AllArgsConstructor
public class AuthorizationServerConfigAdapter extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final ClientService clientService;
private final UserService userService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientService);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(this.authenticationManager)
.userDetailsService(userService)
;
}
/*****************************/
#Configuration
#EnableResourceServer
public class ResourceServerConfigAdapter extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/").permitAll();
}
}
/*****************************/
#Configuration
#EnableWebSecurity
#AllArgsConstructor
#Order(1) // Since we have this working as N, Z and R sever.
public class WebSecurityConfigAdapter extends WebSecurityConfigurerAdapter {
private final UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/authorize**", "/login**", "/error**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A);
}
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Bearer"));
})
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.antMatchers("/").permitAll();
}
I have a spring-boot application with spring-security and dropwizard metrics. It uses Angularjs as a frontend. Authentication is done using separate login.html page with angularjs controller posting credentials to '/login' and after seccessful response routing to index.html (separate angularjs app). This all works quite well until I try to access dropwizard metrics. In this case I get a spring-security exception saying that user is anonymous (all other urls work fine).
My spring-security config:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableWebSecurity
public class FormLoginSecurityConfigurer extends WebSecurityConfigurerAdapter {
private class AuthSuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
}
}
private class AuthFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login.html", "/scripts/login/**", "/libs/**", "/styles/**", "/images/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login.html").loginProcessingUrl("/login")
.usernameParameter("username").passwordParameter("password")
.successHandler(new AuthSuccessHandler())
.failureHandler(new AuthFailureHandler())
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login.html")
.and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(CsrfHeaderFilter.csrfTokenRepository());
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
The metrics servlet is registered in the ServletContextInitilizer:
/**
* Configuration of web application with Servlet 3.0 APIs.
*/
#Configuration
public class WebConfigurer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
initMetrics(servletContext,
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC));
}
/**
* Initializes Metrics.
*/
private void initMetrics(ServletContext servletContext, EnumSet<DispatcherType> disps) {
log.debug("Initializing Metrics registries");
servletContext.setAttribute(InstrumentedFilter.REGISTRY_ATTRIBUTE,
metricRegistry);
servletContext.setAttribute(MetricsServlet.METRICS_REGISTRY,
metricRegistry);
log.debug("Registering Metrics Filter");
FilterRegistration.Dynamic metricsFilter = servletContext.addFilter("webappMetricsFilter",
new InstrumentedFilter());
metricsFilter.addMappingForUrlPatterns(disps, true, "/*");
metricsFilter.setAsyncSupported(true);
log.debug("Registering Metrics Servlet");
ServletRegistration.Dynamic metricsAdminServlet =
servletContext.addServlet("metricsServlet", new MetricsServlet());
metricsAdminServlet.addMapping("/metrics/metrics/*");
metricsAdminServlet.setAsyncSupported(true);
metricsAdminServlet.setLoadOnStartup(2);
}
}
However when I access anything under /metrics/metrics the browser prompts for basic authentication. The response has the following header WWW-Authenticate:"Basic realm="Spring"". Other resources are downloaded fine.
I'm new to this kind of applications and getting a bit frustrated :) Any help is appreciated.
Seems its all in the docs if one knows what to look for - link
The Actuator security features can be modified using external properties (management.security.*). To override the application access rules add a #Bean of type WebSecurityConfigurerAdapter and use #Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) if you don’t want to override the actuator access rules, or #Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) if you do want to override the actuator access rules.
Changed the order to ManagementServerProperties.ACCESS_OVERRIDE_ORDER and now it works.
Using Spring-Boot 1.1.17, Spring-MVC with Spring-Security:
I have several subdomains that I want to to allow unauthenticated users (Visitors) access to. For example:
mysite.com/customerA
mysite.com/customerB
If a invalid customer site is attempted, then my controller would either throw an exception or redirect back to / (mysite.com/) Naturally other parts of the domain (mysite.com/customerA/myaccount) will require login.
I haven't really figured out how to do this with spring security and spring-mvc. Here is what I am attempting so far:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CSRFTokenGeneratorFilter(), CsrfFilter.class)
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers( "/**/" ).permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/wizard").permitAll()
.antMatchers("/menu").permitAll()
.antMatchers("/error").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/libs/**").permitAll();
http
.formLogin()
.loginPage("/loginPage")
.permitAll()
.loginProcessingUrl("/login")
.failureUrl("/login?error")
.defaultSuccessUrl("/?tab=success")
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll()
.and()
.csrf();
http
.sessionManagement()
.maximumSessions(1)
.expiredUrl("/login?expired")
.maxSessionsPreventsLogin(true)
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/");
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService( customUserDetailsService ).passwordEncoder( encoder );
}
#Override
public void configure(WebSecurity security){
security.ignoring().antMatchers("/css/**","/fonts/**","/libs/**");
}
}
And my homepage controller:
#Controller
#RequestMapping("/{officeName}/")
public class HomeController {
private AuthenticatedUser getVisitor(#PathVariable String officeName) {
.. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
When I try to access that url, I get the following errors:
o.s.web.servlet.DispatcherServlet : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/customerA/]
s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /customerA/
s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/customerA/]
o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/customerA/] are [/**]
o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/customerA/] are {}
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/customerA/] to HandlerExecutionChain with handler [org.springframework.web.servlet.resource.ResourceHttpRequestHandler#2f295527] and 1 interceptor
o.s.web.servlet.DispatcherServlet : Last-Modified value for [/customerA/] is: -1
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: ServletContext resource [/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [META-INF/resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [resources/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [static/]
o.s.w.s.r.ResourceHttpRequestHandler : Trying relative path [customerA] against base location: class path resource [public/]
o.s.w.s.r.ResourceHttpRequestHandler : No matching resource found - returning 404
I tried adding this ServletRegistrationBean:
#Bean
public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean( dispatcherServlet );
registration.addUrlMappings("/", "/testCustomer/*" );
for ( Office office : officeService.findAllActiveOffices() ) {
registration.addUrlMappings( office.getUrl() + "/*" );
}
return registration;
}
But this would seem to only work if the application knows of the customer at startup, not dynamically in the case of customer signup.
Is there a way to configure this to handle these types of wildcards?
You can try with a configuration like the following:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService _userService;
#Autowired
private PasswordEncoder _passwordEncoder;
/**
* Defines the password encoder used by Spring security during the
* authentication procedure.
*/
#Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
/**
* Sets security configurations for the authentication manager
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(_userService)
.passwordEncoder(_passwordEncoder);
return;
}
/**
* Configures where Spring Security will be disabled (security = none).
* From spring reference: "Typically the requests that are registered [here]
* should be that of only static resources. For requests that are dynamic,
* consider mapping the request to allow all users instead."
*/
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/css/**",
"/js/**",
"/fonts/**",
"/resources/**",
"/libs/**");
return;
}
/**
* Sets security configurations in the HttpSecurity object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set security configurations
http
.authorizeRequests()
// the following urls are allowed for any user (no authentication)
.antMatchers(
"/",
"/login",
"/menu")
.permitAll()
// any other url must be authenticated
.anyRequest().authenticated()
.and()
// define the login page url
.formLogin()
.loginPage("/login")
.permitAll()
.and()
// define the logout url
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
return;
} // method configure
} // class WebSecurityConfig
Adding your personal configurations... You can try to add the following controller:
#Controller
public class HomeController {
#RequestMapping("/{officeName}/")
public AuthenticatedUser getVisitor(#PathVariable String officeName) {
// .. do something with the office if found, redirect otherwise
if (!StringUtils.isEmpty(officeName)) {
Office office = officeService.findByName( officeName );
return office.getUrl();
}
return "/";
}
}
If the user is correctly authenticated he should access the url at the officeName.