com.appspot.potlachkk.config.WebSecurityConfig.java Source code

Java tutorial

Introduction

Here is the source code for com.appspot.potlachkk.config.WebSecurityConfig.java

Source

package com.appspot.potlachkk.config;

/*
 * Potlach - Coursea POSA Capstone Project
 * Copyright (C) 2014  KK
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version. 
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.savedrequest.NullRequestCache;

import com.appspot.potlachkk.auth.LoginStatusBuilder;
import com.appspot.potlachkk.auth.LoginStatusBuilder.LoginStatusCode;
import com.appspot.potlachkk.repository.PotlachUserDetailsService;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // This anonymous inner class' onAuthenticationSuccess() method is invoked
    // whenever a client successfully logs in. The class just sends back an
    // HTTP 200 OK status code to the client so that they know they logged
    // in correctly. The class does not redirect the client anywhere like the
    // default handler does with a HTTP 302 response. The redirect has been
    // removed to be friendlier to mobile clients and Retrofit.
    private static final AuthenticationSuccessHandler NO_REDIRECT_SUCCESS_HANDLER = new AuthenticationSuccessHandler() {

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws IOException, ServletException {
            response.setStatus(HttpStatus.SC_OK);
            response.setContentType("application/json");
            LoginStatusBuilder lsb = new LoginStatusBuilder(LoginStatusCode.LOGIN_OK, "you are logged in");
            response.getWriter().write(lsb.toJson());
        }
    };

    private static final AuthenticationFailureHandler NO_REDIRECT_FAILURE_HANDLER = new AuthenticationFailureHandler() {

        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException authentication) throws IOException, ServletException {

            response.setStatus(HttpStatus.SC_UNAUTHORIZED);
            response.setContentType("application/json");
            LoginStatusBuilder lsb = new LoginStatusBuilder(LoginStatusCode.LOGIN_FAILURE, "bad credentials");
            response.getWriter().write(lsb.toJson());
        }

    };

    // Normally, the logout success handler redirects the client to the login page. We
    // just want to let the client know that it successfully logged out and make the
    // response a bit of JSON so that Retrofit can handle it. The handler sends back
    // a 200 OK response and an empty JSON object.
    private static final LogoutSuccessHandler JSON_LOGOUT_SUCCESS_HANDLER = new LogoutSuccessHandler() {
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws IOException, ServletException {

            response.setStatus(HttpStatus.SC_OK);
            response.setContentType("application/json");
            LoginStatusBuilder lsb = new LoginStatusBuilder(LoginStatusCode.LOGIN_OK, "you are logged out");
            response.getWriter().write(lsb.toJson());
        }
    };

    //I don't need any login form, just kind of confirmation using JSON
    private static final AuthenticationEntryPoint JSON_AUTHENTICATION_ENTRY_POINT = new AuthenticationEntryPoint() {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException authException) throws IOException, ServletException {

            response.setHeader("Cache-Control", "no-cache");
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);

            response.setContentType("application/json");
            LoginStatusBuilder lsb = new LoginStatusBuilder(LoginStatusCode.NOT_AUTHORIZED, "you need to log in");
            response.getWriter().write(lsb.toJson());
        }
    };

    //The name of the configureGlobal method is not important. However, 
    //it is important to only configure AuthenticationManagerBuilder in a 
    //class annotated with either @EnableWebSecurity, @EnableWebMvcSecurity, 
    //@EnableGlobalMethodSecurity, or @EnableGlobalAuthentication. 
    //Doing otherwise has unpredictable results.

    @Autowired
    private PotlachUserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // We don't want to cache requests during login
        http.requestCache().requestCache(new NullRequestCache());

        //I am not sure if this configuration is not a "work-aroud"
        //maybe there is a simpler/more elegant solution

        //Avoid CSRF token related problems with mobile clients
        http.csrf().disable();

        //if attempt to access protected URL without authentication
        //send the client HTTP code (instead of redirecting to login form)
        //now to login a POST to /login with password=pass1&username=user1 
        //Content-Type: application/x-www-form-urlencoded must be sent
        http.exceptionHandling().authenticationEntryPoint(JSON_AUTHENTICATION_ENTRY_POINT);

        http.formLogin().successHandler(NO_REDIRECT_SUCCESS_HANDLER).failureHandler(NO_REDIRECT_FAILURE_HANDLER)
                .permitAll().and().logout().logoutUrl("/logout").logoutSuccessHandler(JSON_LOGOUT_SUCCESS_HANDLER)
                .deleteCookies("JSESSIONID").invalidateHttpSession(true).permitAll();

        //GAE - specific localhost maintenance URL
        http.authorizeRequests().antMatchers("/_ah/**").permitAll();

        //configuration URL - should be disabled in production
        http.authorizeRequests().antMatchers("/config").permitAll();
        http.authorizeRequests().antMatchers("/delconfig").permitAll();

        //test
        http.authorizeRequests().antMatchers("/image/**").permitAll();
        //http.authorizeRequests().antMatchers("/chain/**").permitAll();
        //http.authorizeRequests().antMatchers("/gift/**").permitAll();

        http.authorizeRequests().anyRequest().authenticated();
    }

}