Skip to content

Commit

Permalink
perf($POM): upgrade JJWT@0.11.1
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnymillergh committed May 5, 2020
1 parent 6706a97 commit a79dffb
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 51 deletions.
18 changes: 15 additions & 3 deletions api-portal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,28 @@
<version>1.1.22</version>
</dependency>


<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

<!-- https://github.com/jwtk/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
<artifactId>jjwt-api</artifactId>
<version>0.11.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;

/**
* <h1>JwtConfiguration</h1>
* <p>
Expand All @@ -19,8 +21,9 @@
@ConfigurationProperties(prefix = "jwt.configuration")
public class JwtConfiguration {
public JwtConfiguration(ProjectProperty projectProperty) {
this.signingKey = projectProperty.getParentArtifactId();
log.info("Initiated JWT signing key: {}", this.signingKey);
this.signingKey = String.format("%s %s", projectProperty.getParentArtifactId(), projectProperty.getVersion());
log.info("Initiated JWT signing key: {}. The specified key byte array is {} bits", this.signingKey,
this.signingKey.getBytes(StandardCharsets.UTF_8).length * 8);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}

@Override
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ public ResponseBodyBean<String> validationTest(@RequestBody ValidationTestPayloa
commonService.validateObject(payload);
return ResponseBodyBean.ofDataAndMessage(payload.getName(), "validationTest()");
}

@GetMapping("/get-jwt")
@ApiOperation(value = "/get-jwt", notes = "Get JWT")
public ResponseBodyBean<String> getJwt(String username) {
return ResponseBodyBean.ofData(commonService.generateJwt(username));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,33 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
log.info("JWT authentication is filtering [{}] client requested access. URL: {}",
RequestUtil.getRequestIpAndPort(request),
request.getServletPath());
log.info("JWT authentication is filtering [{}] client requested access. URL: {}, HTTP method: {}",
RequestUtil.getRequestIpAndPort(request), request.getServletPath(), request.getMethod());
if (customConfiguration.getWebSecurityDisabled()) {
log.warn("The web security is disabled! Might face severe web security issue.");
filterChain.doFilter(request, response);
return;
}
if (checkIgnores(request)) {
log.info("The request can be ignored. URL: {}", request.getRequestURI());
filterChain.doFilter(request, response);
return;
}
String jwt = jwtUtil.getJwtFromRequest(request);
if (StrUtil.isBlank(jwt)) {
log.error("Invalid JWT, the JWT of request is empty.");
ResponseUtil.renderJson(response, HttpStatus.UNAUTHORIZED, null);
return;
}
String username;
try {
username = jwtUtil.getUsernameFromJwt(jwt);
} catch (Exception e) {
log.error("Exception occurred when getting username from JWT. JWT: {}, exception message: {}", jwt,
e.getMessage());
ResponseUtil.renderJson(response, HttpStatus.UNAUTHORIZED, null);
return;
}
String username = jwtUtil.getUsernameFromJwt(jwt);
UserDetails userDetails;
try {
userDetails = customUserDetailsServiceImpl.loadUserByUsername(username);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,12 @@ public interface CommonService {
* @param payload the payload
*/
void validateObject(ValidationTestPayload payload);

/**
* Generate jwt string.
*
* @param username the username
* @return the string
*/
String generateJwt(String username);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.jmsoftware.apiportal.universal.service.impl;

import com.google.common.collect.Lists;
import com.jmsoftware.apiportal.universal.aspect.ValidateArgument;
import com.jmsoftware.apiportal.universal.configuration.ProjectProperty;
import com.jmsoftware.apiportal.universal.domain.ValidationTestPayload;
import com.jmsoftware.apiportal.universal.service.CommonService;
import com.jmsoftware.apiportal.universal.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
Expand All @@ -29,6 +31,7 @@
@RequiredArgsConstructor
public class CommonServiceImpl implements CommonService {
private final ProjectProperty projectProperty;
private final JwtUtil jwtUtil;

@Override
public Map<String, Object> getApplicationInfo() {
Expand All @@ -49,6 +52,11 @@ public void validateObject(@Valid ValidationTestPayload payload) {
log.info("Validation passed! {}", payload);
}

@Override
public String generateJwt(String username) {
return jwtUtil.createJwt(false, Long.MAX_VALUE, username, Lists.newLinkedList(), Lists.newLinkedList());
}

/**
* Gets field value by name.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jmsoftware.apiportal.universal.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.jmsoftware.apiportal.universal.configuration.Constants;
import com.jmsoftware.apiportal.universal.configuration.JwtConfiguration;
Expand All @@ -10,17 +11,21 @@
import com.jmsoftware.common.exception.BaseException;
import com.jmsoftware.common.exception.SecurityException;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

import javax.annotation.PostConstruct;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
Expand All @@ -33,9 +38,20 @@
@Slf4j
@Configuration
@RequiredArgsConstructor
@SuppressWarnings("unused")
public class JwtUtil {
private final JwtConfiguration jwtConfiguration;
private final RedisService redisService;
private SecretKey secretKey;
private JwtParser jwtParser;

@PostConstruct
public void init() {
log.info("Start to init class members of {}.", this.getClass().getSimpleName());
secretKey = Keys.hmacShaKeyFor(jwtConfiguration.getSigningKey().getBytes(StandardCharsets.UTF_8));
log.warn("Secret key for JWT was generated. Algorithm: {}", secretKey.getAlgorithm());
jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
}

/**
* Create JWT.
Expand All @@ -47,35 +63,34 @@ public class JwtUtil {
* @param authorities Granted Authority
* @return JWT
*/
@SuppressWarnings("unused")
public String createJwt(Boolean rememberMe,
Long id,
String subject,
List<String> roles,
Collection<? extends GrantedAuthority> authorities) {
Date now = new Date();
var now = new Date();
JwtBuilder builder = Jwts.builder()
.setId(id.toString())
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, jwtConfiguration.getSigningKey())
.claim("roles", roles);
.setId(id.toString())
.setSubject(subject)
.setIssuedAt(now)
.signWith(secretKey)
.claim("roles", roles);
// TODO: Don't generate authority information in JWT.
// .claim("authorities", authorities)

// Set expire duration of JWT.
Long ttl = rememberMe ? jwtConfiguration.getTtlForRememberMe() : jwtConfiguration.getTtl();
if (ttl > 0) {
builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue()));
}

String jwt = builder.compact();
// Store new JWT in Redis
Boolean result = redisService.set(Constants.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl, TimeUnit.MILLISECONDS);
var result = redisService.set(Constants.REDIS_JWT_KEY_PREFIX + subject, jwt, ttl,
TimeUnit.MILLISECONDS);
if (result) {
return jwt;
} else {
throw new BaseException(HttpStatus.ERROR, "Cannot persist JWT into Redis");
}
throw new BaseException(HttpStatus.ERROR);
}

/**
Expand All @@ -98,45 +113,38 @@ public String createJwt(Authentication authentication, Boolean rememberMe) {
* @return {@link Claims}
*/
public Claims parseJwt(String jwt) {
Claims claims;
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtConfiguration.getSigningKey())
.parseClaimsJws(jwt)
.getBody();

String username = claims.getSubject();
String redisKey = Constants.REDIS_JWT_KEY_PREFIX + username;

// Check if JWT exists
Long expire = redisService.getExpire(redisKey, TimeUnit.MILLISECONDS);
if (Objects.isNull(expire) || expire <= 0) {
throw new SecurityException(HttpStatus.TOKEN_EXPIRED);
}

// Check if current JWT is equal to the one in Redis.
// If it's noe equal, that indicates current user has signed out or logged in before.
// Both situations reveal the JWT expired.
String redisToken = redisService.get(redisKey);
if (!StrUtil.equals(jwt, redisToken)) {
throw new SecurityException(HttpStatus.TOKEN_OUT_OF_CONTROL);
}
return claims;
claims = Optional.ofNullable(jwtParser.parseClaimsJws(jwt).getBody())
.orElseThrow(() -> new BaseException(HttpStatus.TOKEN_PARSE_ERROR, "The JWT Claims Set is null"));
} catch (ExpiredJwtException e) {
log.error("Token expired.");
log.error("JWT is expired. JWT:{}, message: {}", jwt, e.getMessage());
throw new SecurityException(HttpStatus.TOKEN_EXPIRED);
} catch (UnsupportedJwtException e) {
log.error("Token not supported.");
log.error("JWT is unsupported. JWT:{}, message: {}", jwt, e.getMessage());
throw new SecurityException(HttpStatus.TOKEN_PARSE_ERROR);
} catch (MalformedJwtException e) {
log.error("Invalid token.");
throw new SecurityException(HttpStatus.TOKEN_PARSE_ERROR);
} catch (SignatureException e) {
log.error("Invalid signature of token.");
log.error("JWT is invalid. JWT:{}, message: {}", jwt, e.getMessage());
throw new SecurityException(HttpStatus.TOKEN_PARSE_ERROR);
} catch (IllegalArgumentException e) {
log.error("Token parameter not exists.");
log.error("The parameter of JWT is invalid. JWT:{}, message: {}", jwt, e.getMessage());
throw new SecurityException(HttpStatus.TOKEN_PARSE_ERROR);
}
String username = claims.getSubject();
String redisKeyOfJwt = Constants.REDIS_JWT_KEY_PREFIX + username;
// Check if JWT exists
Long expire = redisService.getExpire(redisKeyOfJwt, TimeUnit.MILLISECONDS);
if (ObjectUtil.isNull(expire) || expire <= 0) {
throw new SecurityException(HttpStatus.TOKEN_EXPIRED);
}
// Check if the current JWT is equal to the one in Redis.
// If it's noe equal, that indicates current user has signed out or logged in before.
// Both situations reveal the JWT has expired.
String jwtInRedis = redisService.get(redisKeyOfJwt);
if (!StrUtil.equals(jwt, jwtInRedis)) {
throw new SecurityException(HttpStatus.TOKEN_OUT_OF_CONTROL);
}
return claims;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ logging:

custom:
configuration:
super-user: "admin"
# Make `web-security-disabled` equal to true to disable web security. We suggest you do not turn off web security
# feature unless development environment.
web-security-disabled: false
Expand All @@ -59,6 +60,7 @@ custom:
- "/auth/check-email-uniqueness"
- "/auth/validate-username/**"
- "/user/get-avatar"
- "/common/get-jwt"
# Request need to be ignored that matches pattern.
pattern:
- "/druid/**"
Expand Down

0 comments on commit a79dffb

Please sign in to comment.