Spring Security – JSON Web-Token-Authentifizierung (3/3)

4. Sicherheitseinstellungen

SecurityConfig

SecurityConfig ist die Hauptklasse für die Security-Einstellungen. In der zweiten configure()-Methode bestimmt man welche Pfäde öffentlich erreichbar sind und für welche Nutzer angemeldet sein müssen. Des Weiteren werden Filter für die Request-Verfizierung umgesetzt. Da die Applikation ein separates Frontend hat, das auf einem anderen Port erreichbar ist, sind die allowedOrigins-Parameter freigeschaltet. So sind „externe“ Anfragen möglich.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RestSecurityErrorHandler restSecurityErrorHandler;

    @Bean
    public JwtAuthTokenFilter authenticationJwtTokenFilter() {
        return new JwtAuthTokenFilter();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                .antMatchers(HttpMethod.GET, CLAIM_TYPES_URL).permitAll()
                .antMatchers(HttpMethod.POST, CREATE_CLAIM_URL).permitAll()
                .antMatchers(HttpMethod.GET, EMAIL_CHECK_URL).permitAll()
                .antMatchers(HttpMethod.POST, LOGIN_URL).permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().authenticationEntryPoint(restSecurityErrorHandler)
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(
                authenticationJwtTokenFilter(),
                UsernamePasswordAuthenticationFilter.class
        );
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:4200");
        config.setAllowedMethods(Collections.singletonList("*"));
        config.setAllowedHeaders(Collections.singletonList("*"));

        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

JwtAuthTokenFilter

@Slf4j
public class JwtAuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtProvider jwtProvider;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    public static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthTokenFilter.class);

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        try {
            String jwt = getJwt(httpServletRequest);

            if(jwt != null && jwtProvider.validateToken(jwt)) {
                String username = jwtProvider.getUsernameFromToken(jwt);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails,
                                null,
                                userDetails.getAuthorities()
                        );

                authenticationToken.setDetails(new WebAuthenticationDetailsSource()
                        .buildDetails(httpServletRequest));

                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }

        } catch (Exception e) {
            LOGGER.error("Cannot set user authentication -> Message: {}", e);
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    private String getJwt(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.replace("Bearer ", "");
        }
        return null;
    }
}

SecurityConstants

public class SecurityConstants {
    public static final String SECRET = "SecretKeyToGenJWTs";
    public static final long EXPIRATION_DATE = 800000000L;
    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_STRING = "Authorization";
    public static final String SIGN_UP_URL = "/v1/claimapp/users/register";
    public static final String EMAIL_CHECK_URL = "/v1/claimapp/usercheck";
    public static final String CLAIM_TYPES_URL = "/v1/claimapp/claimtypes";
    public static final String CREATE_CLAIM_URL = "/v1/claimapp/claims";
    public static final String LOGIN_URL = "/auth/login";
}

SecurityInitializer

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}

UserDetailsServiceImpl:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private ApplicationUserRepository applicationUserRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ApplicationUser applicationUser = applicationUserRepository
                .findByUsername(username)
                .orElseThrow(
                        () -> new UsernameNotFoundException("User not found")
                );

        return UserDetailsImpl.build(applicationUser);
    }
}

Zusammenfassung

Die Implementierung eines Authentifizierungsprozesses ist ein umfangreiches Thema und auf keinen Fall eine triviale Aufgabe. JWTs haben in den letzten Jahren an Popularität gewonnen. Man sollte aber wissen, dass sie nicht immer optimal sind. Ein großes Problem bei den Tokens sind einige Sicherheitslücken, die bei sessionbasierten Implementierungen nicht so gravierend sind. Wird ein Token gestohlen, kann ein größerer Schaden entstehen, da sie im Gegensatz zu Sessions schwieriger zu deaktivieren sind. Deswegen werden die JWTs weiterentwickelt, um die Sicherheit zu verbessern.  Momentan wird an einem neuen Standard gearbeitet – PASETO (Platform-Agnostic Security Tokens), der aktuelle Sicherheitsprobleme von Tokens beheben sollte.

Beim Verwenden von JWTs ist es wichtig, sich an bestimmte Regeln zu halten:

  • Halte die Tokens nie in local- oder session-Storage des Browsers – schädliche Skripte können Tokens stehlen!
  • Tokens sollten möglichst kurzlebig sein – das verringert das Risiko von potenziellen Schäden!
  • Kenne den Alogrithmus, mit dem du die JWTs encodierst – beachte die Anforderungen, um die Tokens sicher zu verschlüsseln!

Und ganz wichtig: Unabhängig davon, welches Authentifizierungsmodell gewählt wurde, sollte man nie vergessen, eine Web-App durch ein HTTPS-Zertifikat zu sichern – das ist eine der wichtigsten Vorkehrungen, um sich vor Gefahren zu schützen.  

Links

Mehr über JWTs und Implementationsbeispiele:

https://jwt.io/introduction/

Angular Spring Boot JWT Authentication example | Angular 6 + Spring Security + MySQL Full Stack – Part 2: Build Backend

https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/

https://developer.okta.com/blog/2018/06/20/what-happens-if-your-jwt-is-stolen

https://paseto.io/


Spring Security – JSON Web-Token-Authentifizierung (1/3) | Spring Security – JSON Web-Token-Authentifizierung (2/3)

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

%d Bloggern gefällt das: