로그인한 회원은 마이페이지에서 회원정보를 볼 수 있고, 수정할 수도 있는데,
이러한 요청시 클라이언트가 memberId를 보내준다면 백엔드 작업이 간편해지겠지만
전달받은 토큰에서 member를 찾아내서 처리하기로 했다.
이를 감안하고 컨트롤러를 작성했는데 Security 설정을 전혀 하지 않아서
Authentication, UserDetails, getPrincipal() 등이 빨간글씨로 뜨고 있다.
더 이상은 시큐리티를 미룰 수 없을 것 같다. 이번 주말내로 해치워야지!
@GetMapping("/member")
public ResponseEntity<MemberResponse> getMember(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String username = userDetails.getUsername();
Member member = memberService.findByEmail(username);
MemberResponse memberResponse = MemberMapper.toMemberResponse(member);
return ResponseEntity.ok(memberResponse);
}
@PatchMapping("/member")
public ResponseEntity<MemberResponse> patchMember(
@RequestBody @Valid UpdateRequest updateRequest, Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String username = userDetails.getUsername();
Member member = memberService.updateMember(updateRequest, username);
MemberResponse updatedMemberResponse = MemberMapper.toMemberResponse(member);
return ResponseEntity.ok(updatedMemberResponse);
}
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-test'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
참고자료 : mvnrepository 최신버전확인
secret key의 경우는 64바이트 이상으로 임의의 긴 문자를 사용하여야 하는데
이를 그대로 사용하면 보안이 우려 되므로 환경변수에 담아서 사용하였다.
logging:
level:
server.security: DEBUG
jwt:
header: Authorization
secret: ${SECRET_KEY}
//secret: AHTxTnMLcmBdoFSpwAqRfqCqWJSZWhOtHwI4Aq2O19Wty1o...
access-token-expiration-minutes: 1000
refresh-token-expiration-minutes: 2000
HS512 알고리즘에 사용되는 JWT 서명 키의 크기가 충분하지 않으면 아래와 같은 오류를 볼 수 있으니 넉넉한 길이로 해야한다.

// 새로운 Authority entity 생성
@Entity
@Table(name = "authority")
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "authority_role", length = 50)
private String role;
}
// member Entity에 매핑 추가
public class Member extends Auditable {
@ManyToMany
@JoinTable(
name = "member_authority",
joinColumns = {@JoinColumn(name = "member_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_role")})
private Set<Authority> authorities;

@RestController
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) {
try {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.createToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new TokenResponse(jwt), httpHeaders, HttpStatus.OK);
} catch (AuthenticationException ex) {
ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.UNAUTHORIZED,"아이디 또는 비밀번호가 맞지 않습니다. 다시 확인해 주세요.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);
}
}
}
코드량이 제법 있어서 상세한 코드는 git hub로 확인 할 수 있게끔 링크를 걸어두었다.
alzpaCare 프로젝트
이후 차례로 작업을 하다가 Security 6.1.0버전부터는 설정하던 방식이 바뀌어서 조금 막혔었는데 공식문서와 블로그를 열심히 찾아보면서 익혀나갔다.
토큰방식으로 인증하기 때문에 csrf설정은 끄고
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final TokenProvider tokenProvider;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig(TokenProvider tokenProvider, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtAccessDeniedHandler jwtAccessDeniedHandler) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(withDefaults())
.csrf((csrfConfig) ->
csrfConfig.disable())
.headers((headerConfig) ->
headerConfig.frameOptions(frameOptionsConfig ->
frameOptionsConfig.disable()
)
)
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.httpBasic(withDefaults())
.exceptionHandling((exceptionConfig) ->
exceptionConfig
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler())
)
.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests
.requestMatchers("/","/login/**","/join/**").permitAll()
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll()
)
.with(new JwtSecurityConfig(tokenProvider), Customizer.withDefaults())
.logout((logoutConfig) ->
logoutConfig.logoutSuccessUrl("/"));
return http.build();
}
}
토큰이 필요한 요청시 토큰값을 복붙하여 테스트 하기가 번거로운데 아래 사진처럼 설정하면 테스트시간을 훨씬 줄일 수 있다.


