Skip to content

Commit 3c0ddf7

Browse files
committed
Jwt classes
1 parent 2ca9742 commit 3c0ddf7

File tree

10 files changed

+310
-0
lines changed

10 files changed

+310
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.reflectoring.security.config;
2+
3+
import org.springframework.security.core.AuthenticationException;
4+
import org.springframework.security.web.AuthenticationEntryPoint;
5+
import org.springframework.stereotype.Component;
6+
7+
import javax.servlet.ServletException;
8+
import javax.servlet.http.HttpServletRequest;
9+
import javax.servlet.http.HttpServletResponse;
10+
import java.io.IOException;
11+
12+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
13+
14+
@Component
15+
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
16+
@Override
17+
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
18+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
19+
response.setContentType(APPLICATION_JSON_VALUE);
20+
Exception exception = (Exception) request.getAttribute("exception");
21+
String message;
22+
if (exception != null) {
23+
24+
} else {
25+
if (authException.getCause() != null) {
26+
message = authException.getCause().toString();
27+
} else {
28+
29+
}
30+
}
31+
}
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.reflectoring.security.config;
2+
3+
import lombok.Data;
4+
import org.springframework.boot.context.properties.ConfigurationProperties;
5+
import org.springframework.context.annotation.Configuration;
6+
7+
@ConfigurationProperties(prefix = "jwt")
8+
@Configuration
9+
@Data
10+
public class JwtProperties {
11+
12+
private String secretKey;
13+
private long validity;
14+
private long refreshExpiry;
15+
16+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.reflectoring.security.config;
2+
3+
public class SecurityConfiguration {
4+
}

spring-security-jwt/getting-started/SecureApplication/src/main/java/com/reflectoring/security/filter/JwtFilter.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
package com.reflectoring.security.filter;
22

3+
import com.reflectoring.security.jwt.JwtHelper;
4+
import com.reflectoring.security.service.AuthUserDetailsService;
5+
import io.jsonwebtoken.ExpiredJwtException;
6+
import io.jsonwebtoken.MalformedJwtException;
7+
import io.jsonwebtoken.UnsupportedJwtException;
38
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.security.authentication.BadCredentialsException;
10+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
11+
import org.springframework.security.core.context.SecurityContext;
12+
import org.springframework.security.core.context.SecurityContextHolder;
13+
import org.springframework.security.core.userdetails.UserDetails;
14+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
15+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
416
import org.springframework.stereotype.Component;
517
import org.springframework.web.filter.OncePerRequestFilter;
618

@@ -9,6 +21,7 @@
921
import javax.servlet.http.HttpServletRequest;
1022
import javax.servlet.http.HttpServletResponse;
1123
import java.io.IOException;
24+
import java.util.Objects;
1225

1326

1427
@Component
@@ -17,9 +30,53 @@ public class JwtFilter extends OncePerRequestFilter {
1730

1831
public static final String AUTHORIZATION = "Authorization";
1932

33+
private final AuthUserDetailsService userDetailsService;
34+
35+
private final JwtHelper jwtHelper;
36+
37+
public JwtFilter(AuthUserDetailsService userDetailsService, JwtHelper jwtHelper) {
38+
this.userDetailsService = userDetailsService;
39+
this.jwtHelper = jwtHelper;
40+
}
41+
2042

2143
@Override
2244
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
45+
try {
46+
final String authorizationHeader = request.getHeader(AUTHORIZATION);
47+
String jwt = null;
48+
String username = null;
49+
if (Objects.nonNull(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
50+
jwt = authorizationHeader.substring(7);
51+
username = jwtHelper.extractUsername(jwt);
52+
}
53+
54+
if (Objects.nonNull(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
55+
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
56+
boolean isTokenValidated = jwtHelper.validateToken(jwt, userDetails);
57+
if (isTokenValidated) {
58+
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
59+
new UsernamePasswordAuthenticationToken(userDetails, null);
60+
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
61+
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
62+
}
63+
}
64+
} catch (ExpiredJwtException jwtException) {
65+
Boolean isRefreshToken = Boolean.valueOf(request.getHeader("isRefreshToken"));
66+
String requestUri = request.getRequestURI();
67+
if (Objects.nonNull(isRefreshToken) && isRefreshToken && requestUri.contains("refresh")) {
68+
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
69+
new UsernamePasswordAuthenticationToken(null, null);
70+
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
71+
request.setAttribute("claims", jwtException.getClaims());
72+
} else {
73+
request.setAttribute("exception", jwtException);
74+
}
75+
} catch (BadCredentialsException | UnsupportedJwtException | MalformedJwtException e) {
76+
request.setAttribute("exception", e);
77+
}
78+
79+
filterChain.doFilter(request, response);
2380

2481
}
2582
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.reflectoring.security.jwt;
2+
3+
import com.reflectoring.security.config.JwtProperties;
4+
import io.jsonwebtoken.*;
5+
import org.springframework.security.core.userdetails.UserDetails;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.util.CollectionUtils;
8+
9+
import javax.crypto.spec.SecretKeySpec;
10+
import java.security.Key;
11+
import java.time.Instant;
12+
import java.util.*;
13+
import java.util.function.Function;
14+
15+
@Component
16+
public class JwtHelper {
17+
18+
private final JwtProperties jwtProperties;
19+
20+
21+
public JwtHelper(JwtProperties jwtProperties) {
22+
this.jwtProperties = jwtProperties;
23+
}
24+
25+
private Jws<Claims> extractClaims(String bearerToken) {
26+
return Jwts.parserBuilder().setSigningKey(jwtProperties.getSecretKey())
27+
.build().parseClaimsJws(bearerToken);
28+
}
29+
30+
public <T> T extractClaimBody(String bearerToken, Function<Claims, T> claimsResolver) {
31+
Jws<Claims> jwsClaims = extractClaims(bearerToken);
32+
return claimsResolver.apply(jwsClaims.getBody());
33+
}
34+
35+
public <T> T extractClaimHeader(String bearerToken, Function<JwsHeader, T> claimsResolver) {
36+
Jws<Claims> jwsClaims = extractClaims(bearerToken);
37+
return claimsResolver.apply(jwsClaims.getHeader());
38+
}
39+
40+
public Date extractExpiry(String bearerToken) {
41+
return extractClaimBody(bearerToken, Claims::getExpiration);
42+
}
43+
44+
public String extractUsername(String bearerToken) {
45+
return extractClaimBody(bearerToken, Claims::getSubject);
46+
}
47+
48+
private Boolean isTokenExpired(String bearerToken) {
49+
return extractExpiry(bearerToken).before(new Date());
50+
}
51+
52+
public String createToken(Map<String, Object> claims, String subject) {
53+
Date expiryDate = Date.from(Instant.ofEpochMilli(System.currentTimeMillis() + jwtProperties.getValidity()));
54+
Key hmacKey = new SecretKeySpec(Base64.getDecoder().decode(jwtProperties.getSecretKey()),
55+
SignatureAlgorithm.HS256.getJcaName());
56+
return Jwts.builder()
57+
.setClaims(claims)
58+
.setSubject(subject)
59+
.setIssuedAt(new Date(System.currentTimeMillis()))
60+
.setExpiration(expiryDate)
61+
.signWith(hmacKey)
62+
.compact();
63+
}
64+
65+
public boolean validateToken(String token, UserDetails userDetails) {
66+
final String userName = extractUsername(token);
67+
return userName.equals(userDetails.getUsername()) && !isTokenExpired(token);
68+
}
69+
70+
public String generateRefreshToken(String subject) {
71+
Date expiryDate = Date.from(Instant.ofEpochMilli(System.currentTimeMillis() + jwtProperties.getRefreshExpiry()));
72+
Key hmacKey = new SecretKeySpec(Base64.getDecoder().decode(jwtProperties.getSecretKey()),
73+
SignatureAlgorithm.HS256.getJcaName());
74+
return Jwts.builder()
75+
.setClaims(Collections.EMPTY_MAP)
76+
.setSubject(subject)
77+
.setIssuedAt(new Date(System.currentTimeMillis()))
78+
.setExpiration(expiryDate)
79+
.signWith(hmacKey)
80+
.compact();
81+
}
82+
83+
84+
85+
86+
87+
88+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.reflectoring.security.model;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
@Data
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
@Builder
12+
public class TokenRequest {
13+
private String username;
14+
private String password;
15+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.reflectoring.security.model;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
@Data
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
@Builder
12+
public class TokenResponse {
13+
14+
private String token;
15+
16+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.reflectoring.security.service;
2+
3+
import com.reflectoring.security.jwt.JwtHelper;
4+
import com.reflectoring.security.model.TokenRequest;
5+
import com.reflectoring.security.model.TokenResponse;
6+
import org.apache.catalina.User;
7+
import org.springframework.security.authentication.AuthenticationManager;
8+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9+
import org.springframework.security.core.userdetails.UserDetails;
10+
import org.springframework.security.core.userdetails.UserDetailsService;
11+
import org.springframework.stereotype.Service;
12+
13+
import java.util.Collections;
14+
15+
@Service
16+
public class TokenService {
17+
18+
private final AuthenticationManager authenticationManager;
19+
20+
private final AuthUserDetailsService userDetailsService;
21+
22+
private final JwtHelper jwtHelper;
23+
24+
public TokenService(AuthenticationManager authenticationManager,
25+
AuthUserDetailsService userDetailsService,
26+
JwtHelper jwtHelper) {
27+
this.authenticationManager = authenticationManager;
28+
this.userDetailsService = userDetailsService;
29+
this.jwtHelper = jwtHelper;
30+
}
31+
32+
33+
public TokenResponse generateToken(TokenRequest tokenRequest) {
34+
this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(tokenRequest.getUsername(), tokenRequest.getPassword()));
35+
final UserDetails userDetails = userDetailsService.loadUserByUsername(tokenRequest.getUsername());
36+
String token = jwtHelper.createToken(Collections.emptyMap(), userDetails.getUsername());
37+
return TokenResponse.builder()
38+
.token(token)
39+
.build();
40+
}
41+
42+
public TokenResponse generateRefreshToken(String subject) {
43+
String token = jwtHelper.generateRefreshToken(subject);
44+
return TokenResponse.builder()
45+
.token(token)
46+
.build();
47+
}
48+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.reflectoring.security.web;
2+
3+
import com.reflectoring.security.model.TokenRequest;
4+
import com.reflectoring.security.model.TokenResponse;
5+
import com.reflectoring.security.service.TokenService;
6+
import org.springframework.web.bind.annotation.PostMapping;
7+
import org.springframework.web.bind.annotation.RequestAttribute;
8+
import org.springframework.web.bind.annotation.RequestBody;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
public class TokenController {
13+
14+
private final TokenService tokenService;
15+
16+
public TokenController(TokenService tokenService) {
17+
this.tokenService = tokenService;
18+
}
19+
20+
@PostMapping("/token/create")
21+
public TokenResponse createToken(@RequestBody TokenRequest tokenRequest) {
22+
return tokenService.generateToken(tokenRequest);
23+
}
24+
25+
@PostMapping("/token/refresh")
26+
public TokenResponse refreshToken(@RequestAttribute String claims) {
27+
return tokenService.generateRefreshToken(claims);
28+
}
29+
}

spring-security-jwt/getting-started/SecureApplication/src/main/resources/application.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ spring:
1212
username: sa
1313
password:
1414

15+
jwt:
16+
secretKey: 5JzoMbk6E5qIqHSuBTgeQCARtUsxAkBiHwdjXOSW8kWdXzYmP3X51C0
17+
validity: 600000
18+
refreshExpiry: 600000
19+
1520

1621
logging:
1722
level:

0 commit comments

Comments
 (0)