I use Spring Boot and Apache Wicket in a web application. I have to add file upload.
In the below code Wicket's Form component's onSubmit method is fired but "uploads" is empty.
#Override
protected void onSubmit()
{
final List<FileUpload> uploads = fileUploadField.getFileUploads();
if (uploads != null)
{
for (FileUpload upload : uploads)
{
// Create a new file
File newFile = new File(getUploadFolder(), upload.getClientFileName());
// Check new file, delete if it already existed
checkFileExists(newFile);
try
{
// Save to new file
newFile.createNewFile();
upload.writeTo(newFile);
// UploadPage.this.info("saved file: " + upload.getClientFileName());
}
catch (Exception e)
{
throw new IllegalStateException("Unable to write file", e);
}
}
}
}
Spring's configure method
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests().antMatchers("/**").permitAll()
.and()
.logout()
.permitAll();
http.headers().frameOptions().disable();
}
I have created a separate application which contains Wicket but not Spring and same upload code works without problem.
I have tried this and did not work.
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
#Bean(name = "filterMultipartResolver")
public CommonsMultipartResolver getMultipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
return multipartResolver;
}
and when i add below suggestion to Form component's action url via javascript, Wicket's "onSubmit" method is not even triggered
Spring MVC - upload file is blocked by spring security
Edit:
When I watch on the network I see that Spring returns 302 to Wicket's POST request for upload.
Adding this to application.properties file solved the problem.
spring.servlet.multipart.enabled=false
Related
I have a vuejs + spring boot app. All was working fine, but suddenly got this issue - requests to files in /js/, /css/, /img/ are returning the index.html content despite having a resource mapping pointing to classpath:/static.
Can't trance the original change which lead to the appearance of this problem. front-end works fine by itself (tried deploying to surge & zeit now), so i suppose the problem is that spring boot ignores the resource mapping.
spring boot v2.1.2
WebMvcConfig:
#Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
String baseApiPath = "/api";
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/**/*.css", "/**/*.html", "/**/*.js", "/**/*.png", "/**/*.ttf")
.setCachePeriod(0)
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/")
.setCachePeriod(0)
.addResourceLocations("classpath:/static/index.html")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
#Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
if (resourcePath.startsWith(baseApiPath) || resourcePath.startsWith(baseApiPath.substring(1))) {
return null;
}
return location.exists() && location.isReadable() ? location : null;
}
});
}
}
in index.html links like this <script src=/js/chunk-vendors.b7114b0e.js></script><script src=/js/app.5c7ddca5.js></script> returning the index.html itself.
I need to update my Spring Security configuration to introduce multi-tenant management (where I get URL for each web request and through a configuration file I retrieve the correct schema).
So I add a filter (because with handler the login page doesn't have the correct schema since the handler is called after spring security) to my spring security configuration but now I catch the URL, set the schema but the page still empty and doesn't redirect to login page and also if I write /login no HTML page appears.
This is how I have configured spring security:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private RoleServices roleServices;
#Autowired
private CustomSuccessHandler customSuccessHandler;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth)throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery("select username,password,enabled from user where username=?")
.authoritiesByUsernameQuery("select u.username, CONCAT('ROLE_' , r.role) from user u inner join role r on u.idRole = r.idRole where lower(u.username) = lower(?)");
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**","/users/{\\d+}/password/recover","/users/{\\d+}/token/{\\d+}/password/temporary")
.antMatchers(HttpMethod.PUT,"/users/{\\d+}/token/{\\d+}/password/temporary");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
List<Role> roles=roleServices.getRoles();
//Retrieve array of roles(only string field without id)
String[] rolesArray = new String[roles.size()];
int i=0;
for (Role role:roles){
rolesArray[i++] = role.getRole();
}
http
.authorizeRequests() //Authorize Request Configuration
.anyRequest().hasAnyRole(rolesArray)//.authenticated()
.and()//Login Form configuration for all others
.formLogin()
.loginPage("/login").successHandler(customSuccessHandler)
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.deleteCookies("JSESSIONID", "JSESSIONID")
.invalidateHttpSession(true)
.permitAll()
.and()
.sessionManagement().invalidSessionUrl("/login")
.and()
.addFilterAfter(new MultiTenancyInterceptor(), BasicAuthenticationFilter.class);
}
}
I added MultiTenancyInterceptor filter where I set the Tenant
#Component
public class MultiTenancyInterceptor extends OncePerRequestFilter {
#Override
public void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
String url = request.getRequestURL().toString();
URI uri;
try {
uri = new URI(url);
String domain = uri.getHost();
if(domain!=null){
TenantContext.setCurrentTenant(domain);
}
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
but as I write the controller of login page doesn't receive the call:
#Override
#RequestMapping(value = { "/login" }, method = RequestMethod.GET)
public String loginPage(){
return "login";
}
Do you see an error in my configure method? If you need further information I can add the other classes. Thanks
PS: I noticed that doFilter is called twice for each page request
Best way is to implement Filter inteface and do some your url logic and then forward it to next action using filterChain.doFilter(request, response);
Make sure to add this filter in web.xml.
Either way is you can use spring org.springframework.web.servlet.handler.HandlerInterceptorAdapter for pre and post handling for http requests. Spring internally forwards to next controller request method.
Example : https://www.mkyong.com/spring-mvc/spring-mvc-handler-interceptors-example/
After the suggest of dur I add the code
filterChain.doFilter(request, response);
at the end of filter method
We have migrated from Basic Authentication to Keycloak method in our project in the production environment. However we would like continue using Basic Authentication, for local development, standalone and demo instalations, which could be triggered by a profile or something like this.
In this project we have REST APIs developed with Java/Spring boot and an AngularJS application which consumes these APIs. We are using Keycloak to protect both AngularJS app and the APIs.
The problem is how to make Spring Security and Keycloak to work "together" in the same application with different profiles. The solution I found so far, was to configure both Spring Security and Keycloak, and made a workaround with properties files, as described below:
application-keycloak.properties
#Unactivate Basic Authentication
security.ignored=/**
application-local-auth.properties
#Unactivate Keycloak
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
When I wanto to use keycloak, I have to ignore security in order to not have problems and when I want to use basic authentication I have to exclude Keycloak configuration in order to also prevent conflicts.
This is my Security Configuration class:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.authorizeRequests()
.antMatchers("/","/scripts/**","/keycloak/isActive","/keycloak/config","/bower_components/**","/views/**","/fonts/**",
"/views/inventory/dialogs/**", "/services/**","/resources/**","/styles/**", "/info")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
And this is my Keycloak Spring Boot configuration:
# Keycloak
keycloak.realm=local
keycloak.realmKey=MIIBIjANBgkqhkiG9wsIIBCgKCAQEAuJYmaWvF3YhifflJhspXOs8RJn74w+eVD8PtpVbu2cYG9OIa49P8SwqVn/kyJQr7kT3OlCq3XMZWBHe+JSzSz7KttKkhfFSfzISdKDKlkPena2H/i3FKlRZIldbeeuQNYdD6nMpzU6QWLwGF1cUAo1M11f2p99QI1FOhVPJSErWsjDsKpWqG+rMMjT1eos0QCNP7krx/yfMdlUyaJCYiDvpOAoec3OWXvDJovEajBNAZMWVXgJF90wAVPRF6szraA2m7K2gG9ozaCNWB0v4Sy6czekbKjqEBPJo45uEmGHd92V//uf/WQG4HSiuv8CTV+b6TQxKtZCpQpqp2DyCLewIDAQAB
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=none
keycloak.resource=App-backend
keycloak.bearer-only=true
keycloak.credentials.secret=a714aede-5af9-4560-8c9d-d655c831772f
keycloak.securityConstraints[0].securityCollections[0].name=Secured API
keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=ROLE_USER
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/api/*
It is working, however I think it is not an elegant solution. I have tried to implement this using the Keycloak property enable-basic-auth, but I could not understand how it works but it seems that it is just to protect Rest APIs, it does not allow the browser to create a session and use it for all the other requests.
Have someone ever had to implement something like this and can give me some better idea?
I managed to solve this. However, how beautiful my solution is is up for debate.
My use case is that I need to secure most of my endpoints using Keycloak but some (for batch processing) should just use Basic Auth. Configuring both has the downside that Keycloak tries to validate the Authorization Header even if it is Basic Auth so I needed to do three things.
Deactivate all automatic security for my batch route.
Write a custom request filter which secures the batch route.
Manipulate the servlet request object such that the zealous keycloak filter doesn't trip on it.
My security configuration.
#EnableWebSecurity
#EnableResourceServer
public class SecurityConfiguration extends KeycloakWebSecurityConfigureAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
// usual configuration ...
.antMatchers("/api/v1/batch/**").permitAll() // decouple security for this route
.anyRequest().denyAll();
}
}
My custom request filter (needs to run before the spring security filter, thus the ordering annotation):
#Component
#Slf4j
#Order(Ordered.HIGHEST_PRECEDENCE + 2)
public class BasicAuthRequestFilter extends OncePerRequestFilter {
#Value("${batch.user}")
private String user;
#Value("${batch.password}")
private String password;
#Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
if (isBatchRequest(request)) {
SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
if (AuthOutcome.AUTHENTICATED.equals(auth(facade))) {
filterChain.doFilter(new AuthentifiedHttpServletRequest(request), response);
}
log.debug("Basic auth failed");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
return;
}
filterChain.doFilter(request, response);
}
private boolean isBatchRequest(HttpServletRequest request) {
return request.getRequestURI().startsWith("/api/v1/batch/");
}
private AuthOutcome auth(HttpFacade exchange) {
return extractToken(exchange.getRequest().getHeaders(HttpHeaders.AUTHORIZATION))
.map(token -> extractUserPw(token)
.filter(userpw -> verify(userpw.getFirst(), userpw.getSecond()))
.map(userpw -> AuthOutcome.AUTHENTICATED)
.orElse(AuthOutcome.FAILED))
.orElse(AuthOutcome.NOT_ATTEMPTED);
}
private Optional<String> extractToken(List<String> authHeaders) {
return authHeaders == null ? Optional.empty() : authHeaders.stream().map(authHeader -> authHeader.trim().split("\\s+"))
.filter(split -> split.length == 2)
.filter(split -> split[0].equalsIgnoreCase("Basic"))
.map(split -> split[1])
.findFirst();
}
private Optional<Pair<String, String>> extractUserPw(String token) {
try {
String userpw = new String(Base64.decode(token));
String[] parts = userpw.split(":");
if (parts.length == 2) {
return Optional.of(Pair.of(parts[0], parts[1]));
}
} catch (Exception e) {
log.debug("Basic Auth Token formatting error", e);
}
return Optional.empty();
}
private boolean verify(String user, String password) {
return (this.user.equals(user) && this.password.equals(password));
}
}
And finally the wrapped ServletRequest (as you cannot remove Headers from the request):
public class AuthentifiedHttpServletRequest extends HttpServletRequestWrapper {
public AuthentifiedHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
return true;
}
#Override
public String getAuthType() {
return "Basic";
}
#Override
public String getHeader(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getHeader(name);
}
return null;
}
#Override
public Enumeration<String> getHeaders(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getHeaders(name);
}
return Collections.enumeration(Collections.emptyList());
}
#Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(EnumerationUtils.toList(super.getHeaderNames())
.stream()
.filter(s -> !HttpHeaders.AUTHORIZATION.equalsIgnoreCase(s))
.collect(Collectors.toList()));
}
#Override
public int getIntHeader(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getIntHeader(name);
}
return -1;
}
}
Not quite sure whether this is still relevant or not, but maybe someone will find it helpful.
By default, Keycloak is overwriting plenty of configurations. It's intercepting all Auth request (OAuth2, BasicAuth etc.)
Fortunately, with Keycloak, it's possible to enable authentication both with OAuth2 and BasicAuth in parallel, which I assume is what you want to enable in your dev/localhost environments.
In order to do that, you first need to add the following property to your
application-local-auth.properties:
keycloak.enable-basic-auth=true
This property will enable Basic Auth in your dev environment. However, you also need to enable Basic Auth at your client in Keycloak.
You can accomplish that by connecting to the Keycloak Admin Console on your local Keycloak server and enabling the Direct Access Grant for your client:
Enabling Basic Auth in Keycloak
After that you can authenticate both with Bearer Token and Basic Auth.
I'm developing a web application unsing Spring Security 4.0.0's Java Config instead of xml config. I'm using ObjectPostProcessors to customize the some of Spring Security's beans, notably the session consurrency ones (to achive immediate invalidation of a session as soon as a user logs in again, as opposed to Spring's standard behavior of invalidating at the next request).
It's working as expected most of the times, but sometimes when I restart the application it seems like not all beans get modified as I want.
Are SecurityBuilders processed in a specific order or are they instead processed with a ramdom order?
EDIT:
My Config
#EnableWebSecurity
public class SecurityConfig extends AbstractCASWebSecurityConfigurerAdapter {
public SecurityConfig() {
super(true, false, true);
}
#Autowired
private Environment env;
// we need a custom SessionRegistry as there's no way to get ahold of the one created by the configurer.
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
// we need a custom HttpSessionCsrfTokenRepository as there's no way to get ahold of the one created by the configurer.
#Bean
public CsrfTokenRepository csrfTokenRepository() {
return new HttpSessionCsrfTokenRepository();
}
// our custom ConcurrentSessionControlAuthenticationStrategy that invalidates session immediately
#Bean
public SessionInvalidatingConcurrentSessionControlAuthenticationStrategy myConcurrentSessionControlAuthenticationListener()
{
// we have to recreate the LogoutHandlers because we need to call them
// before invalidating the session
final LogoutHandler [] logoutHandlers = new LogoutHandler [] {
new CookieClearingLogoutHandler("JSESSIONID"),
new CsrfLogoutHandler(csrfTokenRepository())
//, new SecurityContextLogoutHandler() // seems to create problems with redirecting to the same page that caused the login request
};
SessionInvalidatingConcurrentSessionControlAuthenticationStrategy mine = new SessionInvalidatingConcurrentSessionControlAuthenticationStrategy(sessionRegistry(), logoutHandlers);
mine.setExceptionIfMaximumExceeded(false);
mine.setMaximumSessions(1);
return mine;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
boolean devMode = this.env.acceptsProfiles("development");
final String [] ignoredPaths = devMode
? new String [] {"/webjars/**", "/static/**", "/bower_components/**" }
: new String [] {"/webjars/**", "/static/**" };
web
.ignoring()
.antMatchers(ignoredPaths)
.and()
.debug(false)
;
}
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.sessionManagement()
.maximumSessions(73467436) // this is just to trigger the ConcurrencyControlConfigurer
.sessionRegistry(sessionRegistry())
.and()
.withObjectPostProcessor(new ObjectPostProcessor<ConcurrentSessionControlAuthenticationStrategy>() {
#SuppressWarnings("unchecked")
#Override
public <O extends ConcurrentSessionControlAuthenticationStrategy> O postProcess(O concurrentSessionControlAS) {
// substitute the ConcurrentSessionControlAuthenticationStrategy created by
// ConcurrencyControlConfigurer with our own
return (O) myConcurrentSessionControlAuthenticationListener();
}
})
.and()
// we need to ignore the stomp endpoint to allow SockJS javascript client to issue POST requests
// to /push/../../.. when using trasports which are not WebSocket;
// at that time, protection is given by Stomp CSRF headers
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.ignoringAntMatchers("/push/**")
.and()
// allow same origin to frame our site to support iframe SockJS
.headers()
.frameOptions().sameOrigin()
.and()
.authorizeRequests()
.antMatchers("/help/**").permitAll() // help redirects do not require authentication
.antMatchers("/push/info").permitAll() // do not require being authenticated for the /info request by SockJS
.anyRequest().authenticated()
.and()
// remove the session cookie when logging out
.logout()
.deleteCookies("JSESSIONID") // see: http://docs.spring.io/autorepo/docs/spring-security/current/reference/htmlsingle/#detecting-timeouts
.and()
;
}
}
AbstractCASWebSecurityConfigurerAdapter is an AbstractWebSecurityConfigurerAdapter that configures CAS.
I am trying to authenticate against AD in my application created with Vaadin, which is using also Spring (SpringVaadinIntegration).
I can't find any information about how to achieve this and a lot of confusing, different and partial ways to connect to Active Directory with Spring security.
Since Vaadin form fields don't have a name, I don't know if I can even use a normal form or I have to write my own JSP. My impression is that to map the username and the password entered in the form to the xml it's necessary that the fields have a name.
Has anybody achieved this or anybody has a clue on how to do it?
If somebody can provide a link where this is explained step by step, for dummies, would be great too. I just can find partial solutions, where you don't get an overall of the system and how should be configured.
We have a TextField (username), a PasswordField (password) and a Button on a UI:
public class MyUI extends UI {
#Override
protected void init( VaadinRequest request ) {
setContent( VaadinSession.getCurrent().getAttribute("userId") == null ? getNewLoginLayout() : getNewMainLayout() );
}
private VerticalLayout getNewLoginLayout() {
TextField username = ...
TextField password = ...
Button login = ...
return new VerticalLayout(username, password, login);
}
}
When the button pushed we do a simple LDAP search like this on the server side (for example pass these parameters to a Spring bean). If it is successful we set a VaadinSession attribute (userId) and change the UI content to the main layout. Spring security need not necessarily.
Even this question is already answered I want to show you my solution.
We use Spring Security for LDAP authentication, so we have these two configuration classes:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
// #formatter:off
http
.authorizeRequests()
.anyRequest().authenticated() // Alle Requests erfordern einen Login...
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/#!").permitAll() // http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#jc-form
.and()
.logout().permitAll() // http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#jc-logout
.and()
.csrf().disable(); // CSRF (https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html) wird von Vaadin selbst gehandhabt!
// #formatter:on
}
/**
* #see http://stackoverflow.com/questions/34944617/java-config-for-spring-security-with-vaadin/35212403#35212403
*/
#Override
public void configure(WebSecurity web) throws Exception
{
// #formatter:off
web
.ignoring()
.antMatchers("/resources/**", "/VAADIN/**");
// #formatter:on
}
}
#Configuration
public class SecurityConfigActiveDirectory
{
#Value("${ldap.url}")
String ldapUrl;
#Value("${ldap.domain}")
String ldapDomain;
#Bean
public AuthenticationManager authenticationManager()
{
ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(ldapDomain, ldapUrl);
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
adProvider.setAuthoritiesMapper(getAuthorityMapper());
return new ProviderManager(Arrays.asList(adProvider));
}
private static SimpleAuthorityMapper getAuthorityMapper()
{
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setConvertToUpperCase(true);
return mapper;
}
}
SecurityConfig class defines which pages should be protected in our web application and SecurityConfigActiveDirectory defines the LDAP authentication provider.
ldap.domain can be something like private.myTest.de and ldap.url something like ldap://myLdapHost.private.myTest.de:389.
Cheers!