How to Build a High‑Performance JWT Authentication Service in Java
This article explains how to design and implement a stateless JWT‑based authentication center in Java, covering JWT fundamentals, token structure, RSA signing, Spring Boot microservice setup, JPA entity and DAO creation, service interfaces, REST endpoints, and a comparison with traditional session authentication.
Authentication Center Service
Understanding JWT
JSON Web Token is an open standard that defines a compact, self‑contained way for securely transmitting information as a JSON object between parties.
After server authentication, a JSON object is generated and sent to the client; subsequent requests must include this token, allowing the server to identify the client without storing session data, enabling stateless high‑performance authentication.
JWT is suitable for user authorization and information exchange.
JWT Components
Header : contains token type and signing algorithm, Base64‑encoded.
Payload: the data we want to transmit.
Payload is a key‑value data structure containing the information (e.g., user token).
Signature: the cryptographic signature.
Signature is generated from the Base64‑encoded header, payload, and a secret key using the algorithm specified in the header.
Signature formula:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)JWT uses dots to concatenate the three parts: header.payload.signature.
Authorization and Authentication Design
We implement a common utility class for authentication for three reasons:
JWT is algorithm‑based and framework‑agnostic, so it can be extracted.
Our e‑commerce system consists of multiple microservices, each needing authentication.
Local authentication avoids extra HTTP overhead, improving performance.
Implementation
Create a new service e-commerce-authority-center and import dependencies (Spring Cloud Alibaba Nacos, Spring Boot JPA, MySQL, Kafka, Zipkin, Screw, etc.).
<dependencies>
<!-- spring cloud alibaba nacos discovery dependency -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!-- Java Persistence API, ORM spec -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
<scope>runtime</scope>
</dependency>
<!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<!-- spring-kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<!-- screw generate database doc -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>Configure application.yml with server port, Nacos discovery, JPA settings, datasource, Kafka, Zipkin, and management endpoints.
server:
port: 7000
servlet:
context-path: /ecommerce-authority-center
spring:
application:
name: e-commerce-authority-center
cloud:
nacos:
discovery:
enabled: true
server-addr: 127.0.0.1:8848
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
jpa:
show-sql: true
hibernate:
ddl-auto: none
properties:
hibernate.show_sql: true
hibernate.format_sql: true
open-in-view: false
datasource:
url: jdbc:mysql://127.0.0.1:3306/imooc_e_commerce?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username: root
password: root
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 8
minimum-idle: 4
idle-timeout: 30000
connection-timeout: 30000
max-lifetime: 45000
auto-commit: true
pool-name: ImoocEcommerceHikariCP
kafka:
bootstrap-servers: 127.0.0.1:9092
producer:
retries: 3
consumer:
auto-offset-reset: latest
zipkin:
sender:
type: kafka
base-url: http://127.0.0.1:9411/
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: alwaysEnable JPA auditing with @EnableJpaAuditing in the main class.
@EnableJpaAuditing
@SpringBootApplication
@EnableDiscoveryClient
public class AuthorityApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorityApplication.class, args);
}
}Define entity EcommerceUser with fields id, username, password, extraInfo, createTime, updateTime, and corresponding JPA annotations.
@Entity
@Table(name = "t_ecommerce_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EcommerceUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "username", nullable = false)
private String username;
@Column(name = "password", nullable = false)
private String password;
@Column(name = "extra_info", nullable = false)
private String extraInfo;
@CreatedDate
@Column(name = "create_time", nullable = false)
private Date createTime;
@CreatedDate
@Column(name = "update_time", nullable = false)
private Date updateTime;
}Create EcommerceUserDao extending JpaRepository with custom query methods.
public interface EcommerceUserDao extends JpaRepository<EcommerceUser, Long> {
EcommerceUser findByUsername(String name);
EcommerceUser findByUsernameAndPassword(String name, String password);
}Generate RSA key pair in a test class and store the private key and public key constants in AuthorConstant and CommonConstant.
public class AuthorConstant {
public static final String PRIVATE_KEY = "MIIEvAIBADAN..."; // truncated for brevity
}
public class CommonConstant {
public static final String PUBLIC_KEY = "MIIBIjANBgkqh..."; // truncated for brevity
public static final String JWT_USER_INFO_KEY = "e-commerce-user";
public static final String AUTHORITY_CENTER_SERVICE_ID = "e-commerce-authority-center";
}Define VO classes JwtToken, LoginUserinfo, and UsernameAndPassword.
@Data
public class JwtToken { private String token; }
@Data
public class LoginUserinfo { private Long id; private String username; }
@Data
public class UsernameAndPassword { private String username; private String password; }Define IJWTService interface with methods to generate token (default and custom expiry) and register user.
public interface IJWTService {
String generateToken(String username, String password) throws Exception;
String generateToken(String username, String password, Integer expireTime) throws Exception;
String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception;
}Implement IJWTServiceImpl handling token creation using Jwts.builder(), RSA signing, and user lookup.
@Service
@Transactional(rollbackFor = Exception.class)
public class IJWTServiceImpl implements IJWTService {
@Autowired
private EcommerceUserDao ecommerceUserDao;
@Override
public String generateToken(String username, String password) throws Exception {
return generateToken(username, password, 0);
}
@Override
public String generateToken(String username, String password, Integer expireTime) throws Exception {
EcommerceUser user = ecommerceUserDao.findByUsernameAndPassword(username, password);
if (user == null) {
log.error("cannot find user: [{}, {}]", username, password);
return null;
}
LoginUserinfo info = new LoginUserinfo(user.getId(), user.getUsername());
if (expireTime <= 0) {
expireTime = AuthorConstant.DEFAULT_EXPIRE_DAY;
}
ZonedDateTime zdt = LocalDate.now().plus(expireTime, ChronoUnit.DAYS)
.atStartOfDay(ZoneId.systemDefault());
Date expireDate = Date.from(zdt.toInstant());
return Jwts.builder()
.claim(CommonConstant.JWT_USER_INFO_KEY, JSON.toJSONString(info))
.setId(UUID.randomUUID().toString())
.setExpiration(expireDate)
.signWith(getPrivateKey(), SignatureAlgorithm.RS256)
.compact();
}
@Override
public String registerUserAndGenerateToken(UsernameAndPassword dto) throws Exception {
if (ecommerceUserDao.findByUsername(dto.getUsername()) != null) {
log.error("username already registered: {}", dto.getUsername());
return null;
}
EcommerceUser user = new EcommerceUser();
user.setUsername(dto.getUsername());
user.setPassword(dto.getPassword()); // assume already MD5‑hashed
user.setExtraInfo("{}");
user = ecommerceUserDao.save(user);
log.info("registered user: {}", user.getUsername());
return generateToken(user.getUsername(), user.getPassword());
}
private PrivateKey getPrivateKey() throws Exception {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(AuthorConstant.PRIVATE_KEY));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(priPKCS8);
}
}Provide TokenParseUtil to parse JWT and extract LoginUserinfo using the stored public key.
public class TokenParseUtil {
public static LoginUserinfo parseUserInfoFromToken(String token) throws Exception {
if (token == null) return null;
Jws<Claims> jws = Jwts.parser().setSigningKey(getPublicKey()).parseClaimsJws(token);
Claims body = jws.getBody();
if (body.getExpiration().before(Calendar.getInstance().getTime())) return null;
return JSON.parseObject(body.get(CommonConstant.JWT_USER_INFO_KEY).toString(), LoginUserinfo.class);
}
private static PublicKey getPublicKey() throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(CommonConstant.PUBLIC_KEY));
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
}Expose REST endpoints in AuthorityController for /token (login) and /register (user registration) returning JwtToken without global response wrapping.
@RestController
@RequestMapping("/authority")
public class AuthorityController {
private final IJWTService jwtService;
public AuthorityController(IJWTService jwtService) { this.jwtService = jwtService; }
@IgnoreResponseAdvice
@PostMapping("/token")
public JwtToken token(@RequestBody UsernameAndPassword dto) throws Exception {
log.info("login request: {}", JSON.toJSONString(dto));
return new JwtToken(jwtService.generateToken(dto.getUsername(), dto.getPassword()));
}
@IgnoreResponseAdvice
@PostMapping("/register")
public JwtToken register(@RequestBody UsernameAndPassword dto) throws Exception {
log.info("register request: {}", JSON.toJSONString(dto));
return new JwtToken(jwtService.registerUserAndGenerateToken(dto));
}
}Testing shows token generation, parsing, and registration work as expected, with JWT strings consisting of header, payload, and signature separated by dots.
Summary of Authentication Service
Compared with traditional session‑based authentication, JWT provides stateless, cross‑platform, and scalable security, while sessions offer stronger server‑side control. Both have trade‑offs; JWT is well‑suited for microservice architectures where lightweight, distributed authentication is required.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
