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://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
https://developer.okta.com/blog/2018/06/20/what-happens-if-your-jwt-is-stolen
Spring Security „“ JSON Web-Token-Authentifizierung (1/3) | Spring Security „“ JSON Web-Token-Authentifizierung (2/3)