I found the answer myself with help of this article: http://automateddeveloper.blogspot.cz/2014/03/securing-your-mobile-api-spring-security.html
Maybe there is a better way of doing it, but here is my solution:
I used two configuration files, first for resources accesed by both client apps and original web app and second for my login page
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
@EnableWebSecurity
@Order(1)
public class ConfigApi extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.csrf()
.disable()
.authorizeRequests().anyRequest().authenticated().and()
.addFilterBefore(new CustomFilter(), BasicAuthenticationFilter.class );
}
}
First config adds filter before every request on urls starting with /api/**
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableOAuth2Sso
@Order(2)
public class ConfigLogin extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/classification-login").authenticated();
}
}
Second config says that request on url /classification-login needs to be authenticated, but does not add any filter. That means user will be redirected to authorization server, where he logs in and spring security will save the authentication on his session (using JSESSIONID)
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
public class CustomFilter extends GenericFilterBean {
@Value("${security.oauth2.client.clientId}")
private String clientId;
@Value("${security.oauth2.resource.tokenInfoUri}")
private String checkToken;
@Value("${security.oauth2.client.scope}")
private String scope;
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
try {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final String authorization = httpServletRequest.getHeader("Authorization");
final String token = authorization.replace("Bearer ", "");
//Here I verify the user by token sent in headers (using tokenInfoUri of my authorization server)
final RestTemplate restTemplate = new RestTemplate();
final TokenInfo tokenInfo = restTemplate.getForObject(checkToken + "?token=" + token, TokenInfo.class);
final String userName = tokenInfo.getUserName();
final Set<String> scopes = new HashSet<>();
scopes.add(scope);
final OAuth2Request oAuth2Request = new OAuth2Request(Collections.<String, String>emptyMap(), clientId, null, true, scopes, null, null, null, null);
final List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
final UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userName, null, authorities);
final OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, usernamePasswordAuthenticationToken);
oAuth2Authentication.setAuthenticated(true);
final SecurityContextImpl securityContext = (SecurityContextImpl) SecurityContextHolder.getContext();
securityContext.setAuthentication(oAuth2Authentication);
} catch (Exception ignore) {
System.out.println(ignore);
}
chain.doFilter(request, response);
}
}
Here is my filter, basically I just check headers in request, read the token and authenticate user by token myself. (if there is no token, it catches the exception and continues [probably will do it better in the future, no time for it now])
Result:
If the user uses my webapp he logs in using /classification-login and then he is allowed to use the api because spring security saves his authentication on his session.
If someone wants to use the api from his app he needs to use the same authorization server in his app, get his token and pass it in request in headers.
If anyone knows a better solution feel free to comment, I spent way too much time on this, so I don't plan to investigate any further.