인프런 커뮤니티 질문&답변

성부근님의 프로필 이미지

작성한 질문수

스프링부트 시큐리티 & JWT 강의

권한 인증 403가 뜹니다

작성

·

1.6K

0

https://github.com/bgseong/Security-test

 

public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    private TokenService tokenService;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager, TokenService tokenService) {
        super(authenticationManager);
        this.tokenService = tokenService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        super.doFilterInternal(request, response, chain);

        String token = tokenService.resolveToken(request);

        if(token == null){
            chain.doFilter(request, response);
            return;
        }

        if (tokenService.validateToken(token)) {
            Authentication authentication = tokenService.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            System.out.println(SecurityContextHolder.getContext().getAuthentication());
        }

        chain.doFilter(request,response);
    }
}
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    @Autowired
    TokenService tokenService;

    @Autowired
    CorsConfig corsConfig;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http
                .csrf().disable()
                .httpBasic().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .formLogin().disable()
                .apply(new MyCustomDsl())

                .and()
                .authorizeHttpRequests(authorize ->
                        authorize
                                .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 특정 정적 리소스 허용
                                .requestMatchers("/api/v1/user/**").hasAnyRole("ADMIN", "MANAGER")
                                .requestMatchers("/api/v1/manager/**").hasRole("ADMIN")
                                .requestMatchers("/api/v1/admin/**").hasRole("ROLE_ADMIN")
                                .anyRequest().permitAll());

        return http.build();


    }

    public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
            http
                    .addFilter(corsConfig.corsFilter())
                    .addFilter(new LoginFilter(authenticationManager,tokenService))
                    .addFilter(new JwtAuthorizationFilter(authenticationManager,tokenService));

        }
    }
}
@Component
public class TokenService implements InitializingBean {
    private final UserRepository usersrepository;

    private final Logger logger = LoggerFactory.getLogger(TokenService.class);
    private static final String AUTHORITIES_KEY = "auth";
    private final String secret;
    private final long accessTokenValidityInMilliseconds;

    private final long refreshTokenValidityInMilliseconds;

    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String REFRESHTOKEN_HEADER = "RefreshToken";
    private Key key;

    public TokenService(
            UserRepository usersrepository, @Value("${spring.jwt.secret}") String secret,
            @Value("${spring.jwt.token-validity-in-seconds}") long tokenValidityInSeconds) {
        this.usersrepository = usersrepository;
        this.secret = secret;
        this.accessTokenValidityInMilliseconds = tokenValidityInSeconds * 500;
        this.refreshTokenValidityInMilliseconds = tokenValidityInSeconds * 1000 * 336;
    }

    @Override
    public void afterPropertiesSet() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }


    public String createAccessToken(PrincipalDetails principalDetails) {
        return createAccessToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities());
    }

    public String createRefreshToken(PrincipalDetails principalDetails) {
        return createRefreshToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities());
    }

    public String createAccessToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) {
        String authorities = inputAuthorities.stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        long now = (new Date()).getTime();

        String accessToken = Jwts.builder()
                .setSubject(email)
                .claim(AUTHORITIES_KEY, authorities)
                .signWith(key, SignatureAlgorithm.HS512)
                .setExpiration(new Date(now + this.accessTokenValidityInMilliseconds))
                .compact();

        return accessToken;
    }

    public String createRefreshToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) {
        String authorities = inputAuthorities.stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(","));

        long now = (new Date()).getTime();

        String Token = Jwts.builder()
                .setSubject(email)
                .claim(AUTHORITIES_KEY, authorities)
                .signWith(key, SignatureAlgorithm.HS512)
                .setExpiration(new Date(now + this.refreshTokenValidityInMilliseconds))
                .compact();

        return Token;
    }

    public Authentication getAuthentication(String token) {
        Claims claims = Jwts
                .parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();

        User user = usersrepository.findByEmail(claims.get("sub",String.class));

        Collection<? extends GrantedAuthority> authorities =
                Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

        PrincipalDetails principal = new PrincipalDetails(user);

        return new UsernamePasswordAuthenticationToken(principal, null, authorities);
    }

    public String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }

        return null;
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            logger.info("worng JWT sign");
        } catch (ExpiredJwtException e) {
            logger.info("expire JWT");
        } catch (UnsupportedJwtException e) {
            logger.info("No support JWT");
        } catch (IllegalArgumentException e) {
            logger.info("JWT is worng");
        }
        return false;
    }

}

이렇게 구성해 놨습니다. 그런데 모든 권한이 적용된 url에 접근을 하면 403 에러가 뜹니다.

필터에서 SecurityContextHolder를 출력하면 아래와 같이 출력이 되는 걸 확인했고

[Principal=com.securitytest.Securitytest.auth.PrincipalDetails@45095607, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN]]

컨트롤러에서 PrincipalDetails를 호출해보니, null이라서 오류가 난다고 뜹니다. 무엇이 문제일까요ㅠㅠ..

답변 2

0

코드를 보니 권한은 ADMIN 이라고 쓴 곳도 있고 ROLE_ADMIN 이라고 쓴 곳도 보이네요.

예를 들어 SecurityConfig 클래스 안에 SecurityFilterChain 를 보면,

권한 설정 하는 부분이 그러합니다. User.role 에 저장되는 권한 이름이 ADMIN 이라면 ADMIN, USER 등으로

권한을 설정하시고, ROLE_ADMIN, ROLE_USER 라면 이런 식으로 설정하시면 되겠습니다.

이 부분을 먼저 확인해보시겠어요 ?

0

안녕하세요, 인프런 AI 인턴이에요. 권한 인증 403 에러는 클라이언트가 요청한 리소스에 대한 접근 권한이 없을 때 발생하는 오류입니다.

403 에러가 발생하는 이유는 여러 가지가 있을 수 있는데요, 주로 다음과 같은 이유로 발생할 수 있습니다.

  1. 권한 설정 오류: 서버에서 요청한 리소스에 대한 접근 권한 설정이 제대로 이루어지지 않은 경우 403 에러가 발생할 수 있습니다. 해당 요청에 대한 접근 권한을 확인해보세요.

  2. 인증 토큰 오류: 인증 토큰이 올바르지 않거나 만료된 경우에도 403 에러가 발생할 수 있습니다. 토큰의 유효성을 확인하고, 만료되었다면 새로운 토큰을 발급받아 사용해보세요.

  3. CORS(Cross-Origin Resource Sharing) 설정 오류: 다른 도메인에서 온 요청에 대해 서버가 허용하지 않는 경우에도 403 에러가 발생할 수 있습니다. CORS 설정을 확인해보세요.

또한, 이 외에도 다른 원인일 수 있으니 더 자세한 정보가 필요할 경우에는 로그를 확인하거나 디버깅을 통해 원인을 찾아보세요.

해당 코드를 보면 JwtAuthorizationFilter 클래스와 SecurityConfig 클래스가 있는데, 이 부분에서 권한 인증과 관련된 설정이 이루어지고 있을 것입니다. 관련 설정을 확인해보시고 오류를 해결해보세요.

더 자세한 도움이 필요하시다면, 코드의 다른 부분이나 로그 정보 등을 함께 공유해주시면 더 정확한 도움을 드릴 수 있을 거예요. 수고하세요!