[Spring] 스프링 시큐리티 사용하기 - spring security (로그인...

[Spring] 스프링 시큐리티 사용하기 - spring security (로그인...

스프링 시큐리티 프레임워크

스피링 시큐리티(Spring Security)

'스프링 시큐리티' 프레임워크는 스프링 서버에서 필요한 인증 및 인가를 위해 스프링에서 제공해주는 프레임워크입니다.

스프링 시큐리티 프레임워크 추가(빌드 추가)

build.gradle

// 스프링 시큐리티 implementation 'org.springframework.boot:spring-boot-starter-security' // Thymeleaf (뷰 템플릿 엔진) implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

스프링 시큐리티 활성화

security 패키지 > WebSecurityConfig.Java

@Configuration @EnableWebSecurity // 스프링 Security 지원을 가능하게 함 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.headers().frameOptions().disable(); http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .defaultSuccessUrl("/") .permitAll() .and() .logout() .permitAll(); } }

@Configuration - 이 Class, .java 파일이 설정파일임을 명시하기위한 어노테이션 입니다.(Bean에 등록합니다.)

@EnableWebSecurity - 스프링 시큐리티를 사용하겠다는 어노테이션입니다. WebSecurityConfigyreAdapter를 상속받아 시큐리티 기능을 사용할 수 있습니다.

@Override - Configure 함수를 @Override 하여 해당 설정을들 바꾼다는 의미 입니다.

.anyRequest().authenticated() - 어떤 요청이 들어오든 로그인을 하도록하겠다는 의미(인증)

defaultSuccessUrl('/') - 로그인 성공시 돌아갈 페이지

PermitAll() - 인증을 예외로 허가하겠다는 의미입니다. ➡ 위의 설정에서는 로그인과 로그아웃은 허가하겠다는 의미입니다.

스피링 시큐리티 Default 로그인 기능

이렇게 설정을 해놓으면, 아무것도 만들지 않아도 실행하자마자 로그인 페이지가 로딩 됩니다.

회원가입을 하지 않았기 때문에, 인텔리제이 터미널에 패스워드로 로그인 할 수 있습니다.

Username: user

Password: spring 로그 확인 (서버 시작 시마다 변경됨)

스프링 시큐리티 로그인 페이지 커스텀

이번엔 로그인 과 회원가입 페이지를 구현해보자

1) 로그인 페이지를 Static > login.html 을 만든다.

2) 회원가입 페이지 요청처리를 위해 타임리프 구문을 추가한다.

main > resources > application.properties

spring.thymeleaf.prefix=classpath:/static/

3) 시큐리티 설정에서 로그인 페이지 url을 변경해준다.

.security > WebSecurityConfig

.formLogin() .loginPage("/user/login") .failureUrl("/user/login/error")

- login 은 "/user/login" url로 렌더링

- 로그인 실패시 "error" 페이지 렌더링

UserController

@Controller public class UserController { // 회원 로그인 페이지 @GetMapping("/user/login") public String login() { return "login"; } @GetMapping("/user/login/error") public String loginError(Model model) { model.addAttribute("loginError", true); return "login"; } // 회원 가입 페이지 @GetMapping("/user/signup") public String signup() { return "signup"; } }

이렇게 실행하면, 스프링 시큐리티에서 CSS, JS가 적용되지 않는다.

css, js 적용 안됨

'개발자 도구' 에러 확인 302code

스프링 시큐리티에서 허가를 해주지 않았기 때문이다.

security > WebSecurityConfig

http.authorizeRequests() // image 폴더를 login 없이 허용 .antMatchers("/images/**").permitAll() // css 폴더를 login 없이 허용 .antMatchers("/css/**").permitAll() // 그 외 모든 요청은 인증과정 필요 .anyRequest().authenticated() .and() .formLogin() .loginPage("/user/login") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/") .permitAll() .and() .logout() .permitAll();

.antMatchers().permitAll()로 인증을 허가해준다.

회원가입 기능 구현

User 테이블을 먼저 설계한다.

- role 컬럼에는 User 아니면 Admin (관리자) 를 값으로 받는 enum 클래스 타입을 만들어서 사용해준다.

@Setter @Getter // get 함수를 일괄적으로 만들어줍니다. @NoArgsConstructor // 기본 생성자를 만들어줍니다. @Entity // DB 테이블 역할을 합니다. public class User extends Timestamped { public User(String username, String password, String email, UserRole role) { this.username = username; this.password = password; this.email = email; this.role = role; } // ID가 자동으로 생성 및 증가합니다. @GeneratedValue(strategy = GenerationType.AUTO) @Id private Long id; // 반드시 값을 가지도록 합니다. @Column(nullable = false) private String username; @Column(nullable = false) private String password; @Column(nullable = false) private String email; @Column(nullable = false) @Enumerated(value = EnumType.STRING) private UserRole role; }

package com.sparta.springcore.model; public enum UserRole { USER, // 사용자 권한 ADMIN // 관리자 권한 }

회원가입 API

Controller

// 회원 가입 요청 처리 @PostMapping("/user/signup") public String registerUser(SignupRequestDto requestDto) { userService.registerUser(requestDto); return "redirect:/"; } }

DTO

@Setter @Getter public class SignupRequestDto { private String username; private String password; private String email; private boolean admin = false; private String adminToken = ""; }

Service

Optional : 자바8에 추가된 타입으로 별도의 널포인트 에러처리를 하지 않아도 되는 타입이다.

@RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; private static final String ADMIN_TOKEN = "AAABnv/xRVklrnYxKZ0aHgTBcXukeZygoC"; public User registerUser(SignupRequestDto requestDto) { String username = requestDto.getUsername(); String password = requestDto.getPassword(); // 회원 ID 중복 확인 Optional found = userRepository.findByUsername(username); if (found.isPresent()) { throw new IllegalArgumentException("중복된 사용자 ID 가 존재합니다."); } String email = requestDto.getEmail(); // 사용자 ROLE 확인 UserRole role = UserRole.USER; if (requestDto.isAdmin()) { if (!requestDto.getAdminToken().equals(ADMIN_TOKEN)) { throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다."); } role = UserRole.ADMIN; } User user = new User(username, password, email, role); userRepository.save(user); return user; } }

Repository

public interface UserRepository extends JpaRepository { Optional findByUsername(String username); }

그리고 다시 접근 제한 해제하기

// 회원 관리 URL 전부를 login 없이 허용 .antMatchers("/user/**").permitAll() .antMatchers("/h2-console/**").permitAll()

로그인, 로그아웃 기능 구현하기

로그인은 스프링 시큐리티를 이용한다.

스프링 시큐리티를 사용하면, 클라이언트가 request요청을 보내기 전에 spring security에서 인증 및 인가 절차를 걸친다.

스프링 시큐리티 로그인 처리 과정

회원 정보를 UserDetailsService에서 디비정보를 조회해 호가인한다.

인증 및 인가가 완료되면 UserDetails에 사용자 정보를 담아서 넘겨준다.

인증/인가 성공 시에만, Controller 에게 회원 정보 전달 (UserDetails)

우리가 구현해 줘야 할 클래스 UserDetailsService 인터페이스 → UserDetailsServiceImpl 클래스 UserDetails 인터페이스 → UserDetailsImpl 클래스

security > UserDetailsServiceImpl.java

UserDetailsServicelmpl

회원 정보의 서비스 로직처리를 담당하는 클래스이다.

@RequiredArgsConstructor @Service public class UserDetailsServiceImpl implements UserDetailsService{ private final UserRepository userRepository; public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("Can't find " + username)); return new UserDetailsImpl(user); } }

security > UserDetailsImpl

UserDetailsImpl

사용자 정보가 담기는 클래스이다.

public class UserDetailsImpl implements UserDetails { private final User user; public UserDetailsImpl(User user) { this.user = user; } public User getUser() { return user; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } @Override public Collection getAuthorities() { return Collections.emptyList(); } }

Controller

스프링 시큐리티를 거쳐서 controller로 갈때 @AuthenticationPrincipal 어노테이션을 이용해 로그인 된 사용자 정보를 받아올 수 있다.

@Controller public class HomeController { @GetMapping("/") public String home(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) { model.addAttribute("username", userDetails.getUsername()); return "index"; } }

지금 해 본 방법은 제일 간단한 스프링 시큐리티를 사용하는 기초적인 방법이다

728x90

반응형

from http://thalals.tistory.com/235 by ccl(A) rewrite - 2021-12-04 03:02:01