[Spring Boot] Springsecurity , JWT , Swagger jwt 적용 로직 분석

Posted by 김성철

Spring boot - Springsecurity , JWT , Swagger jwt 적용 로직 분석

소스파일 경로

https://github.com/kkimsungchul/study/tree/master/Spring%20Boot/SpringSecurity  
https://github.com/kkimsungchul/sungchul_ETC/tree/main/src/main/java/com/sungchul/etc/config  

해당 파일 작성 사유

구글링으로 참고한 여러 소스들을 가져다 사용했음  
일단 복붙으로 작성하고 내가 원하는 부분만 커스터마이징 했기 때문에 정확히 어떻게 작동하는지에 대해 한번 서술하면서 정리할 필요를 느낌  
다 적용하고 나니 간단하긴 한데.. 처음이라 그런지 살짝 복잡한 느낌이 들긴함  

작성한 클래스들 목록 및 설명

SpringSecurity  
	csrf  
		CSRFController.java  
  
	jwt  
		config  
			JwtAuthenticationEntryPoint.java  
			JwtRequestFilter.java  
			JwtUserDetailsService.java  
  
		controller  
			JwtAuthenticationController.java  
  
		util  
			JwtTokenUtil.java  
  
		vo  
			JwtRequest.java  
			JwtResponse.java  
  
	security  
		CustomUserDetailsService.java  
		SecurityConfig.java  
		UserContext.java  
		WebSecurityConfig.java  
  
	SwaggerConfig.java  

[ SpringSecurity 적용 ]

	## CustomUserDetailsService.java  
		org.springframework.security.core.userdetails.UserDetailsService 인터페이스를 구현함  
		DB에서 사용자의 정보를 가져 오는 부분이며, 입력받은 ID로 사용자의 정보를 조회해서 "UserContext" 클래스에 담아서 리턴해주는 역할을 함  
  
	## SecurityConfig.java  
		스프링시큐리티의 설정파일  
		어떤 유저가 어떠한 권한이 있는지 체크하고,  
		비밀번호 암호화를 할 수 있도록 암호화 객체를 제공해줌  
		또한 인증없이 접근이 가능한 URL을 설정 할 수 있으며,  
		특정URL에는 특정 권한을 가지고 있는 사용자만이 접근 할 수 있도록 설정할 수 있음  
  
		※ WebSecurityConfig.java 파일을 작성하면서 위에 내용들을 전부다 주석처리하였음  
		※ 위의 내용은 WebSecurityConfig.java 파일에 포함되어있음  
  
	## UserContext.java  
		org.springframework.security.core.userdetails.User 를 상속받아서 구현함  
		넘겨받은 값들을 부모클래스의 생성자에 넣어줌  
		사용자의 ID , 비밀번호, 권한을 넣어줌  
		여기서 넣어준 첫번째 인자값은 꺼내서 사용할 떄 userDetails.getUsername();  로 꺼내서 사용함  

[ JWT 적용 ]
- config 패키지

		## JwtAuthenticationEntryPoint.java  
			허가되지 않은 사용자라면, 접근 불가 메세지를 띄워 리소스 정보획득을 못하게 막아줌  
			해당 클래스는 "WebSecurityConfig.java" 파일에서  아래와 같이 선언하여 사용하며.  
				=================================================================================================================  
				@Autowired  
				private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;  
				=================================================================================================================  
  
			"WebSecurityConfig.java" 파일에서 configure 메소드에서 httpSecurity 에 "authenticationEntryPoint" 옵션에 적용함  
			다만 해당 옵션 적용 시 springSecurity의 기본 로그인 페이지를 사용 할 수 없음  
			해당 옵션을 적용하면 허가되지 않은 페이지 접근시 401오류가 발생하지만,  
			적용하지 않는다면 springSecurity의 html 페이지가 로드됨  
  
				=================================================================================================================  
				.authenticationEntryPoint(jwtAuthenticationEntryPoint)  
				=================================================================================================================  
  
		## JwtRequestFilter.java  
  
			프론트에서 request 를 요청할때마다 해당 필터를 거치도록 해야함  
			해당 필터는 header에 담겨 있는 토큰 정보가 유효한지 확인하는 클래스임  
				=================================================================================================================  
		        final String requestTokenHeader = request.getHeader("jwt")  
				=================================================================================================================  
			위와 같이 헤더에 저장된 값을 꺼내오는 역할을 함  
  
		## JwtUserDetailsService.java  
			위에서 Springsecurity 만 사용할 때 작성한 클래스 "CustomUserDetailsService.java" 와 똑같이 구현하였음.  
			여기서도 똑같이 UserContext.java 클래스를 리턴해줌  
  
	- controller 패키지  
		## JwtAuthenticationController.java  
			사용자의 인증 요청 처리를 담당할 컨트롤러임  
			사용자가 보내온 ID / PW 를 가지고 인증처리를 담당함  
  
			authenticate 메소드를 통해서 사용자가 입력한 ID / PW 를 통해 사용자를 인증함  
			오류가발생할 경우 Exception 을 띄움  
  
			UserDetails클래스의  loadUserByUsername메소드에서 리턴받은 값을 가지고  
			JwtTokenUtil 클래스의 generateToken 메소드를 통해 사용자의 토큰을 발급해줌  
  
	- util 패키지  
		## JwtTokenUtil.java  
			JWT 토큰을 사용할 수 있도록 여러가지 기능들을 구현한 클래스  
			토큰에 저장된 사용자의 ID , 토큰에 저장된 만료일, 토큰이 만료되었는지, 토큰발급등의 로직을 처리함  
  
	- vo 패키지  
		## JwtRequest.java  
			VO임. 사용자가 입력한 ID / PW 를 받아오는 역할을 함  
  
		## JwtResponse.java  
			요청에 대한 응답 객체임.  
			사용자가 ID / PW를 입력하여 요청을 보냈을때, 해당 클래스에 값을 담아서 사용자에게 되돌려줌  
  
	-security 패키지  
		## WebSecurityConfig.java  
			해당 파일은 "SecurityConfig.java" 에서 구현한 내용과 비슷하지만, "JwtAuthenticationEntryPoint" , "JwtRequestFilter" 두개의 클래스를 주입받아서  
			매 요청시 "JwtRequestFilter" 가 작동할 수 있도록 구현, 사용자의 인증 정보가 잘못되었을 경우 "JwtAuthenticationEntryPoint" 를 호출하도록 구현하였음  
				=================================================================================================================  
			    @Autowired  
				private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;  
  
				@Autowired  
				private UserDetailsService jwtUserDetailsService;  
  
				@Autowired  
				private JwtRequestFilter jwtRequestFilter;  
				=================================================================================================================  
  
	-application.yml 파일  
		@value로 꺼내서 사용할 값을 기재  
		=================================================================================================================  
  
		spring :  
		  jwt:  
			secret : jwtsecretkey  
  
		=================================================================================================================  

[ Swagger 에 JWT 적용 ]

	## SwaggerConfig.java  
		"JwtRequestFilter" 를 구현함으로써 스웨거UI와 로그인을 제외한 요청에 대해서 JWT 토큰을 검증하도록 하였음  
		헤더값에 매번 jwt 토큰을 넣어 줄 수가 없으니, 스웨거의 authorize 기능을 확용하기로 함  
		기존에는 api() 메소드에서 빌드 부분에 security 관련 설정을 넣지 않았지만, 여기서는 추가하였음  
  
			=================================================================================================================  
		   public Docket api(){  
				return new Docket(DocumentationType.SWAGGER_2)  
					.apiInfo(apiInfo())  
					.select()  
					.apis(RequestHandlerSelectors.any())  
					.paths(PathSelectors.any())  
					.build()  
					.securityContexts(Arrays.asList(securityContext()))  
					.securitySchemes(Arrays.asList(apiKey()));  
			}  
  
			=================================================================================================================  
  
		apiKey() 메소드에서는 apikey의종류, key의 이름, 포함되어 있는 곳에대한 정보를 입력함  
			=================================================================================================================  
  
			private ApiKey apiKey() {  
				return new ApiKey("JWT", "jwt", "header");  
			}  
  
			=================================================================================================================  
  
		그리고 아래의 옵션은 뭐 잘 모르겟음...  
		SecurityContext메소드는 호출되면 defaultAuth() 에서 받아온 값을 넣어서 생성하는것으로 보이고  
		defaultAuth 메소드는 적용 범위와 어떠한 인증 방식을 사용할지 (JWT , Authorization) 를 정하는 것으로 보인다.  
		=================================================================================================================  
		private SecurityContext securityContext() {  
			return SecurityContext.builder().securityReferences(defaultAuth()).build();  
		}  
  
		private List<SecurityReference> defaultAuth() {  
			AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");  
			AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];  
			authorizationScopes[0] = authorizationScope;  
			return Arrays.asList(new SecurityReference("JWT", authorizationScopes));  
		}  
  
		=================================================================================================================