...
 
Commits (6)
......@@ -266,10 +266,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
providers.add(INTERNAL_PROVIDER_ID);
auth.authenticationProvider(daoAuthenticationProvider());
}
boolean oauthOrOidcProvider = false;
if (providerProperties.getOidc().stream().anyMatch(p -> p.isEnabled())) {
oauthOrOidcProvider = true;
providers.add(OIDC_PROVIDER_ID);
}
if (providerProperties.getOauth().values().stream().anyMatch(p -> p.isEnabled())) {
oauthOrOidcProvider = true;
if (providerProperties.getOauth().containsKey(GOOGLE_PROVIDER_ID)
&& providerProperties.getOauth().get(GOOGLE_PROVIDER_ID).isEnabled()) {
providers.add(GOOGLE_PROVIDER_ID);
......@@ -282,6 +285,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
&& providerProperties.getOauth().get(TWITTER_PROVIDER_ID).isEnabled()) {
providers.add(TWITTER_PROVIDER_ID);
}
}
if (oauthOrOidcProvider) {
auth.authenticationProvider(oauthAuthenticationProvider());
}
logger.info("Enabled authentication providers: {}", providers);
......@@ -578,6 +583,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
final OauthCallbackFilter callbackFilter = new OauthCallbackFilter(oauthConfig());
callbackFilter.setAuthenticationManager(authenticationManager());
callbackFilter.setFilterProcessesUrl("/**" + OAUTH_CALLBACK_PATH_SUFFIX);
callbackFilter.setAuthenticationSuccessHandler(successHandler());
callbackFilter.setAuthenticationFailureHandler(failureHandler());
return callbackFilter;
}
......
......@@ -18,44 +18,73 @@
package de.thm.arsnova.controller;
import java.io.IOException;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.pac4j.core.context.J2EContext;
import org.pac4j.oidc.client.OidcClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.RedirectView;
import de.thm.arsnova.config.SecurityConfig;
import de.thm.arsnova.model.ClientAuthentication;
import de.thm.arsnova.model.LoginCredentials;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.security.LoginAuthenticationSucessHandler;
import de.thm.arsnova.service.UserService;
@RestController
@RequestMapping("/auth")
public class AuthenticationController {
private UserService userService;
private OidcClient oidcClient;
private CasAuthenticationEntryPoint casEntryPoint;
public AuthenticationController(final UserService userService) {
this.userService = userService;
}
@PostMapping("/login")
public ClientAuthentication login() {
return userService.getCurrentClientAuthentication();
@Autowired(required = false)
public void setOidcClient(final OidcClient oidcClient) {
this.oidcClient = oidcClient;
}
@PostMapping("/login/registered")
public ClientAuthentication loginRegistered(@RequestBody final LoginCredentials loginCredentials,
final HttpServletRequest request) {
final String loginId = loginCredentials.getLoginId().toLowerCase();
userService.authenticate(new UsernamePasswordAuthenticationToken(loginId, loginCredentials.getPassword()),
UserProfile.AuthProvider.ARSNOVA, request.getRemoteAddr());
return userService.getCurrentClientAuthentication();
@Autowired(required = false)
public void setCasEntryPoint(final CasAuthenticationEntryPoint casEntryPoint) {
this.casEntryPoint = casEntryPoint;
}
@PostMapping("/login")
public ClientAuthentication login(@RequestParam(defaultValue = "false") final boolean refresh,
final HttpServletRequest request, final HttpServletResponse response) {
if (request.getCookies() != null && Arrays.stream(request.getCookies())
.anyMatch(c -> c.getName().equalsIgnoreCase(LoginAuthenticationSucessHandler.AUTH_COOKIE_NAME))) {
/* Delete cookie */
final Cookie cookie = new Cookie(LoginAuthenticationSucessHandler.AUTH_COOKIE_NAME, null);
cookie.setPath(request.getContextPath());
cookie.setMaxAge(0);
response.addCookie(cookie);
}
return userService.getCurrentClientAuthentication(refresh);
}
@PostMapping("/login/guest")
public ClientAuthentication loginGuest(final HttpServletRequest request) {
final ClientAuthentication currentAuthentication = userService.getCurrentClientAuthentication();
final ClientAuthentication currentAuthentication = userService.getCurrentClientAuthentication(false);
if (currentAuthentication != null
&& currentAuthentication.getAuthProvider() == UserProfile.AuthProvider.ARSNOVA_GUEST) {
return currentAuthentication;
......@@ -63,6 +92,51 @@ public class AuthenticationController {
userService.authenticate(new UsernamePasswordAuthenticationToken(null, null),
UserProfile.AuthProvider.ARSNOVA_GUEST, request.getRemoteAddr());
return userService.getCurrentClientAuthentication();
return userService.getCurrentClientAuthentication(false);
}
@PostMapping("/login/{providerId}")
public ClientAuthentication loginViaProvider(
@PathVariable final String providerId,
@RequestBody final LoginCredentials loginCredentials,
final HttpServletRequest request) {
switch (providerId) {
case "registered":
final String loginId = loginCredentials.getLoginId().toLowerCase();
userService.authenticate(new UsernamePasswordAuthenticationToken(
loginId, loginCredentials.getPassword()),
UserProfile.AuthProvider.ARSNOVA, request.getRemoteAddr());
return userService.getCurrentClientAuthentication(false);
case SecurityConfig.LDAP_PROVIDER_ID:
userService.authenticate(new UsernamePasswordAuthenticationToken(
loginCredentials.getLoginId(), loginCredentials.getPassword()),
UserProfile.AuthProvider.LDAP, request.getRemoteAddr());
return userService.getCurrentClientAuthentication(false);
default:
throw new IllegalArgumentException("Invalid provider ID.");
}
}
@GetMapping("/sso/{providerId}")
public View redirectToSso(@PathVariable final String providerId,
final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
switch (providerId) {
case SecurityConfig.OIDC_PROVIDER_ID:
if (oidcClient == null) {
throw new IllegalArgumentException("Invalid provider ID.");
}
return new RedirectView(
oidcClient.getRedirectAction(new J2EContext(request, response)).getLocation());
case SecurityConfig.CAS_PROVIDER_ID:
if (casEntryPoint == null) {
throw new IllegalArgumentException("Invalid provider ID.");
}
casEntryPoint.commence(request, response, null);
return null;
default:
throw new IllegalArgumentException("Invalid provider ID.");
}
}
}
......@@ -66,6 +66,8 @@ import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.ServiceDescription;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.model.migration.v2.ClientAuthentication;
import de.thm.arsnova.security.LoginAuthenticationFailureHandler;
import de.thm.arsnova.security.LoginAuthenticationSucessHandler;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
......@@ -208,8 +210,8 @@ public class AuthenticationController extends AbstractController {
}
}
request.getSession().setAttribute("ars-login-success-url", serverUrl + successUrl);
request.getSession().setAttribute("ars-login-failure-url", serverUrl + failureUrl);
request.getSession().setAttribute(LoginAuthenticationSucessHandler.URL_ATTRIBUTE, serverUrl + successUrl);
request.getSession().setAttribute(LoginAuthenticationFailureHandler.URL_ATTRIBUTE, serverUrl + failureUrl);
if (casProperties.isEnabled() && "cas".equals(type)) {
casEntryPoint.commence(request, response, null);
......
......@@ -28,7 +28,7 @@ public class AuthenticationProvider {
public enum Type {
ANONYMOUS,
USERNAME_PASSWORD,
EXTERNAL
SSO
}
private String id;
......@@ -51,7 +51,7 @@ public class AuthenticationProvider {
|| provider instanceof AuthenticationProviderProperties.Ldap) {
type = Type.USERNAME_PASSWORD;
} else {
type = Type.EXTERNAL;
type = Type.SSO;
}
}
......
......@@ -34,6 +34,7 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
*/
public class LoginAuthenticationFailureHandler extends
SimpleUrlAuthenticationFailureHandler {
public static final String URL_ATTRIBUTE = "ars-login-failure-url";
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private String failureUrl;
......@@ -45,8 +46,8 @@ public class LoginAuthenticationFailureHandler extends
final AuthenticationException exception
) throws IOException, ServletException {
final HttpSession session = request.getSession();
if (session != null && session.getAttribute("ars-login-failure-url") != null) {
failureUrl = (String) session.getAttribute("ars-login-failure-url");
if (session != null && session.getAttribute(URL_ATTRIBUTE) != null) {
failureUrl = (String) session.getAttribute(URL_ATTRIBUTE);
}
redirectStrategy.sendRedirect(request, response, failureUrl);
......
......@@ -18,29 +18,65 @@
package de.thm.arsnova.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import de.thm.arsnova.security.jwt.JwtService;
/**
* This class gets called when a user successfully logged in.
*/
public class LoginAuthenticationSucessHandler extends
SimpleUrlAuthenticationSuccessHandler {
public static final String AUTH_COOKIE_NAME = "auth";
public static final String URL_ATTRIBUTE = "ars-login-success-url";
private static final String AUTHENTICATION_ATTRIBUTE = "authentication";
private JwtService jwtService;
private String targetUrl;
@Autowired
public void setJwtService(final JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected String determineTargetUrl(
final HttpServletRequest request,
final HttpServletResponse response) {
final HttpSession session = request.getSession();
if (session == null || session.getAttribute("ars-login-success-url") == null) {
return targetUrl;
}
final HttpSession session = request.getSession(false);
final String url = (String) session.getAttribute(URL_ATTRIBUTE);
session.removeAttribute(URL_ATTRIBUTE);
return (String) session.getAttribute("ars-login-success-url");
return url;
}
@Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws IOException, ServletException {
final HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(URL_ATTRIBUTE) == null) {
final String token = jwtService.createSignedToken((User) authentication.getPrincipal(), true);
final Cookie cookie = new Cookie(AUTH_COOKIE_NAME, token);
cookie.setPath(request.getContextPath());
cookie.setSecure(request.isSecure());
cookie.setHttpOnly(true);
response.addCookie(cookie);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().println("<!DOCTYPE html><script>if (window.opener) window.close()</script>");
return;
}
request.setAttribute(AUTHENTICATION_ATTRIBUTE, authentication);
super.onAuthenticationSuccess(request, response, authentication);
}
public void setTargetUrl(final String url) {
......
......@@ -46,6 +46,7 @@ public class JwtService {
private String serverId;
private TemporalAmount defaultValidityPeriod;
private TemporalAmount guestValidityPeriod;
private TemporalAmount temporaryValidityPeriod;
private JWTVerifier verifier;
private UserService userService;
......@@ -56,19 +57,21 @@ public class JwtService {
this.serverId = securityProperties.getJwt().getServerId();
this.defaultValidityPeriod = securityProperties.getJwt().getValidityPeriod();
guestValidityPeriod = Duration.parse("P180D");
temporaryValidityPeriod = Duration.parse("PT30S");
algorithm = Algorithm.HMAC256(securityProperties.getJwt().getSecret());
verifier = JWT.require(algorithm)
.withAudience(serverId)
.build();
}
public String createSignedToken(final User user) {
public String createSignedToken(final User user, final boolean temporary) {
final String[] roles = user.getAuthorities().stream()
.map(ga -> ga.getAuthority())
.filter(ga -> ga.startsWith(ROLE_PREFIX))
.map(ga -> ga.substring(ROLE_PREFIX.length())).toArray(String[]::new);
final TemporalAmount expiresAt = user.getAuthProvider() == UserProfile.AuthProvider.ARSNOVA_GUEST
? guestValidityPeriod : defaultValidityPeriod;
final TemporalAmount expiresAt = temporary ? temporaryValidityPeriod
: (user.getAuthProvider() == UserProfile.AuthProvider.ARSNOVA_GUEST
? guestValidityPeriod : defaultValidityPeriod);
return JWT.create()
.withIssuer(serverId)
.withAudience(serverId)
......
......@@ -19,12 +19,15 @@
package de.thm.arsnova.security.jwt;
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -35,6 +38,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import de.thm.arsnova.security.LoginAuthenticationSucessHandler;
@Component
public class JwtTokenFilter extends GenericFilterBean {
private static final Pattern BEARER_TOKEN_PATTERN = Pattern.compile("Bearer (.*)", Pattern.CASE_INSENSITIVE);
......@@ -51,28 +56,44 @@ public class JwtTokenFilter extends GenericFilterBean {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
JwtToken token = null;
final String jwtHeader = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
if (jwtHeader != null) {
final Matcher tokenMatcher = BEARER_TOKEN_PATTERN.matcher(jwtHeader);
if (tokenMatcher.matches()) {
final JwtToken token = new JwtToken(tokenMatcher.group(1));
try {
final Authentication authenticatedToken = jwtAuthenticationProvider.authenticate(token);
if (authenticatedToken != null) {
logger.debug("Storing JWT to SecurityContext: {}", authenticatedToken);
SecurityContextHolder.getContext().setAuthentication(authenticatedToken);
} else {
logger.debug("Could not authenticate JWT.");
}
} catch (final Exception e) {
logger.debug("JWT authentication failed", e);
}
token = new JwtToken(tokenMatcher.group(1));
} else {
logger.debug("Unsupported authentication scheme.");
}
} else {
logger.debug("No authentication header present.");
/* Look for auth cookie if Authorization header is not present. */
if (httpServletRequest.getCookies() != null) {
final Optional<Cookie> cookie = Arrays.stream(httpServletRequest.getCookies())
.filter(c -> c.getName().equalsIgnoreCase(LoginAuthenticationSucessHandler.AUTH_COOKIE_NAME))
.findFirst();
if (cookie.isPresent()) {
logger.debug("Trying to use authentication from cookie.");
token = new JwtToken(cookie.get().getValue());
}
}
}
if (token != null) {
try {
final Authentication authenticatedToken = jwtAuthenticationProvider.authenticate(token);
if (authenticatedToken != null) {
logger.debug("Storing JWT to SecurityContext: {}", authenticatedToken);
SecurityContextHolder.getContext().setAuthentication(authenticatedToken);
} else {
logger.debug("Could not authenticate JWT.");
}
} catch (final Exception e) {
logger.debug("JWT authentication failed", e);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
......
......@@ -39,7 +39,7 @@ public interface UserService extends EntityService<UserProfile> {
User getCurrentUser();
de.thm.arsnova.model.ClientAuthentication getCurrentClientAuthentication();
de.thm.arsnova.model.ClientAuthentication getCurrentClientAuthentication(boolean refresh);
boolean isAdmin(String username);
......
......@@ -203,14 +203,14 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple
}
@Override
public de.thm.arsnova.model.ClientAuthentication getCurrentClientAuthentication() {
public de.thm.arsnova.model.ClientAuthentication getCurrentClientAuthentication(final boolean refresh) {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !(authentication.getPrincipal() instanceof User)) {
return null;
}
final User user = (User) authentication.getPrincipal();
final String jwt = authentication instanceof JwtToken
? (String) authentication.getCredentials() : jwtService.createSignedToken(user);
final String jwt = !refresh && authentication instanceof JwtToken
? (String) authentication.getCredentials() : jwtService.createSignedToken(user, false);
final ClientAuthentication clientAuthentication =
new ClientAuthentication(user.getId(), user.getUsername(),
......