ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL 76일 - JWT 토큰 검증
    공부/Spring 사용한 프로젝트 2022. 3. 15. 21:48

    jwt 토큰으로 인증하는 걸 해보자....

    참고글에 있는 코드랑은 조금 다르다.

     

    package com.example.ical.Infrastructure;
    
    import com.example.ical.Domain.User;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;
    
    @Component
    public class JwtTokenUtil implements Serializable {
        private static final long serialVersionUID = -2550185165626007488L;
    
        public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
    
        @Value("${jwt.secret}")
        private String secret;
    
        //retrieve username from jwt token
        public String getRoleFromToken(String token) {
            return getClaimRoleFromToken(token);
        }
    
        //retrieve expiration date from jwt token
        public Date getExpirationDateFromToken(String token) {
            return getClaimFromToken(token, Claims::getExpiration);
        }
    
        public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
            final Claims claims = getAllClaimsFromToken(token);
            return claimsResolver.apply(claims);
        }
    
        public String getClaimRoleFromToken(String token) {
            final Claims claims = getAllClaimsFromToken(token);
            return claims.get("role").toString();
        }
    
        //for retrieveing any information from token we will need the secret key
        private Claims getAllClaimsFromToken(String token) {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }
    
        //    //check if the token has expired
        private Boolean isTokenExpired(String token) {
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }
    
        //generate token for user
        public String generateToken(User user) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("role", user.getRole());
            return doGenerateToken(claims, "i-cal");
        }
    
        //while creating the token -
        //1. Define  claims of the token, like Issuer, Expiration, Subject, and the ID
        //2. Sign the JWT using the HS512 algorithm and secret key.
        //3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
        //   compaction of the JWT to a URL-safe string
        private String doGenerateToken(Map<String, Object> claims, String subject) {
            return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
                    .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                    .signWith(SignatureAlgorithm.HS512, secret).compact();
        }
    
        //    validate token
        public Boolean validateToken(String token, User user) {
            final String userRole = getRoleFromToken(token);
            return (userRole.equals(user.getRole()) && !isTokenExpired(token));
        }
    
    
    }

    참고글에서는 getSubject를 사용해서 username을 가져왔는데, username이 아니라 role이 필요하기 때문에 Claim으로부터 role을 가져오는 메서드를 만들었다.

    그리고 그 메서드로 validateToken 메서드 쪽의 로직을 수정했다. (일단 되는지는 봐야함)

    JwtRequestFilter

    /* 다른 코드들은 더 필요한지 모르겠는데 얘는 필요해 보여서 해봄 */

    일단 OncePerRequestFilter 클래스를 상속할 예정이다. 요것을 구현하고 나면 모든 요청에 대해 필터 클래스가 실행된가고 한다.

    이 요청에 유효한 JWT 토큰이 있는지 확인한다고 한다. 유효할 경우 인증된 것으로!

    package com.example.ical.Infrastructure;
    
    import com.example.ical.Domain.User;
    import com.example.ical.Service.UserService;
    import io.jsonwebtoken.ExpiredJwtException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component
    public class JwtRequestFilter extends OncePerRequestFilter {
        private UserService userService;
        private JwtTokenUtil jwtTokenUtil;
    
    
        public JwtRequestFilter(UserService userService, JwtTokenUtil jwtTokenUtil) {
            this.userService = userService;
            this.jwtTokenUtil = jwtTokenUtil;
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            final String requestTokenHeader = request.getHeader("Authorization");
            String jwtToken = null;
            Integer userId = null;
            // JWT Token is in the form "Bearer token". Remove Bearer word and get
            // only the Token
            if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
                jwtToken = requestTokenHeader.substring(7);
                try {
                    userId = jwtTokenUtil.getIdFromToken(jwtToken);
                } catch (IllegalArgumentException e) {
                    System.out.println("Unable to get JWT Token");
                } catch (ExpiredJwtException e) {
                    System.out.println("JWT Token has expired");
                }
            } else {
                logger.warn("JWT Token does not begin with Bearer String");
            }
    
            // Once we get the token validate it.
            System.out.println(request.getRequestURI());
            System.out.println("/login");
    
            if (request.getMethod().equalsIgnoreCase("POST")) {
                User user = this.userService.getUserById(userId);
    
                // if token is valid configure Spring Security to manually set
                // authentication
                System.out.println(jwtTokenUtil.validateToken(jwtToken, user));
                if (!jwtTokenUtil.validateToken(jwtToken, user)) {
                    throw new RuntimeException("권한이 없습니다.");
                }
            }
    
    
            filterChain.doFilter(request, response);
    
        }
    }

    일단 이렇게 하니까 POST 요청일 때는 role을 확인하고, admin인 경우에만 요청에 성공하도록 하는 것은 됐다.

    role이 user일 때

    다만... 뭔가... 코드가 이런식이지는 않았는데 음... 일단 동작은 정상적으로 하는 그런... 이상한... 상황이라고 해야하나.... 근데 filter의 역할이 뭔지는 조금 알 거 같음.

    OncePerRequestFilter?

    한번의 요청에서 한번만 필터가 동작하도록 하는 것

    필터는 서블릿 전후로 동작하도록 할 수 있는데, 한번의 요청에서 여러 번의 서블릿을 거칠 경우 필터가 여러번 동작할 수 있는 걸 막을 수 있다고 한다.

     

    참고글

    https://www.javainuse.com/spring/boot-jwt

     

    Spring Boot Security + JWT Hello World Example | JavaInUse

    package com.javainuse; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootHelloWorldApplication { public static void main(String[] args) { Sp

    www.javainuse.com

     

    '공부 > Spring 사용한 프로젝트' 카테고리의 다른 글

    TIL 78일 - mvc test  (0) 2022.03.17
    TIL 75일 - 로그인 시 jwt 반환  (0) 2022.03.14
    TIL 74일 - Filters  (0) 2022.03.13
    TIL 73일 - 디스패쳐 서블릿  (0) 2022.03.12
    TIL 71일 - 스프링 자잘자잘  (0) 2022.03.10
Designed by Tistory.