spring security - how to remove cache control in certain url pattern - spring

I am trying to filter some url pattern to caching.
What I have attempted is put some codes into WebSecurityConfigurerAdapter implementation.
#Override
protected void configure(HttpSecurity http) throws Exception {
initSecurityConfigService();
// For cache
http.headers().defaultsDisabled()
.cacheControl()
.and().frameOptions();
securityConfigService.configure(http,this);
}
However this code will effect all of the web application. How can I apply this to certain URL or Content-Type like images.
I have already tried with RegexRequestMatcher, but it does not work for me.
// For cache
http.requestMatcher(new RegexRequestMatcher("/page/", "GET"))
.headers().defaultsDisabled()
.cacheControl()
.and().frameOptions();
I read this article : SpringSecurityResponseHeaders, but there is no sample for this case.
Thanks.
P.S. In short, I want to remove SpringSecurity defaults for certain url and resources.

What about having multiple WebSecurityConfigurerAdapters? One adapter could have cache controls for certain URLs and another one will not have cache control enabled for those URLs.

I solved this with Filter.
Below is part of my implementation of AbstractAnnotationConfigDispatcherServletInitializer. In onStartup method override.
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
if(springSecurityFilterChain != null){
springSecurityFilterChain.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/render/*", "/service/*");
// I removed pattern url "/image/*" :)
}
What I have done is remove /image/* from MappingUrlPatterns.
Thanks for your answers!

Related

How do I configure a custom URL for the springdoc swagger-ui HTML page?

After adding the springdoc-openapi-ui dependency to my Spring project (not Spring Boot) OpenAPI V3 documentation is generated and can be viewed using the default swagger-ui page: localhost:8080/swagger-ui.html. Because the springdoc documentation replaces previous Swagger documentation I want to make it available at the same URL, localhost:8080/docs/index.html. Based on the springdoc documentation I get the impression that can be done by using the springdoc.swagger-ui.path option in the application.properties:
springdoc.swagger-ui.path=/docs/index.html
However, where I would expect to be able to navigate to the API documentation by going to localhost:8080/docs/index.html I get a 404 instead, localhost:8080/swagger-ui.html still works but now redirects to http://localhost:8080/docs/swagger-ui/index.html?configUrl=/restapi/v3/api-docs/swagger-config.
How can I configure my project too make the swagger-ui page available through a custom URL, i.e. localhost:8080/docs/index.html instead of the default localhost:8080/swagger-ui.html?
Edit
After trying some more to get it working and looking through the available information online, such as the springdoc FAQ (mentioned in this answer by H3AR7B3A7) I couldn't get it working. I've decided to go with a different solution which should have the same effect. The springdoc.swagger-ui.path option allows specifying a custom URL but, as I understand it, going to the custom URL redirects a user to the standard localhost:8080/swagger-ui.html page. So the redirect is now configured manually:
#RequestMapping("/docs/index.html")
public void apiDocumentation(HttpServletResponse response) throws IOException {
response.sendRedirect("/swagger-ui.html");
}
I had a similar task for Spring Boot 2.5.6 and springdoc-openapi-webflux-ui 1.5.12. I've found several possible solutions for myself. Maybe it will be helpful for somebody else.
Set springdoc.swagger-ui.path directly
The straightforward way is to set property springdoc.swagger-ui.path=/custom/path. It will work perfectly if you can hardcode swagger path in your application.
Override springdoc.swagger-ui.path property
You can change default swagger-ui path programmatically using ApplicationListener<ApplicationPreparedEvent>. The idea is simple - override springdoc.swagger-ui.path=/custom/path before your Spring Boot application starts.
#Component
public class SwaggerConfiguration implements ApplicationListener<ApplicationPreparedEvent> {
#Override
public void onApplicationEvent(final ApplicationPreparedEvent event) {
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
Properties props = new Properties();
props.put("springdoc.swagger-ui.path", swaggerPath());
environment.getPropertySources()
.addFirst(new PropertiesPropertySource("programmatically", props));
}
private String swaggerPath() {
return "/swagger/path"; //todo: implement your logic here.
}
}
In this case, you must register the listener before your application start:
#SpringBootApplication
#OpenAPIDefinition(info = #Info(title = "APIs", version = "0.0.1", description = "APIs v0.0.1"))
public class App {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(App.class);
application.addListeners(new SwaggerConfiguration());
application.run(args);
}
}
Redirect using controller
You can also register your own controller and make a simple redirect (the same as what you suggest, but in my case, I need to use the WebFlux approach):
#RestController
public class SwaggerEndpoint {
#GetMapping("/custom/path")
public Mono<Void> api(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("/swagger-ui.html"));
return response.setComplete();
}
}
The problem with such an approach - your server will still respond if you call it by address "/swagger-ui.html".
Apparantly the library integrates natively only with spring-boot applications like you mentioned in your comment.
If you want to use spring, it's possible but the integration details aren't documented, because it really depends on the version/module and the nature of you spring application.
You can check the FAQ to see if it answers your questions.
There are some more answers here on SO.
I solved by a clear code.
The problem is in webflux that security need permitAll for some uri resources so I solved in this mode in WebFlux Security I image the same in Springboot no WebFlux:
.authorizeExchange().pathMatchers(
// START To show swagger 3:
"/swagger-ui.html",
"/webjars/swagger-ui/**",
"/v3/api-docs/swagger-config",
"/v3/api-docs", // This is the one URI resource used by openApi3 too.
// END To show swagger 3:
"/other-uri-permitAll",
...
"/other-uri-permitAll_N",
.permitAll()
.and()
.authorizeExchange().pathMatchers(
"/other-uri-authenticated-only_1",
"...",
"/other-uri-authenticated-only_1")
.authenticated()
.and()
.authenticationManager(myAuthenticationManager)
.securityContextRepository(mySecurityContextRepository)
.authorizeExchange().anyExchange().authenticated()
.and()
.build();

Spring Security Custom Authentication Filter and Authorization

I've implemented a custom authentication filter, and it works great. I use an external identity provider and redirect to my originally requested URL after setting my session and adding my authentication object to my security context.
Security Config
#EnableWebSecurity(debug = true)
#Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {
// this is needed to pass the authentication manager into our custom security filter
#Bean
#Override
AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean()
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
//.antMatchers("/admin/test").hasRole("METADATA_CURATORZ")
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new CustomSecurityFilter(authenticationManagerBean()), UsernamePasswordAuthenticationFilter.class)
}
}
Filter logic
For now, my custom filter (once identity is confirmed) simply hard codes a role:
SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("METADATA_CURATORZ")
return new PreAuthenticatedAuthenticationToken(securityUser, null, [myrole])
That authentication object (returned above) is then added to my SecurityContext before redirecting to the desired endpoint:
SecurityContextHolder.getContext().setAuthentication(authentication)
Controller Endpoint
#RequestMapping(path = '/admin/test', method = GET, produces = 'text/plain')
String test(HttpServletRequest request) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication()
String roles = auth.getAuthorities()
return "roles: ${roles}"
}
This endpoint then yields a response in the browser of:
"roles: [METADATA_CURATORZ]"
Great. So my authentication and applying a role to my user is working great.
Now, if I uncomment this line from the security config:
//.antMatchers("/admin/test").hasRole("METADATA_CURATORZ")
I can no longer access that resource and get a 403 -- even though we've already proven the role was set.
This seems totally nonsensical and broken to me, but I'm no Spring Security expert.
I'm probably missing something very simple. Any ideas?
Some questions I have:
Does my custom filter need to be placed before a specific built-in filter to ensure the authorization step occurs after that filter is executed?
When in the request cycle is the antMatcher/hasRole check taking place?
Do I need to change the order of what I am calling in my security configure chain, and how should I understand the config as I've currently written it? It's obviously not doing what I think it should be.
Does my custom filter need to be placed before a specific built-in filter to ensure the authorization step occurs after that filter is executed?
Your filter MUST come before FilterSecurityInterceptor, because that is where authorization and authentication take place. This filter is one of the last to be invoked.
Now as to where the best place for your filter might be, that really depends. For example, you really want your filter to come before AnonymousAuthenticationFilter because if not, unauthenticated users will always be "authenticated" with an AnonymousAuthenticationToken by the time your filter is invoked.
You can check out the default order of filters in FilterComparator. The AbstractPreAuthenticatedProcessingFilter pretty much corresponds to what it is you're doing - and its placement in the order of filters gives you an idea of where you could put yours. In any case, there should be no issue with your filter's order.
When in the request cycle is the antMatcher/hasRole check taking place?
All of this happens in FilterSecurityInterceptor, and more precisely, in its parent AbstractSecurityInterceptor:
protected InterceptorStatusToken beforeInvocation(Object object) {
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
...
}
...
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
...
throw accessDeniedException;
}
Extra information:
In essence, the FilterSecurityInterceptor has a ExpressionBasedFilterInvocationSecurityMetadataSource that contains a Map<RequestMatcher, Collection<ConfigAttribute>>. At runtime, your request is checked against the Map to see if any RequestMatcher key is a match. If it is, a Collection<ConfigAttribute> is passed to the AccessDecisionManager, which ultimately either grants or denies access. The default AccessDecisionManager is AffirmativeBased and contains objects (usually a WebExpressionVoter) that process the collection of ConfigAttribute and via reflection invokes the SpelExpression that corresponds to your "hasRole('METADATA_CURATORZ')" against a SecurityExpressionRoot object that was initialized with your Authentication.
Do I need to change the order of what I am calling in my security configure chain, and how should I understand the config as I've currently written it? It's obviously not doing what I think it should be.
No, there shouldn't be any issue with your filters. Just as a side note, in addition to what you have in your configure(HttpSecurity http) methods, the WebSecurityConfigurerAdapter you extend from has some defaults:
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
You can take a look at HttpSecurity if you want to see exactly what these do and what filters they add.
THE PROBLEM
When you do the following:
.authorizeRequests()
.antMatchers("/admin/test").hasRole("METADATA_CURATORZ")
... the role that is searched for is "ROLE_METADATA_CURATORZ". Why?
ExpressionUrlAuthorizationConfigurer's static hasRole(String role) method ends up processing "METADATA_CURATORZ":
if (role.startsWith("ROLE_")) {
throw new IllegalArgumentException(
"role should not start with 'ROLE_' since it is automatically inserted. Got '"
+ role + "'");
}
return "hasRole('ROLE_" + role + "')";
}
So your authorization expression becomes "hasRole('ROLE_METADATA_CURATORZ'" and this ends up calling the method hasRole('ROLE_METADATA_CURATORZ') on SecurityExpressionRoot, which in turn searches for the role ROLE_METADATA_CURATORZ in the Authentication's authorities.
THE SOLUTION
Change
SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("METADATA_CURATORZ");
to:
SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("ROLE_METADATA_CURATORZ");

Spring Security + Auth LDAP : BindRequest and UnbindRequest?

After days of Google researching, Reading The F* Spring Security Manual and testing, I'm becoming desperate ...
The context : I'm implementing a Micro-Services architecture with Eureka etc...
I implemented an Auth Service which works very well with a MySQL authentication database. But now, I want to join my company LDAP through an OpenLDAP who works adequately.
So, I'm trying to join the LDAP with my spring security authentication.
The code of my configure() method (I replaced my company and domain name, the account {0} is "test"):
auth.ldapAuthentication()
.contextSource()
.url("ldap://myldap/ou=users,dc=mydomain,dc=mycompany")
.and()
.userDnPatterns("cn={0}");
I also tried different ways to write this and all the time, I get Bad Credentials or LDAP 32 error. With userDnPattern, usersearchbase method, passwordcompare, passwordencoder and others. I also tried to put DC in the root() method and the OU in the group...() method, no change (I think in fact that Spring Security sort these parameters smartly before sending LDAP Requests). To be honest, I tried 357654 differents ways to write the configure() method ...
The problem is that : When I put the same config, credentials, domains ... in an LDAP explorer software, it works correctly.
So I monitored LDAP networks exchanges with Wireshark and I saw this :
Wireshark screen
As we can see, there's 8 requests exchanged. The first 5 are OK. It find my account "test" correctly. But there's three over requests (with the unbindRequest which going back).
The problem is that Spring give me the result of the last request and say me the account doesn't exist or the credentials don't work, etc...
Have you got a clue for this ? Do you know how Spring Security works to question LDAP ? How can I do to contact my LDAP adequately with the framework ?
Thank you for reading.
Help me Stack Overflow, you're my only hope ...
I finally found the problem and got a solution.
My enterprise LDAP is an LDAP above an AD.
And this LDAP+AD needs Bind Authentication and doesn't authorize anonymous bind then authentication.
In Spring Security, there's an object which can do this : BindAuthenticator
This is how I try to use it (and it works).
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(ldapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() throws Exception {
LdapAuthenticationProvider lAP = new LdapAuthenticationProvider(ldapAuthenticator(), ldapAuthoritiesPopulator());
return lAP;
}
#Bean
public LdapContextSource ldapContextSource() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource([URL of the LDAP]);
return contextSource;
}
#Bean
public LdapAuthenticator ldapAuthenticator() throws Exception {
BindAuthenticator authenticator = new BindAuthenticator(ldapContextSource());
authenticator.setUserDnPatterns(new String[] {"CN={0},"+[MY ENTERPRISE LDAP FILTER]});
return authenticator;
}
Hope this sample code will help some people ...
Thank you !

How to set up cache-control on a 304 reply with Spring-security [duplicate]

I am trying to filter some url pattern to caching.
What I have attempted is put some codes into WebSecurityConfigurerAdapter implementation.
#Override
protected void configure(HttpSecurity http) throws Exception {
initSecurityConfigService();
// For cache
http.headers().defaultsDisabled()
.cacheControl()
.and().frameOptions();
securityConfigService.configure(http,this);
}
However this code will effect all of the web application. How can I apply this to certain URL or Content-Type like images.
I have already tried with RegexRequestMatcher, but it does not work for me.
// For cache
http.requestMatcher(new RegexRequestMatcher("/page/", "GET"))
.headers().defaultsDisabled()
.cacheControl()
.and().frameOptions();
I read this article : SpringSecurityResponseHeaders, but there is no sample for this case.
Thanks.
P.S. In short, I want to remove SpringSecurity defaults for certain url and resources.
What about having multiple WebSecurityConfigurerAdapters? One adapter could have cache controls for certain URLs and another one will not have cache control enabled for those URLs.
I solved this with Filter.
Below is part of my implementation of AbstractAnnotationConfigDispatcherServletInitializer. In onStartup method override.
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
if(springSecurityFilterChain != null){
springSecurityFilterChain.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/render/*", "/service/*");
// I removed pattern url "/image/*" :)
}
What I have done is remove /image/* from MappingUrlPatterns.
Thanks for your answers!

How to configure grails/spring authentication scheme per url?

How can I configure a grails application using Spring security such that one set of url's will redirect unauthenticated users to a custom login form with an http response code of 200, whereas another set of url's are implementing restful web services and must return a 401/not authorized response for unauthenticated clients so the client application can resend the request with a username and password in response to the 401.
My current configuration can handle the first case with the custom login form. However, I need to configure the other type of authentication for the restful interface url's while preserving the current behavior for the human interface.
Thanks!
If I understood right what you want to do, I got the same problem, before! but it is easy to solve it using Spring Security grails Plugin! So, first of all, you have to set your application to use basic authentication:
grails.plugins.springsecurity.useBasicAuth = true
So your restful services will try to login, and if it doesnt work it goes to 401!
This is easy but you also need to use a custom form to login right?! So you can just config some URL to gets into your normal login strategy like this:
grails.plugins.springsecurity.filterChain.chainMap = [
'/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter',
'/**': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]
So noticed, that above, everything that comes to the URL /api/ will use the Basic Auth, but anything that is not from /api/ uses the normal authentication login form!
EDIT
More information goes to http://burtbeckwith.github.com/grails-spring-security-core/docs/manual/guide/16%20Filters.html
I had the same issue and did not found a good solution for this. I am really looking forward a clean solution (something in the context like multi-tenant).
I ended up manually verifying the status and login-part for the second system, which should not redirect to the login page (so I am not using the "Secured" annotation). I did this using springSecurityService.reauthenticate() (for manually logging in), springSecurityService.isLoggedIn() and manually in each controller for the second system. If he wasn't, I have been redirecting to the specific page.
I do not know, whether this work-around is affordable for your second system.
You should make stateless basic authentication. For that please make following changes in your code.
UrlMappings.groovy
"/api/restLogin"(controller: 'api', action: 'restLogin', parseRequest: true)
Config.groovy
grails.plugin.springsecurity.useBasicAuth = true
grails.plugin.springsecurity.basic.realmName = "Login to My Site"
grails.plugin.springsecurity.filterChain.chainMap = [
'*' : 'statelessSecurityContextPersistenceFilter,logoutFilter,authenticationProcessingFilter,customBasicAuthenticationFilter,securityContextHolderAwareRequestFilter,rememberMeAuthenticationFilter,anonymousAuthenticationFilter,basicExceptionTranslationFilter,filterInvocationInterceptor',
'/api/': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]
resources.groovy
statelessSecurityContextRepository(NullSecurityContextRepository) {}
statelessSecurityContextPersistenceFilter(SecurityContextPersistenceFilter, ref('statelessSecurityContextRepository')) {
}
customBasicAuthenticationEntryPoint(CustomBasicAuthenticationEntryPoint) {
realmName = SpringSecurityUtils.securityConfig.basic.realmName
}
customBasicAuthenticationFilter(BasicAuthenticationFilter, ref('authenticationManager'), ref('customBasicAuthenticationEntryPoint')) {
authenticationDetailsSource = ref('authenticationDetailsSource')
rememberMeServices = ref('rememberMeServices')
credentialsCharset = SpringSecurityUtils.securityConfig.basic.credentialsCharset // 'UTF-8'
}
basicAccessDeniedHandler(AccessDeniedHandlerImpl)
basicRequestCache(NullRequestCache)
basicExceptionTranslationFilter(ExceptionTranslationFilter, ref('customBasicAuthenticationEntryPoint'), ref('basicRequestCache')) {
accessDeniedHandler = ref('basicAccessDeniedHandler')
authenticationTrustResolver = ref('authenticationTrustResolver')
throwableAnalyzer = ref('throwableAnalyzer')
}
CustomBasicAuthenticationEntryPoint.groovy
public class CustomBasicAuthenticationEntryPoint extends
BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
ApiController
#Secured('permitAll')
class ApiController {
def springSecurityService
#Secured("ROLE_USER")
def restLogin() {
User currentUser = springSecurityService.currentUser
println(currentUser.username)
}
}

Resources