Java tutorial
/* * 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(); } }