From 189670cd44897a24ef3f9a9869db6e4ab75cfcda Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Fri, 2 Mar 2018 11:39:06 +0100 Subject: [PATCH] Wire up JWT authentication --- .../de/thm/arsnova/config/SecurityConfig.java | 18 +++++++ .../jwt/JwtAuthenticationProvider.java | 10 ++-- .../thm/arsnova/security/jwt/JwtService.java | 2 +- .../de/thm/arsnova/security/jwt/JwtToken.java | 1 + .../arsnova/security/jwt/JwtTokenFilter.java | 53 +++++++++++++------ .../de/thm/arsnova/services/UserService.java | 2 + .../thm/arsnova/services/UserServiceImpl.java | 12 +++++ 7 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/thm/arsnova/config/SecurityConfig.java b/src/main/java/de/thm/arsnova/config/SecurityConfig.java index cbb810fdd..63a018f2e 100644 --- a/src/main/java/de/thm/arsnova/config/SecurityConfig.java +++ b/src/main/java/de/thm/arsnova/config/SecurityConfig.java @@ -23,6 +23,8 @@ import de.thm.arsnova.security.LoginAuthenticationFailureHandler; import de.thm.arsnova.security.LoginAuthenticationSucessHandler; import de.thm.arsnova.security.CustomLdapUserDetailsMapper; import de.thm.arsnova.security.RegisteredUserDetailsService; +import de.thm.arsnova.security.jwt.JwtAuthenticationProvider; +import de.thm.arsnova.security.jwt.JwtTokenFilter; import de.thm.arsnova.security.pac4j.OauthCallbackFilter; import de.thm.arsnova.security.pac4j.OauthAuthenticationProvider; import org.jasig.cas.client.validation.Cas20ProxyTicketValidator; @@ -53,6 +55,7 @@ import org.springframework.security.config.annotation.method.configuration.Enabl 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.config.http.SessionCreationPolicy; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -67,6 +70,7 @@ import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator; import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; @@ -132,11 +136,13 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { + http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint()); http.csrf().disable(); http.headers() .addHeaderWriter(new HstsHeaderWriter(false)); + http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); if (casEnabled) { http.addFilter(casAuthenticationFilter()); http.addFilter(casLogoutFilter()); @@ -150,6 +156,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { List<String> providers = new ArrayList<>(); + auth.authenticationProvider(jwtAuthenticationProvider()); if (ldapEnabled) { providers.add("ldap"); auth.authenticationProvider(ldapAuthenticationProvider()); @@ -200,6 +207,17 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { return new Http403ForbiddenEntryPoint(); } + @Bean + public JwtAuthenticationProvider jwtAuthenticationProvider() { + return new JwtAuthenticationProvider(); + } + + @Bean + public JwtTokenFilter jwtTokenFilter() throws Exception { + JwtTokenFilter jwtTokenFilter = new JwtTokenFilter(); + return jwtTokenFilter; + } + @Bean LoginAuthenticationSucessHandler successHandler() { final LoginAuthenticationSucessHandler successHandler = new LoginAuthenticationSucessHandler(); diff --git a/src/main/java/de/thm/arsnova/security/jwt/JwtAuthenticationProvider.java b/src/main/java/de/thm/arsnova/security/jwt/JwtAuthenticationProvider.java index ac7d51b46..569a4df5f 100644 --- a/src/main/java/de/thm/arsnova/security/jwt/JwtAuthenticationProvider.java +++ b/src/main/java/de/thm/arsnova/security/jwt/JwtAuthenticationProvider.java @@ -1,6 +1,7 @@ package de.thm.arsnova.security.jwt; import de.thm.arsnova.security.User; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -8,10 +9,6 @@ import org.springframework.security.core.AuthenticationException; public class JwtAuthenticationProvider implements AuthenticationProvider { private JwtService jwtService; - public JwtAuthenticationProvider(final JwtService jwtService) { - this.jwtService = jwtService; - } - @Override public Authentication authenticate(final Authentication authentication) throws AuthenticationException { final String token = (String) authentication.getCredentials(); @@ -24,4 +21,9 @@ public class JwtAuthenticationProvider implements AuthenticationProvider { public boolean supports(final Class<?> aClass) { return JwtToken.class.isAssignableFrom(aClass); } + + @Autowired + public void setJwtService(final JwtService jwtService) { + this.jwtService = jwtService; + } } diff --git a/src/main/java/de/thm/arsnova/security/jwt/JwtService.java b/src/main/java/de/thm/arsnova/security/jwt/JwtService.java index f1620aaee..ee5bb47ff 100644 --- a/src/main/java/de/thm/arsnova/security/jwt/JwtService.java +++ b/src/main/java/de/thm/arsnova/security/jwt/JwtService.java @@ -71,6 +71,6 @@ public class JwtService { final Collection<GrantedAuthority> authorities = decodedJwt.getClaim(ROLES_CLAIM_NAME).asList(String.class).stream() .map(role -> new SimpleGrantedAuthority(ROLE_PREFIX + role)).collect(Collectors.toList()); - return new User(userService.get(userId), authorities); + return userService.loadUser(userId, authorities); } } diff --git a/src/main/java/de/thm/arsnova/security/jwt/JwtToken.java b/src/main/java/de/thm/arsnova/security/jwt/JwtToken.java index f7ce5cea3..ecce14b5b 100644 --- a/src/main/java/de/thm/arsnova/security/jwt/JwtToken.java +++ b/src/main/java/de/thm/arsnova/security/jwt/JwtToken.java @@ -16,6 +16,7 @@ public class JwtToken extends AbstractAuthenticationToken { super(grantedAuthorities); this.token = token; this.principal = principal; + setAuthenticated(!grantedAuthorities.isEmpty()); } public JwtToken(final String token) { diff --git a/src/main/java/de/thm/arsnova/security/jwt/JwtTokenFilter.java b/src/main/java/de/thm/arsnova/security/jwt/JwtTokenFilter.java index 282a3c16b..f8c5e2e06 100644 --- a/src/main/java/de/thm/arsnova/security/jwt/JwtTokenFilter.java +++ b/src/main/java/de/thm/arsnova/security/jwt/JwtTokenFilter.java @@ -1,29 +1,50 @@ package de.thm.arsnova.security.jwt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.GenericFilterBean; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import java.io.IOException; -public class JwtTokenFilter extends AbstractAuthenticationProcessingFilter { +@Component +public class JwtTokenFilter extends GenericFilterBean { private static final String JWT_HEADER_NAME = "Arsnova-Auth-Token"; - - protected JwtTokenFilter() { - super(new AntPathRequestMatcher("/**")); - } + private static final Logger logger = LoggerFactory.getLogger(JwtTokenFilter.class); + private JwtAuthenticationProvider jwtAuthenticationProvider; @Override - public Authentication attemptAuthentication(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws AuthenticationException { - String jwtHeader = httpServletRequest.getHeader(JWT_HEADER_NAME); - if (jwtHeader == null) { - throw new PreAuthenticatedCredentialsNotFoundException("No authentication header present."); + public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { + String jwtHeader = ((HttpServletRequest) servletRequest).getHeader(JWT_HEADER_NAME); + if (jwtHeader != null) { + JwtToken token = new JwtToken(jwtHeader); + try { + 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); + } + } else { + logger.debug("No authentication header present."); } - JwtToken token = new JwtToken(jwtHeader); + filterChain.doFilter(servletRequest, servletResponse); + } - return getAuthenticationManager().authenticate(token); + @Autowired + public void setJwtAuthenticationProvider(final JwtAuthenticationProvider jwtAuthenticationProvider) { + this.jwtAuthenticationProvider = jwtAuthenticationProvider; } } diff --git a/src/main/java/de/thm/arsnova/services/UserService.java b/src/main/java/de/thm/arsnova/services/UserService.java index 2fab0d042..814bcd84c 100644 --- a/src/main/java/de/thm/arsnova/services/UserService.java +++ b/src/main/java/de/thm/arsnova/services/UserService.java @@ -73,6 +73,8 @@ public interface UserService extends EntityService<UserProfile> { User loadUser(UserProfile.AuthProvider authProvider, String loginId, Collection<GrantedAuthority> grantedAuthorities, boolean autoCreate) throws UsernameNotFoundException; + User loadUser(String userId, Collection<GrantedAuthority> grantedAuthorities); + UserProfile getByAuthProviderAndLoginId(UserProfile.AuthProvider authProvider, String loginId); UserProfile getByUsername(String username); diff --git a/src/main/java/de/thm/arsnova/services/UserServiceImpl.java b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java index c9ce4bd48..87ef81be2 100644 --- a/src/main/java/de/thm/arsnova/services/UserServiceImpl.java +++ b/src/main/java/de/thm/arsnova/services/UserServiceImpl.java @@ -398,6 +398,18 @@ public class UserServiceImpl extends DefaultEntityServiceImpl<UserProfile> imple return new User(userProfile, grantedAuthorities); } + @Override + public User loadUser(final String userId, final Collection<GrantedAuthority> grantedAuthorities) + throws UsernameNotFoundException { + logger.debug("Load user: UserId: {}", userId); + UserProfile userProfile = userRepository.findOne(userId); + if (userProfile == null) { + throw new UsernameNotFoundException("User does not exist."); + } + + return new User(userProfile, grantedAuthorities); + } + @Override public UserProfile getByAuthProviderAndLoginId(final UserProfile.AuthProvider authProvider, final String loginId) { return userRepository.findByAuthProviderAndLoginId(authProvider, loginId); -- GitLab