회원가입, Security 설정 기능

Sol's·2023년 1월 2일

프로젝트

목록 보기
5/16

우선 회원가입 기능부터 시작해 보자.
회원가입시 권한은 USER이고 ID가 sol이라면 ADMIN 권한을 준다.

나중에 ADMIN이 USER의 권한을 변경하는 로직을 추가 할 예정이다.

우선 Controller이다.

  • UserRestController
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Slf4j
@Api(tags = {"User API"})
public class UserRestController {

    private final UserService userService;
    @ApiOperation(value = "회원가입 기능",notes = "userName, password 입력해서 회원가입")
    @PostMapping("/join")
    public Response<UserJoinResponse> join(@RequestBody UserJoinRequest userJoinRequest) {
        log.info("\nusername = {}, password = {}\n",userJoinRequest.getUserName(), userJoinRequest.getPassword());
        //id에 sol라는 단어가 들어가면 ADMIN 권한이 주어진다.
        return Response.success(userService.join(userJoinRequest));
    }
 }

User Entity이다.

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@Table(name = "Users")
public class User extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String userName;
    private String password;

    @OneToMany(mappedBy = "user") //post의 user필드와 연결
    @Builder.Default
    private List<Post> posts = new ArrayList<>();

    @OneToMany(mappedBy = "user")
    @Builder.Default
    private List<Comment> comments = new ArrayList<>();

    @OneToMany(mappedBy = "user")
    @Builder.Default
    private List<Like> likes = new ArrayList<>();

    @OneToMany(mappedBy = "user")
    @Builder.Default
    private List<Alarm> alarms = new ArrayList<>();

    @Enumerated(EnumType.STRING)
    private UserRole role;

    public UserJoinResponse fromEntity() {
        return new UserJoinResponse(this.id, this.userName);
    }
}

Dto를 통해 값을 입력받고 있다.
Request dto와 Response dto이다.

  • UserJoinRequest
@Getter
@AllArgsConstructor
@Slf4j
public class UserJoinRequest {
    private String userName;
    private String password;

    public static User toEntity(UserJoinRequest dto, BCryptPasswordEncoder encoder) {
        UserRole userRole = UserRole.ROLE_USER;
        //초기 ADMIN회원 1명 (id가 sol이라면)
        if(dto.userName.equals("sol")) userRole = UserRole.ROLE_ADMIN;

        User user = User.builder()
                .userName(dto.userName)
                .password(encoder.encode(dto.password))
                .role(userRole)
                .build();
        return user;
    }
}
  • UserJoinResponse
@Getter
@AllArgsConstructor
public class UserJoinResponse {
    private Long userId;
    private String userName;
}

Service에서 비지니스 로직을 처리한다.

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final BCryptPasswordEncoder encoder;

    public UserJoinResponse join(UserJoinRequest dto) {
        // 중복처리
        userRepository.findByUserName(dto.getUserName())
                .ifPresent((user -> {
                    throw new AppException(ErrorCode.DUPLICATED_USER_NAME, dto.getUserName() + "는 이미 존재합니다.");
                }));

        User savedUser = userRepository.save(UserJoinRequest.toEntity(dto, encoder));

        //Entity -> dto
        UserJoinResponse userJoinResponse = savedUser.fromEntity();
        return userJoinResponse;
    }
}

DB에 접근하기위해 JpaRepository를 사용했다.

  • UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUserName(String username);
}

권한을 부여할때 Enum으로 사용할 수 있게 처리했다.

  • UserRole
public enum UserRole {
    ROLE_USER, ROLE_ADMIN
}

Spring Security 적용

우선 Dependency를 추가해주자

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-security'
    testImplementation 'org.springframework.security:spring-security-test'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
}

Security Filter Chain 적용

@EnableWebSecurity를 사용해야 Spring Security를 활성화 시킬 수 있다.
Security가 활성화 되어야 위의 BCryptPasswordEncoder를 사용할 수 있다.
Spring Security적용을 하려면 Security Filter의 구조를 알아야 한다를 참고하면 Spring Security를 이해하는데 큰 도움이 될것이다.

체인 설정 방법

.antMatchers()를 통해 접근제한을 걸수있다.

authenticated() : 인증이 필요함
permitAll() : 인증,인가 필요없음
access() : 인가 필요함

아래에 보면 addFilterBefore()을 사용했다.

.addFilterBefore(new JwtFilter(userService, secretKey), UsernamePasswordAuthenticationFilter.class)

UserNamePasswordAuthenticationFilter적용하기 전에 JWTFilter를 적용 하라는 뜻이다.
이렇게 되면 설정한 UsernamePasswordAuthenticationTokenSecurityContextHolder에서 꺼내 사용할 수 있다.

@EnableWebSecurity
@Configuration
public class SecurityConfig {

    private final UserService userService;

    @Value("${jwt.secret}")
    private String secretKey;

    public SecurityConfig(UserService userService) {
        this.userService = userService;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .httpBasic().disable()
                .csrf().disable()
                .cors().and()
                .authorizeRequests()
                .antMatchers("/api/v1/users/join", "/api/v1/users/login").permitAll() // join, login은 언제나 가능
                //Post는 전부 막아논다.
//                .antMatchers(HttpMethod.POST, "/api/**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
                .antMatchers(HttpMethod.POST, "/api/v1/posts/**").access("hasRole('ROLE_USER')")
                .antMatchers(HttpMethod.POST, "/api/v1/users/{id}/role/change").access("hasRole('ROLE_ADMIN')")
//                .antMatchers(HttpMethod.POST, "/api/**").authenticated()
                .antMatchers(HttpMethod.PUT, "/api/**").authenticated()
                .antMatchers(HttpMethod.DELETE, "/api/**").authenticated()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // STATELESS = jwt사용하는 경우 씀 : 매번 토큰을 사용하는 개념?
                .and()
                .addFilterBefore(new JwtFilter(userService, secretKey), UsernamePasswordAuthenticationFilter.class) //UserNamePasswordAuthenticationFilter적용하기 전에 JWTTokenFilter를 적용 하라는 뜻 입니다.
                .build();
    }
}

Service에서 BCryptPasswordEncoder를 사용해 비밀번호를 암호화 해야한다.
BCryptPasswordEncoder를 만들어보자.

@Configuration
public class EncoderConfig {
    //암호화를 위해 Bean 등록
    @Bean
    public BCryptPasswordEncoder encoder(){
        return new BCryptPasswordEncoder();
    }
}

이제 Password가 암호화되어서 잘 저장되는것을 확인 할 수 있다.

profile
배우고, 생각하고, 행동해라

0개의 댓글