티스토리 뷰
JWT payload에 원하는 정보를 넣기 위해 일단 권한 서버부터 수정해 보자
1. OAuth2TokenCustomizer 빈을 재정의하면 된다
@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer(UserDetailsService userDetailsService) {
return context -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
String username = context.getPrincipal().getName();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
CustomUserDetails user = (CustomUserDetails) userDetails;
context.getClaims().claim("id", user.getId());
context.getClaims().claim("email", user.getEmail());
}
};
}
- user의 id와 email 정보를 JWT claims 정보에 추가했다(user.getId(), usesr.getEmail())
2. 위의 코드를 보면 UserDetails를 구현해 CustomUserDetails로 사용했다. 이를 위해서 User 정보를 얻어오는 JdbcUserDetailsManager를 확장해 CustomJdbcUserDetailsManager도 만들었다
(이 내용은 필수로 해야 하는 작업은 아니다)
2-1. UserDetails 정의
@Getter
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
private final Long id;
private final String email;
private final String password;
private final boolean enabled;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getUsername() {
return email;
}
}
- user에 따른 권한 항목은 없기 때문에 빈 리스트를 리턴한다(List.of())
- username으로는 email을 사용하도록 했다
2-2. CustomJdbcUserDetailsManager 정의
public class CustomJdbcUserDetailsManager extends JdbcUserDetailsManager {
public CustomJdbcUserDetailsManager(DataSource dataSource) {
super(dataSource);
}
@Override
protected List<UserDetails> loadUsersByUsername(String username) {
JdbcTemplate jdbcTemplate = getJdbcTemplate();
return jdbcTemplate.query(getUsersByUsernameQuery(), (ResultSet rs, int rowNum) -> {
Long id = rs.getLong(1);
String email = rs.getString(2);
String password = rs.getString(3);
boolean enabled = rs.getBoolean(4);
return new CustomUserDetails(id, email, password, enabled);
}, username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<UserDetails> userDetails = loadUsersByUsername(username);
if (userDetails.isEmpty()) {
throw new UsernameNotFoundException(username);
}
return userDetails.get(0);
}
}
- username으로부터 userDetils를 조회하기 위한 빈을 정의했다
2-3. UserDetailsService와 PasswordEncoder 빈 정의
- 2-2 코드의 loadUsersByUsername 함수를 보면 쿼리를 실행해 그 결과에서 정보를 추출해 CustomUserDetails를 만들어줬다
- 쿼리를 실행할 쿼리문과 권한을 조회하는 쿼리문을 정의하기 위해 UserDetailsService를 정의하고 추가적으로 user의 비밀번호 암호화 관리를 위해 PasswordEncoder를 정의했다
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
CustomJdbcUserDetailsManager manager = new CustomJdbcUserDetailsManager(dataSource);
manager.setUsersByUsernameQuery(
"select id, email, password, true from user where email = ?"
);
manager.setAuthoritiesByUsernameQuery(
"select email, null from user where email = ?"
);
return manager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
권한 서버에 대한 설정이 마무리됐으니 이제 JWT 정보를 사용할 리소스 서버 설정을 시작해 보자
1. JWT에서 정보를 추출하기 위한 Converter를 구현한다. Jwt를 입력받아 사용할 UserToken으로 변환하는 Converter이다
@Component
public class CustomJwtAuthenticationConverter implements Converter<Jwt, UserToken> {
@Override
public UserToken convert(Jwt jwt) {
Long id = Long.valueOf(jwt.getClaimAsString("id"));
String userId = jwt.getClaimAsString("sub");
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_USER"));
// 🔑 JwtAuthenticationToken 대신 CustomAuthenticationToken 반환
return new UserToken(id, userId, authorities);
}
}
2. 위에서 사용한 UserToken을 정의한다
@Getter
public class UserToken extends AbstractAuthenticationToken {
private final Long id;
private final String email;
public UserToken(Long id, String email, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.id = id;
this.email = email;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null; // JWT 기반이라 자격증명은 없음
}
@Override
public Object getPrincipal() {
return email;
}
}
3. JWT를 해석하기 위한 디코더를 정의한다
3-1. JwtDecoder 정의
@Value("${oauth2.resourceserver.jwt.jwk-set-uri}")
private String jwkSetUri;
@Bean
public JwtDecoder jwtDecoder(JwtKeyProvider keyProvider) {
return NimbusJwtDecoder
.withJwkSetUri(jwkSetUri)
.build();
}
3-2. JWT 사인한 키를 얻기 위한 url을 yaml에 정의
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:8084/oauth2/jwks
4. 위에서 정의한 빈들을 SecurityFilter의 리소스서버 정의 부분에 설정
.oauth2ResourceServer(oauth2 ->
oauth2
.jwt(jwt -> jwt.decoder(jwtDecoder)
.jwtAuthenticationConverter(customJwtAuthenticationConverter)
)
)
이제 모든 서버 설정이 완료됐다
다음으로는 마지막으로 위의 과정을 테스트하기 위한 웹페이지를 리액트로 만들어 보도록 하자
참고자료
Core Model / Components :: Spring Security
The OAuth2AuthorizationService is the central component where new authorizations are stored and existing authorizations are queried. It is used by other components when following a specific protocol flow – for example, client authentication, authorizatio
docs.spring.io
OAuth 2.0 Resource Server JWT :: Spring Security
Most Resource Server support is collected into spring-security-oauth2-resource-server. However, the support for decoding and verifying JWTs is in spring-security-oauth2-jose, meaning that both are necessary in order to have a working resource server that s
docs.spring.io
1. SpringSecurity로 OAuth2 인증서버 만들기 - 1 (권한서버 만들기)
2. SpringSecurity로 OAuth2 인증서버 만들기 - 2 (클라이언트서버 만들기)
3. SpringSecurity로 OAuth2 인증서버 만들기 - 3 (리소스서버 만들기)
4. SpringSecurity로 OAuth2 인증서버 만들기 - 4 (JWT payload 커스텀)
'Spring > Security' 카테고리의 다른 글
| SpringSecurity로 OAuth2 인증서버 만들기 - 5 (Frontend) End. (1) | 2026.06.21 |
|---|---|
| SpringSecurity로 OAuth2 인증서버 만들기 - 3 (리소스서버 만들기) (0) | 2026.06.05 |
| SpringSecurity로 OAuth2 인증서버 만들기 - 2 (클라이언트서버 만들기) (0) | 2026.05.28 |
| SpringSecurity로 OAuth2 인증서버 만들기 - 1 (권한서버 만들기) (0) | 2026.05.23 |
| [Security] hasIpAddress 사용하기 (0) | 2024.03.09 |
- Total
- Today
- Yesterday
