1

I am currently developing web application for my university. University provides me with authorisation server.

I am using rest api to provide data for my angular frontend. On backend I am using spring boot + spring security

I am using this url: /classification-login for login, so when the user access this url, he is redirected to authorisation server, logs in,..., you know how it goes. If I understand this correctly, the result is JSESSIONID saved in browser cookies and the app recognize user by the JSESSIONID and gets his username from its session.

What I need is to use the same REST API for other web apps, so user in completely different app logs in, the other app gets his access token and then uses this token to access my API. Problem is that my app only recognise user by JSESSIONID. So my question is, how do I set spring security to check user firstly by jsessionid and if not present by access token.

Thanks to anyone replying.

Java code:

@SpringBootApplication
@EnableOAuth2Sso
@ComponentScan
@ImportResource({"classpath:classification-connector.xml", "classpath:classification-security.xml"})
public class Application extends WebSecurityConfigurerAdapter {

@Override
public void configure(HttpSecurity http) throws Exception {
    http.logout().and().antMatcher("/**").authorizeRequests()
            .antMatchers("/classification-login").authenticated()
            .anyRequest().permitAll();
}

public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}
}

configuration in yaml:

debug: true
security:
  user:
    password: none
  oauth2:
    client:
      accessTokenUri: https://xxx/oauth/token
      userAuthorizationUri: https://xxx/oauth/authorize
      clientId: supersecret
      clientSecret: supersecret
      scope: read

    resource:
      tokenInfoUri: https://xxx/oauth/check_token

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.