Monday, December 12, 2022

Spring Boot 3 : JWT with SecurityFilterChain, AuthorizeHttpRequests, RequestMatchers

pom.xml
 <?xml version="1.0" encoding="UTF-8"?>  
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">  
      <modelVersion>4.0.0</modelVersion>  
      <parent>  
           <groupId>org.springframework.boot</groupId>  
           <artifactId>spring-boot-starter-parent</artifactId>  
           <version>3.0.0</version>  
           <relativePath/> <!-- lookup parent from repository -->  
      </parent>  
      <groupId>com.javawithlwl</groupId>  
      <artifactId>spring-security</artifactId>  
      <version>0.0.1-SNAPSHOT</version>  
      <name>spring-security</name>  
      <description>Spring Security with Spring Boot</description>  
      <properties>  
           <java.version>19</java.version>  
           <jjwt.version>0.9.1</jjwt.version>  
           <jaxb.version>2.3.1</jaxb.version>  
      </properties>  
      <dependencies>  
           <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-starter-security</artifactId>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-starter-web</artifactId>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-devtools</artifactId>  
                <scope>runtime</scope>  
                <optional>true</optional>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-configuration-processor</artifactId>  
                <optional>true</optional>  
           </dependency>  
           <dependency>  
                <groupId>org.projectlombok</groupId>  
                <artifactId>lombok</artifactId>  
                <optional>true</optional>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-starter-test</artifactId>  
                <scope>test</scope>  
           </dependency>  
           <dependency>  
                <groupId>org.springframework.security</groupId>  
                <artifactId>spring-security-test</artifactId>  
                <scope>test</scope>  
           </dependency>  
           <dependency>  
                <groupId>io.jsonwebtoken</groupId>  
                <artifactId>jjwt</artifactId>  
                <version>${jjwt.version}</version>  
           </dependency>  
           <dependency>  
                <groupId>javax.xml.bind</groupId>  
                <artifactId>jaxb-api</artifactId>  
                <version>${jaxb.version}</version>  
           </dependency>  
      </dependencies>  
      <build>  
           <plugins>  
                <plugin>  
                     <groupId>org.springframework.boot</groupId>  
                     <artifactId>spring-boot-maven-plugin</artifactId>  
                     <configuration>  
                          <excludes>  
                               <exclude>  
                                    <groupId>org.projectlombok</groupId>  
                                    <artifactId>lombok</artifactId>  
                               </exclude>  
                          </excludes>  
                     </configuration>  
                </plugin>  
           </plugins>  
      </build>  
 </project>  
Spring Security Configuration:
 package com.javawithlwl.springsecurity.config;  
 import lombok.RequiredArgsConstructor;  
 import org.springframework.context.annotation.Bean;  
 import org.springframework.context.annotation.Configuration;  
 import org.springframework.security.authentication.AuthenticationManager;  
 import org.springframework.security.authentication.ProviderManager;  
 import org.springframework.security.authentication.dao.DaoAuthenticationProvider;  
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;  
 import org.springframework.security.config.http.SessionCreationPolicy;  
 import org.springframework.security.core.userdetails.UserDetailsService;  
 import org.springframework.security.crypto.password.PasswordEncoder;  
 import org.springframework.security.web.SecurityFilterChain;  
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;  
 @Configuration  
 @EnableWebSecurity  
 @RequiredArgsConstructor  
 public class AppSecurityConfiguration {  
  private final PasswordEncoder passwordEncoder;  
  private final JwtRequestFilter jwtRequestFilter;  
  @Bean  
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  
   return http  
     .csrf(csrf -> csrf.disable())  
     .authorizeHttpRequests(auth -> {  
      auth.requestMatchers("/api/auth/**").permitAll();  
      auth.anyRequest().authenticated();  
     })  
     .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))  
     .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)  
     .build();  
  }  
  @Bean  
  public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {  
   var authProvider = new DaoAuthenticationProvider();  
   authProvider.setUserDetailsService(userDetailsService);  
   authProvider.setPasswordEncoder(passwordEncoder);  
   return new ProviderManager(authProvider);  
  }  
 }  
App Configuration:
 package com.javawithlwl.springsecurity.config;  
 import org.springframework.context.annotation.Bean;  
 import org.springframework.context.annotation.Configuration;  
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;  
 import org.springframework.security.crypto.password.PasswordEncoder;  
 @Configuration  
 public class AppBeanConfiguration {  
   @Bean  
   public PasswordEncoder passwordEncoder(){  
    return new BCryptPasswordEncoder();  
   }  
 }  
JWT Request Filter:
 package com.javawithlwl.springsecurity.config;  
 import jakarta.servlet.FilterChain;  
 import jakarta.servlet.ServletException;  
 import jakarta.servlet.http.HttpServletRequest;  
 import jakarta.servlet.http.HttpServletResponse;  
 import lombok.RequiredArgsConstructor;  
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
 import org.springframework.security.core.context.SecurityContextHolder;  
 import org.springframework.security.core.userdetails.UserDetails;  
 import org.springframework.security.core.userdetails.UserDetailsService;  
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;  
 import org.springframework.stereotype.Component;  
 import org.springframework.web.filter.OncePerRequestFilter;  
 import java.io.IOException;  
 @Component  
 @RequiredArgsConstructor  
 public class JwtRequestFilter extends OncePerRequestFilter {  
  private final JwtUtil jwtUtil;  
  private final UserDetailsService userService;  
  @Override  
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {  
   final String authHeader = request.getHeader("Authorization");  
   String username = null;  
   String jwt = null;  
   if (authHeader != null && authHeader.startsWith("Bearer")) {  
    jwt = authHeader.substring(7);  
    username = jwtUtil.extractUsername(jwt);  
   }  
   if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {  
    UserDetails userDetails = this.userService.loadUserByUsername(username);  
    if(jwtUtil.validateToken(jwt,userDetails)){  
     UsernamePasswordAuthenticationToken authToken=new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());  
     authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));  
     SecurityContextHolder.getContext().setAuthentication(authToken);  
    }  
   }  
   filterChain.doFilter(request,response);  
  }  
 }  
JWT Util:
 package com.javawithlwl.springsecurity.config;  
 import io.jsonwebtoken.Claims;  
 import io.jsonwebtoken.Jwts;  
 import io.jsonwebtoken.SignatureAlgorithm;  
 import org.springframework.beans.factory.annotation.Value;  
 import org.springframework.security.core.userdetails.UserDetails;  
 import org.springframework.stereotype.Component;  
 import java.util.Date;  
 import java.util.HashMap;  
 import java.util.Map;  
 import java.util.function.Function;  
 @Component  
 public class JwtUtil {  
   @Value("${jwt.secret.key:app_secret}")  
   private String SECRET_KEY;  
   public String extractUsername(String token) {  
     return extractClaim(token, Claims::getSubject);  
   }  
   public Date extractExpiration(String token) {  
     return extractClaim(token, Claims::getExpiration);  
   }  
   public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {  
     final Claims claims = extractAllClaims(token);  
     return claimsResolver.apply(claims);  
   }  
   private Claims extractAllClaims(String token) {  
     return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();  
   }  
   private Boolean isTokenExpired(String token) {  
     return extractExpiration(token).before(new Date());  
   }  
   public String generateToken(UserDetails userDetails) {  
     Map<String, Object> claims = new HashMap<>();  
     return createToken(claims, userDetails.getUsername());  
   }  
   private String createToken(Map<String, Object> claims, String subject) {  
     return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))  
         .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))  
         .signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();  
   }  
   public Boolean validateToken(String token, UserDetails userDetails) {  
     final String username = extractUsername(token);  
     return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));  
   }  
 }  
Auth Controller:
 package com.javawithlwl.springsecurity.controller;  
 import com.javawithlwl.springsecurity.config.JwtUtil;  
 import com.javawithlwl.springsecurity.model.LoginRequest;  
 import com.javawithlwl.springsecurity.model.LoginResponse;  
 import lombok.RequiredArgsConstructor;  
 import org.springframework.http.ResponseEntity;  
 import org.springframework.security.authentication.AuthenticationManager;  
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;  
 import org.springframework.security.core.userdetails.UserDetails;  
 import org.springframework.security.core.userdetails.UserDetailsService;  
 import org.springframework.web.bind.annotation.PostMapping;  
 import org.springframework.web.bind.annotation.RequestBody;  
 import org.springframework.web.bind.annotation.RequestMapping;  
 import org.springframework.web.bind.annotation.RestController;  
 @RestController  
 @RequestMapping("/api/auth")  
 @RequiredArgsConstructor  
 public class AuthController {  
    private final AuthenticationManager authenticationManager;  
    private final UserDetailsService userDetailsService;  
    private final JwtUtil jwtUtil;  
    @PostMapping("/login")  
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginRequest){  
       authenticationManager.authenticate(  
         new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())  
       );  
     UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());  
     String jwtToken=jwtUtil.generateToken(userDetails);  
     return ResponseEntity.ok(new LoginResponse(jwtToken));  
    }  
 }  
Greeting Controller:
 package com.javawithlwl.springsecurity.controller;  
 import org.springframework.http.ResponseEntity;  
 import org.springframework.web.bind.annotation.GetMapping;  
 import org.springframework.web.bind.annotation.RequestMapping;  
 import org.springframework.web.bind.annotation.RestController;  
 import java.security.Principal;  
 @RestController  
 @RequestMapping("/api/v1/")  
 public class GreetingController {  
    @GetMapping("/greet")  
    public ResponseEntity<String> greet(Principal principal){  
     return ResponseEntity.ok(String.format("Hi %s, Welcome to Spring Boot World",principal.getName()));  
    }  
 }  
Login Request:
 package com.javawithlwl.springsecurity.model;  
 import lombok.Data;  
 @Data  
 public class LoginRequest {  
  private String username;  
  private String password;  
 }  
Login Response:
 package com.javawithlwl.springsecurity.model;  
 import lombok.AllArgsConstructor;  
 import lombok.Data;  
 @Data  
 @AllArgsConstructor  
 public class LoginResponse {  
   private String jwt;  
 }  
UserDetails Service:
 package com.javawithlwl.springsecurity.service;  
 import jakarta.annotation.PostConstruct;  
 import lombok.RequiredArgsConstructor;  
 import org.springframework.security.core.userdetails.User;  
 import org.springframework.security.core.userdetails.UserDetails;  
 import org.springframework.security.core.userdetails.UserDetailsService;  
 import org.springframework.security.core.userdetails.UsernameNotFoundException;  
 import org.springframework.security.crypto.password.PasswordEncoder;  
 import org.springframework.stereotype.Service;  
 import java.util.ArrayList;  
 import java.util.List;  
 @Service  
 @RequiredArgsConstructor  
 public class AppUserDetailsService implements UserDetailsService {  
  private final PasswordEncoder passwordEncoder;  
  private List<UserDetails> userDetails;  
  @PostConstruct  
  public void init() {  
   userDetails = new ArrayList<>(List.of(  
     User.withUsername("user")  
       .password(passwordEncoder.encode("user@123")).authorities("ROLE_USER").build(),  
     User.withUsername("admin")  
       .password(passwordEncoder.encode("admin@123")).authorities("ROLE_ADMIN").build()  
   ));  
  }  
  @Override  
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
   return userDetails.stream()  
     .filter(user -> user.getUsername().equals(username))  
     .findFirst()  
     .orElseThrow(() -> new UsernameNotFoundException("User not found with given username"));  
  }  
 }  
Main Class:
 package com.javawithlwl.springsecurity;  
 import org.springframework.boot.SpringApplication;  
 import org.springframework.boot.autoconfigure.SpringBootApplication;  
 @SpringBootApplication  
 public class SpringSecurityApplication {  
      public static void main(String[] args) {  
           SpringApplication.run(SpringSecurityApplication.class, args);  
      }  
 }  

Monday, December 5, 2022

Expression Evaluating - Using Java Stack

import java.util.List;
import java.util.Stack;

public class EvaluatingExpression {

  public static void main(String[] args) {
    System.out.println(evaluateExp("2+2"));
    System.out.println(evaluateExp("2 * ( 4 * 5 + 1 )"));
  }

  public static int evaluateExp(String expression) {
    char[] tokens = expression.toCharArray();
    Stack<Integer> values = new Stack<Integer>();
    Stack<Character> operators = new Stack<Character>();
    for (int i = 0; i < tokens.length; i++) {
      if (tokens[i] == ' ') {
        continue;
      }
      if (tokens[i] >= '0' && tokens[i] <= '9') {
        StringBuilder sb = new StringBuilder();
        while (i < tokens.length && tokens[i] >= '0' && tokens[i] <= '9') {
          sb.append(tokens[i++]);
        }
        i--;
        values.push(Integer.parseInt(sb.toString()));
      } else if (tokens[i] == '(') {
        operators.push(tokens[i]);
      } else if (tokens[i] == ')') {
        while (operators.peek() != '(') {
          values.push(applyOperation(operators.pop(), values.pop(), values.pop()));
        }
        operators.pop();
      } else if (isOperator(tokens[i])) {
        while (!operators.isEmpty() && hasPrecedence(tokens[i], operators.peek())) {
          values.push(applyOperation(operators.pop(), values.pop(), values.pop()));
        }
        operators.push(tokens[i]);
      }
    }
    while (!operators.isEmpty()) {
      values.push(applyOperation(operators.pop(), values.pop(), values.pop()));
    }
    return values.pop();

  }

  private static boolean hasPrecedence(char op1, char op2) {
    if (op2 == '(' || op1 == ')') {
      return false;
    }
    if ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) {
      return false;
    }
    return true;
  }

  private static boolean isOperator(char ch) {
    return List.of('+', '*', '-', '/').contains(ch);
  }

  private static Integer applyOperation(Character ope, Integer a, Integer b) {
    return switch (ope) {
      case '+' -> a + b;
      case '-' -> a - b;
      case '*' -> a * b;
      case '/' -> {
        if (b == 0) throw new IllegalArgumentException("Unsupported exception");
        yield a / b;
      }
      default -> throw new IllegalArgumentException("Unknown operator");
    };
  }
}

Spring Boot 3 : JWT with SecurityFilterChain, AuthorizeHttpRequests, RequestMatchers

pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"...