Disable spring security for subdomain - java

I have an application which can be accessed via different domains of type
www.*.example.com
For ex-
www.test.example.com
www.npc.example.com
www.train.example.com
When configuring the spring security (authentication/oauth) , I'd like to enable security only for certain subdomains and keep it disabled for others.
Say enable it for only "www.test.example.com"
Here's my filter config -
#Bean
fun securityWebFilterChain(
http: ServerHttpSecurity
): SecurityWebFilterChain {
return http.csrf().disable()
.cors().configurationSource(corsConfigurationSource())
.and()
.authorizeExchange()
.pathMatchers("/**")
.authenticated()
.and().httpBasic().and().oauth2Login { oauth2 ->
oauth2.authenticationSuccessHandler(oauthSuccessHandler)
.authorizedClientService(redisOauthClientService)
}
.build()
}
The base url is present as another header. Is there a way to use the header to enable/disable security config? Any other approach would also be fine.

You can create a custom ServerWebExchangeMatcher implementation that checks if the request comes from a subdomain and use it in your DSL, something like:
http
.authorizeExchange()
.matcher(new MySubdomainMatcher()).permitAll()
.pathMatchers("/**").authenticated();
class MySubdomainMatcher implements ServerWebExchangeMatcher {
public Mono<MatchResult> matches(ServerWebExchange exchange) {
// perform the logic in the request
}
}

Related

How to disable authenticated access in Spring Security?

For dev and testing environments, I would like to disable authenticated #RestController access entirely throughout the application.
Is there any advantage or disadvantage using configured .anonymous() access over .permitAll()? Both works...
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.anonymous().build();
//or
return http.authorizeExchange()
.anyExchange().permitAll()
.build();
}
if you want everyone(authenticated or unauthenticated) to access your url go with permitall() ,but if want your url to access only by unauthenticated users use anonymous()

Basic Authentication using Swagger UI

I am trying to develop a spring-boot based rest API service with API documentation through Swagger UI. I want to enable basic authentication via the swagger UI so that the user can only run the API's once he/she authenticates using the Authorize button on swagger UI (by which a "authorization: Basic XYZ header is added to the API Call
At the front end (in the .json file for the Swagger UI I have added basic authentication for all the APIs using the following code (as per the documentation):
"securityDefinitions": {
"basic_auth": {
"type": "basic"
}
},
"security": [
{
"basic_auth": []
}
]
How should I implement the backend logic for the use case mentioned above (user can only run the API's once he/she authenticates using the Authorize button on swagger UI and it otherwise shows a 401 Error on running the API)
Some documentation or sample code for the same would be helpful
One option is to use the browser pop up authorization.
When you enable basic auth for your spring boot app, swagger ui will automatically use the browser's pop up window in order to use it for basic auth. This means that the browser will keep the credentials for making requests just like when you trying to access a secured GET endpoint until you close it.
Now, let's say you DON'T want to use the above and want swagger-ui for basic authentication as you say, you have to enable auth functionality on swagger-ui and optionally add security exception when accessing swagger-ui url.
To enable the basic auth functionality to swagger UI (with the "Authorize button" in UI) you have to set security Context and Scheme to your Swagger Docket (This is a simplified version):
#Configuration
#EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer{
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(basicAuthScheme()));
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(Arrays.asList(basicAuthReference()))
.forPaths(PathSelectors.ant("/api/v1/**"))
.build();
}
private SecurityScheme basicAuthScheme() {
return new BasicAuth("basicAuth");
}
private SecurityReference basicAuthReference() {
return new SecurityReference("basicAuth", new AuthorizationScope[0]);
}
}
This enables the authorization button in ui.
Now you probably want for your users to access the swagger-ui freely and use this button for authorization. To do this you have to exempt swagger for app's basic auth. Part of this configuration is Security config and you have to add following code:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(
"/", "/csrf",
"/v2/api-docs",
"/swagger-resources/**",
"/swagger-ui.html",
"/webjars/**"
).permitAll()
.anyRequest().authenticated();
}
}
A similar problem I was facing was that when using springfox documentation with Swagger OAS 3.0, the "Authenticate" button would not appear on the swagger UI.
Turns out there was a bug created for this very issue-
https://github.com/springfox/springfox/issues/3518
The core of the problem-
Class BasicAuth is deprecated.
The solution as found in the bug report above is to use HttpAuthenticationScheme instead to define the SecurityScheme object.
The Docket configuration then looks like so-
return new Docket(DocumentationType.OAS_30)
.groupName("Your_Group_name")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.mypackage"))
.paths(PathSelectors.regex("/.*"))
.build().securitySchemes(Arrays.asList(HttpAuthenticationScheme.BASIC_AUTH_BUILDER.name("basicAuth").description("Basic authorization").build()))
.securityContexts(); //define security context for your app here
Use a following dependency in build.gradle to enable a security:
"org.springframework.boot:spring-boot-starter-security"
In application.properties you can define your own username and password using:
spring.security.user.name=user
spring.security.user.password=password
Those who want to basic auth only for endpoints should do everything what #Sifis wrote but need to change antMatchers as:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(
"/",
"/v2/api-docs/**",
"/v3/api-docs/**",
"/swagger-resources/**",
"/swagger-ui/**",
"/swagger-ui.html").permitAll()
.anyRequest().authenticated();
}
}

Spring Security .permitAll() no longer effective after upgrading to Spring Boot 2.0.2

I work with a web app that exposes a REST API to mobile apps. I upgraded my Spring Boot version from 1.5.3.RELEASE to 2.0.2.RELEASE and after fixing a few breaking changes I am facing one that I cannot solve.
I followed this Spring Boot 2.0 Migration Guide and Spring Boot Security 2.0 and also looked into Security changes in Spring Boot 2.0 M4.
The issue is that the app uses JWT authentication and there is an endpoint (/auth/login) accepts user credentials and generates a long-lived JWT in return.
There is a filter that examines the JWT token sent by the client and determines whether the client can access the requested resource.
Custom security config is like this:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfiguration {
#Configuration
#Order(1)
public class AuthenticationConfiguration extends WebSecurityConfigurerAdapter {
// Some dependencies omitted
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because JWT token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/version/**").permitAll()
// Some more antMatchers() lines omitted
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter(jwtTokenUtil);
}
}
#Configuration
#Order(2)
public class ClientVersionSupportConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(versionCheckingFilter())
.addPathPatterns("/**")
.excludePathPatterns("/error"); // Some more endpoints omitted
}
#Bean
public VersionCheckingInterceptor versionCheckingFilter() {
return new VersionCheckingInterceptor();
}
}
}
Note the .antMatchers("/auth/**").permitAll() line. /auth endpoints should be accessible without JWT since the JWT has not yet been generated when the user has not yet logged in.
Before upgrading Spring Boot, it worked fine, now it is not working. Login attemps are rejected by the filter that checks the JWT. Looks like .permitAll() is not making the requests pass through. /version/** does not work either. Hitting it from the browser gives an error page.
I also tried to delete lines from the config until this remained:
httpSecurity
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
It did not help. Could you please help with restoring the original behavior?
Do you have a base path for you api, e.g. /api ?
The server.contextPath default Spring property name has changed to server.servlet.context-path.
So if you use a default base path for you api, you won't find the endpoints where you expect them. Unless you update the property ;)

Spring Security: enable / disable CSRF by client type (browser / non-browser )

Spring Security documentation says:
"When you use CSRF protection? Our recommendation is to use CSRF
protection for any request that could be processed by a browser by
normal users. If you are only creating a service that is used by
non-browser clients, you will likely want to disable CSRF protection."
What if my service is going to be used by both "browser" and "non-browser" clients such as third party external services, does Spring Security provide a way to disable CSRF exclusively for certain type of clients?
I am sure there is a way to do this in Spring Security XML, but since I am using Java Config, here is my solution.
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class SoapApiConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/soap/**")
.csrf().disable()
.httpBasic();
}
}
#Configuration
public static class WebApiConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginProcessingUrl("/authentication")
.usernameParameter("j_username")
.passwordParameter("j_password").permitAll()
.and()
.csrf().disable()
}
}
}
IMHO, there is nothing like that out of the box. What I would do in your case, is to have a hierarchy of URL for example rooted at /api that would be exempt of csrf. It is easy to configure. In the XML config, you have a normal <http> block including <csrf/>, you just duplicate it and modify the first block like that
<http pattern="/api/**">
...
<!-- csrf -->
</http>
As it is first, it will be triggered for any request to /api hierachy without using csrf, and all other requests will use it.
In the normal part of the application, you never use the /api/** url, and reserve them to non-browser usages.
Then in your controllers, you map them to both their normal url and a copy under /api :
#Controller
#RequestMapping({ "/rootcontrollerurl", "/api/rootcontrollerurl"})
class XController {
#RequestMapping(value = "/request_part_url", ...)
public ModelAndView method() {
...
}
}
(of course, rootcontrollerurl and request_part_url may be blank ...)
But you must analyze the security implication of allowing non csrf controlled requests, and eventually exclude controllers from the /api hierarchy.
Here is what I used to disable the CSRF protection on an specific endpoint
on your appconfig-security.xml add a node with the information of your pattern like the following example:
<http security="none" pattern="/sku/*"/>
<http security="none" pattern="/sku/*/*"/>
<http security="none" pattern="/sku"/>
Just keep in mind the order is important if you are going to use map all request using the symbol '*' goes first.

Chained authentication in Spring Security

Can I chain multiple instances of AuthenticationEntryPoint in Spring Security 3.2.4?
I attempting to create the following scenario:
A certain URL is secured with Spring Security
The AuthenticationEntryPoint used is LoginUrlAuthenticationEntryPoint
An admin interface can spawn services under this URL
The admin can choose to secure these services with CLIENT-CERT
When a user attempts to access the secure URL:
If the path has been secured with CLIENT-CERT then authentication fails unless they have provided a valid certificate the corresponds to a user in the UserService. Standard Spring Security x509 authentication.
Once the user has been authentication as per the first point, or if the URL is not secured with CLIENT-CERT, they are directed to a FORM based authentication page.
Once they successfully authenticate with a username and password, they are directed to a landing page.
I am running on Tomcat 7.0.54 with clientAuth="want". This works perfectly in a "simple" Spring Security set up - i.e. with one WebSecurityConfigurerAdapter set to x509() and another set to formLogin() as per this example
So, I want a process flow something like the following:
I have had some success with dynamically changing the used authentication method by using a DelegatingAuthenticationEntryPoint but:
When using an AntPathRequestMatcher to map, say, /form/** to a LoginUrlAuthenticationEntryPoint the authentication servlet (/j_spring_security_check) gives a HTTP404 error.
When using an AntPathRequestMatcher to map, say, /cert/** to a Http403ForbiddenEntryPoint the user's details are not extracted from the presented client certificate so this gives a HTTP403 error.
I also cannot see how to force a user to authenticate twice.
I am using the java-config and not XML.
My code:
I have a DelegatingAuthenticationEntryPoint:
#Bean
public AuthenticationEntryPoint delegatingEntryPoint() {
final LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> map = Maps.newLinkedHashMap();
map.put(new AntPathRequestMatcher("/basic/**"), new BasicAuthenticationEntryPoint());
map.put(new AntPathRequestMatcher("/cert/**"), new Http403ForbiddenEntryPoint());
final DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(map);
entryPoint.setDefaultEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
return entryPoint;
}
And my configure
#Override
protected void configure(final HttpSecurity http) throws Exception {
defaultConfig(http)
.headers()
.contentTypeOptions()
.xssProtection()
.cacheControl()
.httpStrictTransportSecurity()
.addHeaderWriter(new XFrameOptionsHeaderWriter(SAMEORIGIN))
.and()
.authorizeRequests()
.accessDecisionManager(decisionManager())
.anyRequest()
.authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(delegatingEntryPoint())
.and()
.sessionManagement()
.maximumSessions(1)
.sessionRegistry(sessionRegistry())
.maxSessionsPreventsLogin(true);
}
Where decisionManager() returns a UnanimousBased instance. sessionRegistry() returns a SessionRegistryImpl instance. Both methods are #Bean.
I add a custom UserDetailsService using:
#Autowired
public void configureAuthManager(
final AuthenticationManagerBuilder authBuilder,
final InMemoryUserDetailsService authService) throws Exception {
authBuilder.userDetailsService(authService);
}
And I have a custom FilterInvocationSecurityMetadataSource mapped using a BeanPostProcessor as in this example.
Chaining multiple entry points won't really work.
Your best option here might be to just customize the form-login process to check for the certificate if it's needed (before authenticating the user). That would probably simplify the configuration overall. It would really just be the same as a normal form-login setup.
The work done by the X509 filter is quite minimal. So for example, you could override the attemptAuthentication method, call super.attemptAuthentication() and then check that the certificate information matches the returned user authentication information.

Categories

Resources