de.whs.poodle.security.SpringSecurityConfig.java Source code

Java tutorial

Introduction

Here is the source code for de.whs.poodle.security.SpringSecurityConfig.java

Source

/*
 * Copyright 2015 Westflische Hochschule
 *
 * This file is part of Poodle.
 *
 * Poodle 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.
 *
 * Poodle 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 Poodle.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.whs.poodle.security;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;

import de.whs.poodle.beans.Instructor;
import de.whs.poodle.beans.Student;
import de.whs.poodle.repositories.InstructorRepository;
import de.whs.poodle.repositories.StudentRepository;
import de.whs.poodle.repositories.exceptions.ForbiddenException;

/*
 * Configuration for Spring Security. Contains all logic
 * regarding login, privileges, user switching etc.
 *
 * docs:
 * http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc
 *
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig {

    private static final Logger log = LoggerFactory.getLogger(SpringSecurityConfig.class);

    @Autowired
    private StudentRepository studentRepo;

    @Autowired
    private InstructorRepository instructorRepo;

    @Autowired
    private List<PoodleAuthenticationProvider> authProviders;

    /*
     * WebSecurityConfigurerAdapter to configure login page, site access etc.
     */
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    /* override header config so that iframes are allowed within the same
                     * origin. This is necessary for our "link exercise" CKEditor plugin
                     * which opens the search in an iframe.
                     * see alsohttp://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#headers */
                    .headers().contentTypeOptions().xssProtection().cacheControl().httpStrictTransportSecurity()
                    .addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN)).and()
                    /* define who can access which sites with which roles */
                    .authorizeRequests().antMatchers("/img/**", "/css/**", "/js/**", "/about", "/error").permitAll()
                    // also allow instructor access to the exercise links generated by the "link exercise" CKEditor plugin
                    .antMatchers("/student/exercises/*").hasAnyRole("INSTRUCTOR", "STUDENT")
                    .antMatchers("/student/**").hasRole("STUDENT").antMatchers("/instructor/**")
                    .hasRole("INSTRUCTOR").antMatchers("/switchUser").hasRole("INSTRUCTOR") // see SwitchUserFilter below
                    .antMatchers("/").permitAll().anyRequest().authenticated().and()
                    // define login page
                    .formLogin().loginPage("/").permitAll().successHandler(new PoodleLoginSuccessHandler()).and()
                    /* Disable CSRF protection. In order for this to work correctly, we would have to
                     * refactor all our POST requests to send the CSRF token which is not worth the effort. */
                    .csrf().disable();
        }

        /* This is called after a successful login. It deletes the fake student for an instructor (if any) and takes care
         * of the "last login" timestamp for the instructor. */
        private class PoodleLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                    Authentication authentication) throws ServletException, IOException {
                log.debug("Login successful");
                boolean isInstructor = authentication.getAuthorities()
                        .contains(new SimpleGrantedAuthority("ROLE_INSTRUCTOR"));

                if (isInstructor) {
                    String username = authentication.getName();
                    Instructor instructor = instructorRepo.getByUsername(username);

                    // delete fake student
                    log.debug("instructor {} logged in, deleting fake student", username);
                    boolean existed = studentRepo.dropFakeStudent(instructor.getId());
                    log.debug("fake student existed: {}", existed);

                    // update "last login" timestamp and store the old one in the session
                    Date lastLogin = instructorRepo.updateLastLoginAndGetPrevious(instructor.getId());
                    request.getSession().setAttribute("lastLogin", lastLogin);
                }

                super.onAuthenticationSuccess(request, response, authentication);
            }
        }
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        // configure the authentication methods with the defined PoodleAuthenticationProviders
        for (PoodleAuthenticationProvider ap : authProviders) {
            log.info("Configuration authentication with {}", ap.getClass().getSimpleName());
            ap.configure(auth);
        }
    }

    /*
     * SwitchUserFilter which allows switching the user for admins and also provides
     * the student mode functionality.
     */
    @Bean
    public SwitchUserFilter switchUserFilter() {
        SwitchUserFilter filter = new SwitchUserFilter();
        filter.setTargetUrl("/");
        filter.setSwitchUserUrl("/switchUser");
        filter.setExitUserUrl("/exitUser");
        filter.setSwitchFailureUrl("/?switchUserFailed=1");

        /*
         * Called when a user is switched and returns the UserDetails.
         */
        filter.setUserDetailsService(username -> {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();

            /* If no username is specified, we interpret this as "student mode"
            * (see <form> in instructor/navItems.html). */
            if (username.isEmpty()) {
                // get the logged in student
                Instructor instructor = instructorRepo.getByUsername(auth.getName());

                log.debug("{} switched to student mode", instructor.getUsername());

                // create the fake student and switch
                Student fakeStudent = studentRepo.createFakeStudent(instructor.getId());

                ArrayList<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add(new SimpleGrantedAuthority("ROLE_STUDENT"));
                authorities.add(new SimpleGrantedAuthority("ROLE_FAKE_STUDENT"));
                return new User(fakeStudent.getUsername(), "password", authorities);
            } else { // switch to specified user (admins only)
                boolean isAdmin = auth.getAuthorities().contains(new SimpleGrantedAuthority("ROLE_ADMIN"));
                if (!isAdmin)
                    throw new ForbiddenException();

                log.debug("User {} switching to {}", auth.getName(), username);
                ArrayList<GrantedAuthority> authorities = new ArrayList<>();

                /*
                 *   username is the user that we switched to. We have no information
                 *   on whether he is a student or an instructor. Since he must be
                 *   in the database, let's just check there.
                 */
                if (studentRepo.studentExists(username))
                    authorities.add(new SimpleGrantedAuthority("ROLE_STUDENT"));
                else if (instructorRepo.exists(username))
                    authorities.add(new SimpleGrantedAuthority("ROLE_INSTRUCTOR"));
                else
                    throw new UsernameNotFoundException("user doesn't exist.");

                return new User(username, "password", authorities);
            }
        });

        return filter;
    }

    @Bean
    public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
        return new WebSecurityConfig();
    }
}