Spring/괴발개발

[괴발개발] TODO 웹 개발 노트 - 권한 부여

오잎 클로버 2022. 2. 19. 14:00
728x90

기존에 존재했던 코드를 거의 다 갈아엎었습니다.

아무래도 RESTful API를 본인 어플리케이션에서 사용하면서 권한을 부여하기에는 조금 무리가 있는 것 같았기에 갈아엎었습니다.

(JWT가 문제였는 데, JWT를 사용하여서 해더를 통해 권한을 부여하여야하는 데, 이를 관리하기 위해서는 쿠키에 값을 저장을 해야합니다. 그럼 결국에는 코드 갈아엎는 것은 동일하였기에 코드를 그냥 갈아엎고, RESTful를 포기하였습니다.)

 

@EnableWebSecurity
@AllArgsConstructor
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private MemberService memberService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(memberService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/member/info").hasRole("MEMBER")
                .antMatchers("/**").permitAll()
                .and()
                .formLogin()
                    .loginPage("/member/login")
                    .defaultSuccessUrl("/member/login/result")
                    .permitAll()
                .and()
                    .logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
                    .logoutSuccessUrl("/member/logout/result")
                    .invalidateHttpSession(true)
                .and()
                    .exceptionHandling().accessDeniedPage("/member/denied");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/webjars/**", "/h2-console/**", "/css/**", "/js/**", "/img/**", "/lib/**");
    }
}

권한 부여 방식을 token 방식이 아닌 SpringSecurity 방식으로 변경하였습니다.

(UserDetailsService implements 하여 앱 어플리케이션 내부적으로만 작동)

@Controller
public class MainController {

    // 메인 페이지
    @GetMapping("/")
    public String getIndex() {
        return "home";
    }

    // 어드민 페이지
    @GetMapping("/admin")
    public String getAdmin() {
        return "admin";
    }

}

그리고 html (타임리프 템플릿 엔진) 파일을 생성해 Controller로 매핑

@RequiredArgsConstructor
@RequestMapping("/member")
@Controller
public class MemberController {

    private final MemberService memberService;

    @PostMapping("/signup")
    public String signUp(MemberSignUpDto memberSignUpDto) {
        memberService.signUp(memberSignUpDto);
        return "redirect:/member/login";
    }

    // 회원가입 페이지
    @GetMapping("/signup")
    public String getSignup() {
        return "signup";
    }

    // 로그인 페이지
    @GetMapping("/login")
    public String getLogin() {
        return "login";
    }

    // 로그인 결과 페이지
    @GetMapping("/login/result")
    public String getLoginResult() {
        return "loginSuccess";
    }

    // 로그아웃 결과 페이지
    @GetMapping("/logout/result")
    public String getLogout() {
        return "logout";
    }

    // 접근 거부 페이지
    @GetMapping("/denied")
    public String getDenied() {
        return "denied";
    }

    // 내 정보 페이지
    @GetMapping("/info")
    public String getMyInfo() {
        return "info";
    }

}

로그인은 memberService에서 작동하도록 합니다.

@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {

    private final MemberRepository memberRepository;

    /*
    회원 가입
     */
    @Transactional
    public Long signUp(MemberSignUpDto memberSignUpDto) {
        if (memberRepository.existsByEmail(memberSignUpDto.getEmail())) {
            throw new AlreadyEmailExistedException("이미 존재하는 이메일입니다.");
        }
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return memberRepository.save(memberSignUpDto.toEntity(passwordEncoder)).getId();
    }

    /*
    본인 정보 찾기: Id
     */
    @Transactional(readOnly = true)
    public MemberResponseDto findById(long id) {
        Member member = memberRepository.findById(id)
                .orElseThrow(() -> new MemberNotFoundException("해당 회원을 찾을 수 없습니다."));
        return new MemberResponseDto(member);
    }

    /*
    본인 정보 찾기: email
     */
    @Transactional(readOnly = true)
    public MemberResponseDto findByEmail(String email) {
        Member member = memberRepository.findByEmail(email)
                .orElseThrow(() -> new MemberNotFoundException("해당 회원을 찾을 수 없습니다."));
        return new MemberResponseDto(member);
    }

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Member member = memberRepository.findByEmail(email)
                .orElseThrow(MemberNotFoundException::new);

        List<GrantedAuthority> authorities = new ArrayList<>();

        if (("admin@gmail.com").equals(email)) {
            authorities.add(new SimpleGrantedAuthority(Role.ADMIN.getValue()));
        }
        else {
            authorities.add(new SimpleGrantedAuthority(Role.MEMBER.getValue()));
        }

        return new User(member.getEmail(), member.getPassword(), authorities);
    }
}

 

admin 계정으로 로그인 시, 어드민 권한을 얻고, 그외에는 일단 회원 권한을 얻습니다.

 

예외처리 역시 2가지로 줄였습니다.

(기타 예외처리는 필요하지 않다고 판단하였습니다.)

templates.zip
0.00MB

위 zip 파일은 템플릿 html들 입니다.

 

실행 결과

메인 페이지
회원가입
로그인
로그인 성공
어드민 계정으로 로그인 - 메인
어드민 페이지
로그아웃
어드민 계정이 아닌 일반 계정 - 메인
내 정보 확인 페이지

다음에는...

글을 작성하는 html과 기능을 수정 및 추가하도록 하겠습니다.

또, Docker 배포까지 하도록 하겠습니다.

 

 

이상입니다.