Home 로그인 문제 해결하기
Post
Cancel

로그인 문제 해결하기

로그인 문제 해결하기

문제

기존에 사용하고 있던 폼 로그인에 내가 작성한 oauth2를 적용하게 되면서

로그인이 제대로 이루어지지 않는 상황이 생겼다.




코드

Security

1
2
3
4
http.authorizeRequests()
    .antMatchers("/css/**", "/oauth2/**", "/user/**", "/taste/**", "/js/**").permitAll()
    .antMatchers("/admin/**").hasAuthority("ADMIN")
    .anyRequest().authenticated();
1
2
3
4
http.authorizeRequests()
    .antMatchers(HttpMethod.GET, GET_WHITE_LIST).permitAll() // GET 요청 허용
    .antMatchers(VIEW_LIST).permitAll()
    .antMatchers(USER_ENABLE).hasAnyRole("USER","ADMIN") // USER 접근 가능

2개의 코드를 보여주는 이유는 첫번째 코드는 제대로 oauth2와 폼 로그인이 동작했지만

두번째 코드는 기존에 작성되어있던 코드로써 로그인을 하면 엑세스가 거부되면서 403 error가 떴다.

여기에서 힌트를 얻어서 문제를 쉽게 해결할 수 있었다.




사용자의 역할 정보를 가져오기 위해 UserDetailsService를 구현하고, 해당 서비스를 설정하여 Spring Security에서 사용하도록 해야한다.

그 말은 UserDetailService에서 권한을 설정해야한다

그런데 실제 UserDetailService에서는 권한 설정이 없었다.

UserDetailServiceImpl

1
2
3
4
5
6
7
8
@Override
public UserDetails loadUserByUsername(String username) {
    log.info("[loadUserByUsername] loadUserByUsername 수행. username : {}", username);

    return userRepository.findByNickname(username).orElseThrow(() -> {
        throw new UserNotFoundException();
    });
}

그래서 권한을 가져올 수 없으니 로그인을 해도 Security에서 권한 설정한 페이지를 볼 수 없던 것이었다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Slf4j
@Service
@RequiredArgsConstructor
public class UserDetailServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> user = userRepository.findByNickname(username);
        if (user.isEmpty()) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }

        // 사용자의 권한 정보를 조회하여 List<GrantedAuthority>로 변환
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.get().getRole().name()));

        // UserDetails 객체 생성
        return new org.springframework.security.core.userdetails.User(
                user.get().getNickname(),
                user.get().getPassword(),
                authorities
        );
    }
}

UserDetailsService 인터페이스는 스프링 시큐리티에서 인증과 관련된 작업을 처리하기 위한 인터페이스다.

스프링 시큐리티는 사용자 인증 정보를 가져와서 인증 작업을 수행하는 데 사용된다.

이 인터페이스를 구현한 클래스는 사용자의 인증 정보를 제공하고,

필요한 경우 데이터베이스나 다른 소스로부터 사용자 정보를 조회하여 인증에 활용한다.

UserDetailsService를 구현하는 클래스에는 실제로 사용자 인증 정보를 조회하는 로직이 포함되어야 한다.

이 로직은 보통 데이터베이스 등의 저장소에서 사용자 정보를 조회하고 UserDetails 객체를 생성하여 반환하는 작업을 수행한다.


스프링 시큐리티에서 UserDetailsService를 사용하기 위해서는 해당 클래스를 빈으로 등록해야 한다.

이 클래스는 비즈니스 로직을 수행하는 서비스 계층의 구현체로 볼 수 있기 때문에

@Service 어노테이션을 사용하여 UserDetailsService 구현 클래스를 서비스 계층의 bean으로 등록한다.

붙이지 않으면 아래와 같이 에러가 뜬다.

1
2
3
4
5
6
7
8
9
10
11
12
***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in dalcho.adme.config.security.JwtTokenProvider required a bean of type 'dalcho.adme.service.UserDetailService' that could not be found.


Action:

Consider defining a bean of type 'dalcho.adme.service.UserDetailService' in your configuration.



get()을 많이 쓰는 것 같아서 아래와 같이 수정했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userRepository.findByNickname(username)
            .map(this::buildUserDetails)
            .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
}

private UserDetails buildUserDetails(User user) {
    List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(user.getRole().name()));
    return new org.springframework.security.core.userdetails.User(
            user.getNickname(),
            user.getPassword(),
            authorities
    );
}

loadUserByUsername 메서드는 주어진 사용자명(username)을 기반으로 사용자 정보를 조회하는 역할을 한다.

userRepository.findByNickname(username)를 호출하여 해당 사용자를 데이터베이스에서 조회한다.

조회한 사용자를 Optional로 받아오고, map 함수를 사용하여 buildUserDetails 메서드를 호출하여 UserDetails 객체를 생성한다.


buildUserDetails는 조회한 사용자 정보를 이용하여 UserDetails 인터페이스를 구현한 객체를 생성하는 역할을 한다.

buildUserDetails는 사용자의 권한 정보를 GrantedAuthority 객체로 변환하여 권한 목록을 생성한다.

SimpleGrantedAuthority 클래스를 사용하여 권한을 나타내고 사용자의 UserRole 값(enum)을 기반으로 권한 객체를 생성한다.

생성된 UserDetails 객체에 사용자명, 비밀번호, 권한 목록을 설정하여 반환한다.



JWT 토큰의 검증을 위한 목적으로 UserDetailServiceImpl를 사용해서

JwtTokenProvider와 OAuth2SuccessHandler에서 사용자의 인증 정보를 가져오게 한다.




권한 값 가져오기

Spring Security에서는 hasAuthority() 메소드에 전달된 권한 값과 사용자의 권한 정보를 비교하여 접근 제어를 수행한다.

따라서 사용자의 권한 정보를 제공하기 위해 UserDetails 인터페이스를 구현한 클래스에서

getAuthorities() 메소드를 구현하여 해당 권한 정보를 반환하는 것이 필요하다.


User

Security에서 권한을 지정하는 설정에서 hasAuthority()에 지정된 권한 값을 가져오기 위해 해당 코드를 작성한다.

1
2
3
4
5
6
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    List<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority(this.role.name()));
    return authorities;
}



Security

1
2
3
4
http.authorizeRequests()
        .antMatchers(VIEW_LIST).permitAll()
        .antMatchers("/admin/**").hasAuthority("ADMIN")
        .anyRequest().authenticated();

어떤 url을 입력해도 VIEW_LIST가 아닌 이상 로그인 페이지로 넘어가게 하고 싶었고

ADMIN이 아닌 이상 모두 USER일테니 admin 페이지만 ADMIN 권한을 체크하게 했다.

This post is licensed under CC BY 4.0 by the author.