포트폴리오 서비스(Spring Security)

·2024년 1월 18일
0

Portfolio Backtest

목록 보기
18/31

오늘 할일

  • Spring Security을 적용해서 admin page는 접근하지 못하도록 변경하자

Spring Security 적용 후, 경로별 접근권한 설정하기

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorizeHttpRequests) ->
                        authorizeHttpRequests
                                .requestMatchers("/admin/**").hasRole("ADMIN")
                                .requestMatchers("/**").permitAll()
                )
                .csrf((csrf) -> csrf
                        .ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
                .headers((headers) -> headers
                        .addHeaderWriter(new XFrameOptionsHeaderWriter(
                                XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
        ;
        return http.build();
    }
}

여기서 antMatchers()가 requestMatchers로 바뀌었다는것을 몰라서 한참이 걸렸다. 더 이상 antMatchers는 존재하지 않는다. 추가적으로 h2-console도 설정해주었다.

User 구성하기

Login 구현하는 순서

  • Role 구현하기
  • UserDetails를 이용하여 Entity 구현
  • Repository 구현
  • UserDetailsService를 이용하여 Service 구현
  • Spring Security에서 인증 구현하기

Role 구현하기

@Getter
public enum UserRole {
    ADMIN("ROLE_ADMIN"),
    USER("ROLE_USER");

    UserRole(String value) {
        this.value = value;
    }

    private String value;
}

간단하게 Admin, User로 이루어진 권한을 만들었다.

UserDetails를 이용하여 Entity 구현

package com.hada.portfolio.user;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

@Entity
@Getter
@Setter
public class SiteUser implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String password;

    private String authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        ArrayList<GrantedAuthority> auth = new ArrayList<GrantedAuthority>();
        auth.add(new SimpleGrantedAuthority(authorities));

        return auth;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

권한이 여러가지가 아니기 때문에 String을 통해 문자열로 저장하였다.

UserDetailsService를 이용하여 Service 구현

@Service
public class SiteUserService implements UserDetailsService {

    private final SiteUserRepository siteUserRepository;
    private final PasswordEncoder passwordEncoder;

    public SiteUserService(SiteUserRepository siteUserRepository, PasswordEncoder passwordEncoder){
        this.siteUserRepository = siteUserRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SiteUser siteUser = siteUserRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
        return new User(
                siteUser.getUsername(), siteUser.getPassword(), siteUser.getAuthorities());
    }

    public SiteUser save(String username, String password) {
        SiteUser siteUser = new SiteUser();
        siteUser.setUsername(username);
        siteUser.setPassword(passwordEncoder.encode(password));
        if(username.equals("admin"))
        	siteUser.setAuthorities(UserRole.ADMIN.getValue());
        else
        	siteUser.setAuthorities(UserRole.USER.getValue());
    	return siteUserRepository.save(siteUser);
    }

    public boolean findByUsername(String username) {
    	return siteUserRepository.findByUsername(username).isPresent();
    }
}

중복확인 후 회원가입을 진행하기 위해서 findByUsername을 구현하였다. ADMIN의 권한을 가지는 아이디는 admin으로 하였다.

Spring Security에서 인증 구현하기

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((authorizeHttpRequests) ->
                        authorizeHttpRequests
                                .requestMatchers("/admin/**").hasRole("ADMIN")
                                .requestMatchers("/**").permitAll()
                )
                .csrf((csrf) -> csrf
                        .ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
                .headers((headers) -> headers
                        .addHeaderWriter(new XFrameOptionsHeaderWriter(
                                XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN)))
                .formLogin((formLogin) -> formLogin
                        .loginPage("/user/login")
                        .defaultSuccessUrl("/"))
                .logout((logout) -> logout
                        .logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
                        .logoutSuccessUrl("/")
                        .invalidateHttpSession(true))
        ;
        return http.build();
    }

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

    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
    

UserController

@Controller
@RequestMapping("/user")
public class UserController {

    private final SiteUserService siteUserService;

    public UserController(SiteUserService siteUserService){
        this.siteUserService = siteUserService;
    }

    @GetMapping("/signup")
    public String signup(UserCreateForm userCreateForm) {
        return "signup_form";
    }

    @PostMapping("/signup")
    public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "signup_form";
        }

        if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
            bindingResult.rejectValue("password2", "passwordInCorrect",
                    "2개의 패스워드가 일치하지 않습니다.");
            return "signup_form";
        }

        if (siteUserService.findByUsername(userCreateForm.getUsername())){
            bindingResult.rejectValue("username", "usernameDuplicated",
                    "이미 사용중인 사용자ID입니다.");
            return "signup_form";
        }

        siteUserService.save(userCreateForm.getUsername(), userCreateForm.getPassword1());

        return "redirect:/";
    }

    @GetMapping("/login")
    public String login() {
        return "login_form";
    }
}

회원가입 부분은 userCreateFrom을 만들어서 처리하였다.

profile
백엔드 개발자가 꿈인 컴공과

0개의 댓글

관련 채용 정보