2. Anmeldung testen „“ Vorbereitung
Sobald wir einen Benutzer erstellt haben, können wir die Anmeldung testen. Ein LoginRequest erreicht den AuthenticationController. Dieser enthält eine Referenz auf AuthenticationManager und JwtProvider. JwtProvider ist eine Klasse, die aus der io.jsonwebtoken Biblithek stammt. Sie stellt uns einige Tools für Token Generierung und Validierung zur Verfügung.
Beim LoginRequest werden Username und Password ausgenommen. Für diesen entwickeln wir ein UsernamePasswordAuthenticationToken und der AuthenticationManager gleicht dann wie bereits erwähnt die Credentials mit denen ab, die wir in der Datenbank haben.
Sollte die Authentifizierung erfolgreich sein, setzen wir die Authentication Instanz im SecurityContext ein. Der JwtProvider benutzt das Objekt, um uns ein Token zu generieren.
Falls wir z. B. das falsche Password eingeben, wird eine Exception ausgegeben und vom RestSecurityErrorHandler übernommen.
AuthenticationController
@RestController
@RequestMapping("/auth/")
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtProvider jwtProvider;
@RequestMapping(value = "login", method = RequestMethod.POST)
public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtProvider.createToken(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getUsername()));
}
}
LoginRequest
@Data
public class LoginRequest {
private String username;
private String password;
}
JwtResponse
DerString „Bearer“ ist eine Konvention für JWTs, er wird bei Requests in die Headers gesetzt. Nach „Bearer“ folgt das Token selbst.
@Data
public class JwtResponse {
private String token;
private String type = "Bearer";
private String username;
public JwtResponse(String accessToken, String username) {
this.token = accessToken;
this.username = username;
}
}
JwtProvider
@Component
public class JwtProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtProvider.class);
public String createToken(Authentication authentication) {
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
Date expirationDate = new Date(new Date().getTime() + SecurityConstants.EXPIRATION_DATE);
String token = Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET)
.compact();
LOGGER.info("Created token: " + token);
return token;
}
public boolean validateToken(String token) {
try{
Jwts.parser().setSigningKey(SecurityConstants.SECRET).parseClaimsJws(token);
return true;
} catch (SignatureException e) {
LOGGER.error("Invalid JWT signature -> Message: {} ", e);
} catch (MalformedJwtException e) {
LOGGER.error("Invalid JWT token -> Message: {}", e);
} catch (ExpiredJwtException e) {
LOGGER.error("Expired JWT token -> Message: {}", e);
} catch (UnsupportedJwtException e) {
LOGGER.error("Unsupported JWT token -> Message: {}", e);
} catch (IllegalArgumentException e) {
LOGGER.error("JWT claims string is empty -> Message: {}", e);
}
return false;
}
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(SecurityConstants.SECRET)
.parseClaimsJws(token)
.getBody().getSubject();
}
}
UserDetailsImpl
Spring Security benötigt die Implementierung von UserDetails und UserDetailsService. Diese beiden Interfaces helfen uns, die Konten zu verwalten und z. B die Benutzerdaten anhand der Datenbank zu überprüfen.
@Getter
public class UserDetailsImpl implements UserDetails {
private Long id;
private String username;
@JsonIgnore
private String password;
private Collection authorities;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
private boolean enabled = true;
public UserDetailsImpl(Long id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public static UserDetailsImpl build(ApplicationUser user) {
return new UserDetailsImpl(
user.getId(),
user.getUsername(),
user.getPassword()
);
}
}
RestSecurityErrorHandler
Exceptions, die bei den verschiedenen Security-Vorgängen entstehen, werden von einer separaten Klasse behandelt. Der Security Container steht „vor“ den anderen Endpoints und die Ausnahmen, die hier generiert werden, z. B. bei falschen Anmeldungsdaten, werden durch die üblichen ExceptionHandlers nicht sichtbar. „“ Dafür definieren wir einen entsprechenden Handler.
@Component
public class RestSecurityErrorHandler implements AuthenticationEntryPoint {
@Autowired
private HandlerExceptionResolver resolver;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
resolver.resolveException(request, response, null, authException);
}
}
3. Anmeldung testen „“ Durchführung
Um die Anmeldung zu testen, registrieren wir zunächst einen User:
Dann schicken wir die Daten an den AuthController:
Als Antwort bekommen wir das Token.
Für den /security/v1/claimapp/claims Pfad muss man authentifiziert sein.
Ohne Token sieht das so aus:
Mit Token so:
Spring Security „“ JSON Web-Token-Authentifizierung (1/3) | Spring Security „“ JSON Web-Token-Authentifizierung (3/3)