In the modern digital world, information security is a vital factor, especially in distributed systems and microservices architectures. Encryption plays a key role in protecting data from unauthorized access. This article will delve into two basic types of encryption: Symmetric Encryption and Asymmetric Encryption, and discuss their practical applications, especially in authentication and authorization using JSON Web Tokens (JWT).
Symmetric encryption uses the same secret key to encrypt and decrypt data. This means that both the sender and the receiver must know the secret key.
Asymmetric encryption uses a pair of keys: a public key and a private key. The public key is widely shared, while the private key must be kept absolutely secret. Data encrypted with a public key can only be decrypted with the corresponding private key, and vice versa.
Below is a summary comparison table between symmetric and asymmetric encryption:
Characteristic | Symmetric encryption | Asymmetric encryption |
---|---|---|
Lock | A secret key | Two keys (public key and private key) |
Speed | Fast | Slow |
Effective | Resource efficiency | Resource intensive |
Key Management | Hard | Easier |
Application | Encrypt large amounts of data | Authentication, electronic signature |
JWT is an open standard for securely transmitting data between parties as JSON objects. JWTs are commonly used for authentication and authorization in web applications and APIs. Encryption plays an important role in ensuring the integrity and authentication of JWTs.
The structure of a JWT consists of three parts:
HS256 uses the HMAC-SHA256 algorithm and a secret key to generate the signature for the JWT. The server uses the same secret key to verify the integrity of the JWT.
Advantage:
Disadvantages:
RS256 uses the RSA algorithm and a public/private key pair. The server issuing the JWT uses the private key to generate the signature. Other services use the public key to verify the signature.
Advantage:
Disadvantages:
JWT authentication process with RS256 :
Spring Security is a powerful framework for securing Java applications. Here is a simple example of how to configure Spring Security for authentication and authorization using JWT.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
5.2 SecurityFilterChain Configuration<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { private final AuthTokenFilter authTokenFilter; public SecurityConfig(AuthTokenFilter authTokenFilter) { this.authTokenFilter = authTokenFilter; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() // Cho phép truy cập không cần xác thực .anyRequest().authenticated() // Yêu cầu xác thực cho tất cả các request khác ) .addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class); // Thêm filter để xác thực JWT return http.build(); } }
Explain:@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { private final AuthTokenFilter authTokenFilter; public SecurityConfig(AuthTokenFilter authTokenFilter) { this.authTokenFilter = authTokenFilter; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() // Cho phép truy cập không cần xác thực .anyRequest().authenticated() // Yêu cầu xác thực cho tất cả các request khác ) .addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class); // Thêm filter để xác thực JWT return http.build(); } }
@Component public class AuthTokenFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; public AuthTokenFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = jwtUtil.parseJwt(request); if (jwt != null && jwtUtil.validateJwtToken(jwt)) { String username = jwtUtil.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); } }
Explain:@Component public class AuthTokenFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; public AuthTokenFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = jwtUtil.parseJwt(request); if (jwt != null && jwtUtil.validateJwtToken(jwt)) { String username = jwtUtil.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); } }
@Component public class JwtUtil { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expirationMs}") private int jwtExpirationMs; public String generateJwtToken(Authentication authentication) { UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); return Jwts.builder() .setSubject((userPrincipal.getUsername())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) .signWith(key(), SignatureAlgorithm.HS256) .compact(); } private Key key() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); } public String getUserNameFromJwtToken(String token) { return Jwts.parserBuilder().setSigningKey(key()).build() .parseClaimsJws(token).getBody().getSubject(); } public boolean validateJwtToken(String token) { try { Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token); return true; } catch (MalformedJwtException e) { logger.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { logger.error("JWT token is expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { logger.error("JWT token is unsupported: {}", e.getMessage()); } catch (IllegalArgumentException e) { logger.error("JWT claims string is empty: {}", e.getMessage()); } return false; } public String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { return headerAuth.substring(7, headerAuth.length()); } return null; } }
Explain:@Component public class JwtUtil { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expirationMs}") private int jwtExpirationMs; public String generateJwtToken(Authentication authentication) { UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); return Jwts.builder() .setSubject((userPrincipal.getUsername())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) .signWith(key(), SignatureAlgorithm.HS256) .compact(); } private Key key() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); } public String getUserNameFromJwtToken(String token) { return Jwts.parserBuilder().setSigningKey(key()).build() .parseClaimsJws(token).getBody().getSubject(); } public boolean validateJwtToken(String token) { try { Jwts.parserBuilder().setSigningKey(key()).build().parseClaimsJws(token); return true; } catch (MalformedJwtException e) { logger.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { logger.error("JWT token is expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { logger.error("JWT token is unsupported: {}", e.getMessage()); } catch (IllegalArgumentException e) { logger.error("JWT claims string is empty: {}", e.getMessage()); } return false; } public String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { return headerAuth.substring(7, headerAuth.length()); } return null; } }
In the process of using encryption, protecting the secret key and verifying the public key are extremely important.
The secret key needs to be stored securely to avoid disclosure to unauthorized persons. Some methods of securing the secret key:
Before using a public key to verify a signature, you need to make sure that the public key is correct and belongs to a person or organization you trust. Some methods of verifying public keys:
In microservices architecture, authentication and authorization are extremely important. JWT and asymmetric encryption (RS256) are a popular solution to solve this problem.
For example:
Read more about Authentication and authorization in microservices architecture .
Token expiration is an important factor in security. Tokens should have a short expiration time to minimize the risk in case the token is stolen. However, an expiration time that is too short can be inconvenient for users, as they have to log in again frequently.
A common solution is to use a refresh token. Refresh tokens have a longer expiration time and are used to request a new access token when the access token expires.
Symmetric and asymmetric encryption are two important tools in information security. Choosing the right encryption type depends on the specific requirements of the application. In distributed systems and microservices architectures, asymmetric encryption (RS256) and JWT are a popular solution for authentication and authorization.
Hopefully this article has given you an overview of symmetric and asymmetric encryption and their applications in practice.