user3170702
user3170702

Reputation: 2061

AngularJS and Spring MVC security

I have a front end written in AngularJS, and a Spring MVC backend. The idea I had was to only secure the REST API services and use an interceptor in AngularJS to redirect the user to the login page when an unauthorized service call is made. The problem I'm facing now is that, while a service is called, the page is briefly displayed before the user is redirected. Is there anything I can do about that? Or is this approach fundamentally flawed?

This is the interceptor:

$httpProvider.interceptors.push(function ($q, $location) {
    return {
        'responseError': function(rejection) {
            var status = rejection.status;

            if (status == 401 || status == 403) {
                $location.path( "/login" );
            } else {
            }

            return $q.reject(rejection);
        }
    };});

My security configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/api/**")
            .authorizeRequests()
                .anyRequest().authenticated();
    }

    @Bean(name="myAuthenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

The login controller:

@RequestMapping(value = "/login", method = RequestMethod.POST, produces="application/json")
@ResponseBody
public String login(@RequestBody User user) {
    JSONObject result = new JSONObject();
    UsernamePasswordAuthenticationToken token =
            new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());

    try {

        Authentication auth = authenticationManager.authenticate(token);
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(auth);
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession session = attr.getRequest().getSession(true);
        session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);

        result.put("isauthenticated", true);
    } catch (BadCredentialsException e) {
        result.put("isauthenticated", false);
    }

    return result.toString();
}

Upvotes: 1

Views: 575

Answers (1)

Nikos Paraskevopoulos
Nikos Paraskevopoulos

Reputation: 40296

I think this approach is OK, but you may have to live with the page flash, which in turn means you will have to handle it gracefully.

I guess the page flush happens roughly as follows:

  • A navigation takes place, rendering a template and activating a controller for the new route
  • The controller calls a service; this is asynchronous, so the page without any data is displayed
  • The service returns 401/403, it is intercepted and the new navigation to the login page occurs

You may want to try:

  1. Collecting all data required by the page in the resolve configuration of the route (supported both by ngRoute and angular-ui-router), so that the navigation will not complete before all data is fetched.
  2. Handle it from within the page: while the service call is still pending, display a spinner/message whatever to let the user know that some background activity is going on.
  3. When the interceptor catches a 401/403, have it open a modal popup, explaining the situation and offering the user to login or navigate to the login page as a single option. Combine this with the spinner/message.

Upvotes: 2

Related Questions