레거시(?) 블로그 글 입니다.
JWT 토큰과 Sping Boot 3.0 + Spring Security6 를 이용한 Rest API 방식의 인증-인가 방식을 이용한 더 자세한 내용을 원하시는 분은 아래 글을 참고해주세요 !
🤗 https://thalals.tistory.com/436
스프링 시큐리티 프레임워크
- 스피링 시큐리티(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가 적용되지 않는다.
'개발자 도구' 에러 확인 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<User> 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<User, Long> {
Optional<User> 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<? extends GrantedAuthority> 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";
}
}
지금 해 본 방법은 제일 간단한 스프링 시큐리티를 사용하는 기초적인 방법이다
'Spring > Spring Boot' 카테고리의 다른 글
[Spring] 스프링 부트 페이지네이션 (Query, JPA, offset / cursor 페이지네이션) (2) | 2021.12.23 |
---|---|
[Spring] 스프링 시큐리티 - 카카오 소셜로그인 하기(OAuth) (4) | 2021.12.04 |
[Spring] 웹의 인증,인가 / 쿠키와 세션 (0) | 2021.12.03 |
Spring Putmapping Json 과 File 데이터 함께 전송하기(Form data) (6) | 2021.12.03 |
[Spring] JPA FindAll<Entity> to convert DTO (ModelMapper) (0) | 2021.12.03 |