[Spring Security/JWT] 스프링 시큐리티 JWT

황인찬·2024년 8월 11일
post-thumbnail

동작 원리

구현

  • 인증: 로그인
  • 인가: JWT를 통한 경로별 접근 권한
  • 회원가입
  • 프론트엔드 없이 API 응답 방식으로 진행할 예정

JWT 인증 방식 시큐리티 동작원리

  • 회원가입: 내부 로직은 세션 방식과 JWT 차이 없음
  • 로그인(인증): 로그인 요청을 받은 후 세션 방식은 세션이 유저 정보를 저장하지만, JWT 방식은 토큰을 생성하여 응답
  • 경로접근(인가): JWT Filter를 통해 요청의 헤더에서 JWT를 찾아 검증 후 일시적으로 요청에 대한 세션 생성(생성된 세션은 요청이 끝나면 소멸)

프로젝트 생성 및 의존성 추가

JWT 의존성 추가

  • 대부분 0.11.5버전을 사용하지만 최신 버전은 0.12.3(버전 별로 구현 방법이 다르니 주의)
  • 0.12.3으로 구현 예정
dependencies {

    implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
    implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
    implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
}

기본 Controller 생성

  • AdminController
@RestController
public class AdminController {

    @GetMapping("/admin")
    public String adminP() {
        return "Admin Controller";
    }
}
  • MainController
@RestController
public class MainController {

    @GetMapping("/")
    public String mainP() {
        return "Main Controller";
    }
}

SecurityConfig

기본 요소

  • SecurityConfig 기본 요소 작성
@Configuration
@EnableWebSecurity

public class SecurityConfig {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf((csrf) -> csrf.disable());
        http
                .formLogin((auth) -> auth.disable());
        http
                .httpBasic((auth) -> auth.disable());
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/", "/join", "/login").permitAll()
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .anyRequest().authenticated());
        //세션 설정
        http
                .sessionManagement((session) -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();

    }
}

DB연결 및 Entity 작성

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://아이피:3306/데이터베이스?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
    username: 아이디
    password: 비밀번호
  jpa:
    hibernate:
      ddl-auto: none
      properties:
        hibernate:
          format_sql: true
logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type: trace

User 엔티티 작성

@Entity
@Getter
@Setter
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;

    private String role;
}

UserRepository 작성

public interface UserRepository extends JpaRepository<User, Long> {
}

회원가입 로직 구현

JoinDto

@Getter
@Setter
public class JoinDto {

    private String username;
    private String password;
}

JoinController

  • 일단은 회원가입 여부 상관없이 ok 리턴하는 걸로 작성했음
@RestController
public class JoinController {

    private final JoinService joinService;

    public JoinController(JoinService joinService) {
        this.joinService = joinService;
    }

    @PostMapping("/join")
    public String joinProcess(JoinDto joinDto) {
        joinService.joinProcess(joinDto);
        return "ok";
    }
}

JoinService

@Service
public class JoinService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    public JoinService(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userRepository = userRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    public void joinProcess(JoinDto joinDto) {
        String username = joinDto.getUsername();
        String password = joinDto.getPassword();

        boolean isExist = userRepository.existsByUsername(username);

        if (isExist) {
            return;
        }

        User user = new User();
        user.setUsername(username);
        user.setPassword(bCryptPasswordEncoder.encode(password));
        user.setRole("ROLE_ADMIN");

        userRepository.save(user);
    }
}

UserRepository

public interface UserRepository extends JpaRepository<User, Long> {

    boolean existsByUsername(String username);
}

로그인 필터 구현

스프링 시큐리티 필터 동작 원리

  • 스프링 시큐리티는 클라이언트 요청이 여러개의 필터를 거쳐 DispatcherServlet(Controller)로 향하는 중간 필터에서 요청을 가로챈 후 검증(인증/인가)을 진행
  • 클라이언트 -> 서블릿 필터 -> 서블릿(컨트롤러)
    • 서블릿 컨테이너는 톰캣이라고 생각하면 됨
  • DelegatingFilterProxy: 서블릿 컨테이너에 존재하는 필터 체인에 DelegatingFilterProxy를 등록한 뒤 모든 요청을 가로챔(서블릿 컨테이너와 스프링 IOC 컨테이너의 연결 다리 느낌)
  • 서블릿 필터 체인의 DelegatingFilter → Security 필터 체인 (내부 처리 후) → 서블릿 필터 체인의 DelegatingFilter
    • 가로챈 요청은 Security 필터 체인에서 처리 후 상황에 따른 거부, 리다이렉션, 서블릿으로 요청 전달
  • SecurityFilterChain의 필터 목록과 순서

FormLogin 방식 - UsernamePasswordAuthenticationFilter

  • 클라이언트에서 username, password가 넘어오면 SecurityFilter를 통과할 때 UsernamePasswordAuthentication 필터에서 회원 검증을 진행
  • 회원 검증의 경우 UsernamePasswordAuthenticationFilter가 호출한 AuthenticationManager를 통해 진행하며 DB에서 조회한 데이터를 UserDetailsService를 통해 받음
  • JWT 방식은 formLogin 방식을 disable()처리 했기 때문에 기본적으로 활성화된 UsernamePasswordAuthenticationFilter는 동작하지 않음
  • 필터를 커스텀해서 등록해야함

커스텀 UsernamePasswordAuthentication 필터 작성

  • LoginFilter
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    public LoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //클라이언트 요청에서 username, password를 가로채 추출
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        //스프링 시큐리티에서 username, password를 검증하기 위해서는 token에 담아야함
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);

        //token에 담아 검증을 위한 AuthenticationManager로 전달
        return authenticationManager.authenticate(authToken);
    }

    //로그인 성공 시
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

    }

    //로그인 실패 시
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {

    }
}

SecurityConfig 설정

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final AuthenticationConfiguration authenticationConfiguration;

    public SecurityConfig(AuthenticationConfiguration authenticationConfiguration) {
        this.authenticationConfiguration = authenticationConfiguration;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf((csrf) -> csrf.disable());
        http
                .formLogin((auth) -> auth.disable());
        http
                .httpBasic((auth) -> auth.disable());
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/", "/join", "/login").permitAll()
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .anyRequest().authenticated());
        //필터 추가 LoginFilter는 인자를 받음(AuthenticationManager()메소드에 authenticationConfiguration 객체를 넣어야함) 따라서 등록 필요
        http
                .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)),
                        UsernamePasswordAuthenticationFilter.class);

        //세션 설정
        http
                .sessionManagement((session) -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));



        return http.build();

    }
}

DB기반 로그인 검증 로직

UserRepository

public interface UserRepository extends JpaRepository<User, Long> {

    boolean existsByUsername(String username);
    User findByUsername(String username);
}

UserDetailsService 커스텀 구현

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);

        if(user != null) {
            return new CustomUserDetails(user);
        }

        return null;
    }
}

UserDetails 커스텀 구현

public class CustomUserDetails implements UserDetails {

    private final User user;

    public CustomUserDetails(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collection = new ArrayList<>();
        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collection;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

JWT 발급 및 검증 클래스

JWT 발급 및 검증

  • 로그인 -> 로그인 성공 -> JWT 발급
  • 접근 -> JWT 검증
  • JWTUtil이라는 클래스를 통해 JWT 발급과 검증을 진행하는 메소드 작성

JWT 생성 원리

JWT 공식 홈페이지

  • JWT는 Header, Payload, Signature로 구성
  • Header
    • JWT임을 명시
    • 사용된 암호화 알고리즘
  • Payload
    • 정보
  • Signature
    • 암호화 알고리즘((BASE64(Header) + BASE64(Payload)) + 암호화키)
  • JWT는 단순 BASE64 방식으로 인코딩 하기 때문에 외부에서 쉽게 디코딩 가능
  • 외부에서 열람해도 되는 정보만 담을 것
  • 토큰 자체의 발급처를 확인하기 위해서 사용

JWT 암호화 방식

  • 양방향
    • 대칭키: 이 프로젝트는 양방향 대칭키 사용(HS256)
      • 암호화와 복호화에 같은 키를 사용
    • 비대칭키
      • 암호화와 복호화에 서로 다른 키를 사용
  • 단방향
    • 복호화 불가능

암호화 키 저장

  • 암호화 키는 하드코딩 방식으로 내부에 탑재하는 것을 지양, 변수 파일에 저장할 것
  • application.yml
spring:
  jwt:
    secret: dlscksdlrkcjdmadmfhwlsgodgksmstmvmfldwpdlejqmfdbxlvmfhwprxmdlqslek

JwtUtil

  • 토큰 payload에 저장할 정보
    • username
    • role
    • 생성일
    • 만료일
  • JwtUtil 구현 메소드
    • JwtUtil 생성자
    • username 확인 메소드
    • role 확인 메소드
    • 만료일 확인 메소드
  • JwtUtil 0.12.3
@Component
public class JwtUtil {

    private SecretKey secretKey;

    //미리 저장해둔 String키를 기반으로 객체 키 생성
    public JwtUtil(@Value("${spring.jwt.secret}") String secret) {
        this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
    }

    //유저네임 가져오기
    public String getUsername(String token) {
        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
    }

    //롤값 가져오기
    public String getRole(String token) {
        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
    }

    //토큰 소멸 여부
    public Boolean isExpired(String token) {
        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
    }

    //토큰 생성
    public String createJwt(String username, String role, Long expiredMs) {
        return Jwts.builder()
                //키에 대한 데이터
                .claim("username", username)
                .claim("role", role)
                //현재 발행 시간
                .issuedAt(new Date(System.currentTimeMillis()))
                //언제 소멸 될 것인지
                .expiration(new Date(System.currentTimeMillis() + expiredMs))
                //키를 통해 암호화 진행
                .signWith(secretKey)
                .compact();
    }

}
  • JwtUtil 0.11.5
@Component
public class JWTUtil {

    private Key key;

    public JWTUtil(@Value("${spring.jwt.secret}")String secret) {


				byte[] byteSecretKey = Decoders.BASE64.decode(secret);
        key = Keys.hmacShaKeyFor(byteSecretKey);
    }

    public String getUsername(String token) {

        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get("username", String.class);
    }

    public String getRole(String token) {

        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get("role", String.class);
    }

    public Boolean isExpired(String token) {

        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getExpiration().before(new Date());
    }

    public String createJwt(String username, String role, Long expiredMs) {

				Claims claims = Jwts.claims();
        claims.put("username", username);
        claims.put("role", role);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiredMs))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }
}

로그인 성공 JWT 발급

JwtUtil 주입

  • LoginFilter에 주입
public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final JwtUtil jwtUtil;

    public LoginFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //클라이언트 요청에서 username, password를 가로채 추출
        String username = obtainUsername(request);
        String password = obtainPassword(request);

        //스프링 시큐리티에서 username, password를 검증하기 위해서는 token에 담아야함
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, password, null);

        //token에 담아 검증을 위한 AuthenticationManager로 전달
        return authenticationManager.authenticate(authToken);
    }

    //로그인 성공 시
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();

        //username 뽑아내기
        String username = customUserDetails.getUsername();

        //role 뽑아내기
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
        GrantedAuthority grantedAuthority = iterator.next();
        String role = grantedAuthority.getAuthority();

        String token = jwtUtil.createJwt(username, role, 60*60*10L);

        response.addHeader("Authorization", "Bearer " + token);

    }

    //로그인 실패 시
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setStatus(401);
    }
}
  • SecurityConfig에서 Filter에 JwtUtil 주입
@Component
public class JwtUtil {

    private SecretKey secretKey;

    //미리 저장해둔 String키를 기반으로 객체 키 생성
    public JwtUtil(@Value("${spring.jwt.secret}") String secret) {
        this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
    }

    //유저네임 가져오기
    public String getUsername(String token) {
        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
    }

    //롤값 가져오기
    public String getRole(String token) {
        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
    }

    //토큰 소멸 여부
    public Boolean isExpired(String token) {
        return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
    }

    //토큰 생성
    public String createJwt(String username, String role, Long expiredMs) {
        return Jwts.builder()
                //키에 대한 데이터
                .claim("username", username)
                .claim("role", role)
                //현재 발행 시간
                .issuedAt(new Date(System.currentTimeMillis()))
                //언제 소멸 될 것인지
                .expiration(new Date(System.currentTimeMillis() + expiredMs))
                //키를 통해 암호화 진행
                .signWith(secretKey)
                .compact();
    }

}

LoginFilter - successfulAuthentication()

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    //로그인 성공 시
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();

        //username 뽑아내기
        String username = customUserDetails.getUsername();

        //role 뽑아내기
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
        GrantedAuthority grantedAuthority = iterator.next();
        String role = grantedAuthority.getAuthority();

        String token = jwtUtil.createJwt(username, role, 60*60*10L);

        response.addHeader("Authorization", "Bearer " + token);

    }
  • HTTP 인증 방식은 RFC 7235 정의에 따라 아래 인증 헤더 형태를 가짐
    • 타입 뒤에 띄어쓰기 들어감!
Authorization: 타입 인증토큰

//예시
Authorization: Bearer 인증토큰string

LoginFilter - unsuccessfulAuthentication()

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final JWTUtil jwtUtil;

    public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {

        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {				
		//로그인 실패시 401 응답 코드 반환
        response.setStatus(401);
    }
}

JWT 검증 필터

JwtFilter 역할

  • 스프링 시큐리티에 담긴 JWT를 검증하기 위함
  • Authorization 키에 JWT가 존재하는 경우 JWT를 검증하고 강제로 SecurityContextHolder에 세션을 생성(이때 세션은 STATELESS 방식으로 관리되어 해당 요청이 끝나면 소멸)

JwtFilter 구현

public class JwtFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    public JwtFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //request에서 Authorization 헤더를 추출
        String authorization = request.getHeader("Authorization");

        //Authorization 헤더 검증
        if (authorization == null || !authorization.startsWith("Bearer ")) {
            System.out.println("token null");
            //이 필터를 종료하고 다음 필터로 넘겨주기
            filterChain.doFilter(request, response);

            //조건이 해당되면 메소드 종료(필수)
            return;
        }

        //Bearer 부분 제거 후 순수 토큰 획득
        String token = authorization.split(" ")[1];

        //토큰 소멸 시간 검증
        if (jwtUtil.isExpired(token)) {
            System.out.println("token expired");
            //이 필터를 종료하고 다음 필터로 넘겨주기
            filterChain.doFilter(request, response);

            //조건이 해당되면 메소드 종료(필수)
            return;
        }

        //토큰에서 정보 획득
        String username = jwtUtil.getUsername(token);
        String role = jwtUtil.getRole(token);

        //user 엔티티를 생성하여 set
        User user = new User();
        user.setUsername(username);
        //비밀번호는 토큰에 없으나 초기화를 같이 진행해줘야함.
        //db에서 조회하면 매번 요청할때마다 db를 조회해야하는 손해 발생
        //ContextHolder에 정확한 비밀번호를 담을 필요가 없으므로 임시 비밀번호 생성
        user.setPassword("temppassword");
        user.setRole(role);

        //UserDetails에 회원 정보 객체 담기
        CustomUserDetails customUserDetails = new CustomUserDetails(user);

        //스프링 시큐리티 인증 토큰 생성
        Authentication authToken = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());

        //세션에 사용자 등록
        SecurityContextHolder.getContext().setAuthentication(authToken);

        filterChain.doFilter(request, response);
    }
}

SecurityConfig에 등록

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf((csrf) -> csrf.disable());
        http
                .formLogin((auth) -> auth.disable());
        http
                .httpBasic((auth) -> auth.disable());
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/", "/join", "/login").permitAll()
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .anyRequest().authenticated());
        //필터 추가 LoginFilter는 인자를 받음(AuthenticationManager()메소드에 authenticationConfiguration 객체를 넣어야함) 따라서 등록 필요
        http	//JWT 필터 등록
                .addFilterAfter(new JwtFilter(jwtUtil), LoginFilter.class) 
                .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil),
                        UsernamePasswordAuthenticationFilter.class);

        //세션 설정
        http
                .sessionManagement((session) -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));



        return http.build();

    }
}

세션 정보

MainController

  • 여기서 세션 정보 확인 해볼거임
  • 일시적인 세션이 생성되기 때문에 정보를 가져올 수 있다는 것을 알기 위함
@RestController
public class MainController {

    @GetMapping("/")
    public String mainP() {
        //세션에 현재 사용자 아이디
        String username = SecurityContextHolder.getContext().getAuthentication().getName();

        //세션에 현재 사용자 롤
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
        GrantedAuthority auth = iterator.next();
        String role = auth.getAuthority();

        return "Main Controller : " + username + " " + role;
    }
}

CORS 설정

CORS란?

  • CORS(Cross-Origin Resource Sharing)
  • 원래 도메인과 다른 도메인에서 요청된 웹페이지 자원에 대해 사용을 허가하는 메커니즘

CORS 설정

  • SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //cors 설정
        http
                .cors((cors) -> cors
                        .configurationSource(new CorsConfigurationSource() {
                            @Override
                            public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                                CorsConfiguration configuration = new CorsConfiguration();

                                configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
                                configuration.setAllowedMethods(Collections.singletonList("*"));
                                configuration.setAllowCredentials(true);
                                configuration.setAllowedHeaders(Collections.singletonList("*"));
                                configuration.setMaxAge(3600L);

                                configuration.setExposedHeaders(Collections.singletonList("Authorization"));

                                return configuration;
                            }
                        }));
                        
        return http.build();

    }
}
  • CorsMvcConfig
@Configuration
public class CorsMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
                .addMapping("/**")
                .allowedOrigins("http://localhost:3000");
    }
}

참조링크
개발자 유미 - 스프링 시큐리티 JWT

profile
찬이's 개발로그

0개의 댓글