개발/Java & Kotlin

[Spring] 스프링시큐리티 예제

devhooney 2022. 8. 29. 11:39
728x90

이전에 작성한 포스팅은 단순히 책에 있는 내용을 정리한 것이었는데,

이번엔 직접 코드를 짜보면서 복습해보려고 한다.

 

1. 라이브러리 설치

implementation 'org.springframework.boot:spring-boot-starter-security'

 

2. IndexController 생성

- controller 패키지 생성 후 IndexController 생성

 

@GetMapping({"", "/"})
public String index() {
    return "index";
}

 

- resources/template/index.html 생성

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>인덱스페이지</title>
</head>
<body>
<h1>인덱스페이지입니다.</h1>
</body>
</html>

3. user, admin 메소드 생성

- IndexController에 두 개의 메소드를 만들어 준다.

@GetMapping("/user")
@ResponseBody
public String user() {
    return "user";
}

@GetMapping("/admin")
@ResponseBody
public String admin() {
    return "admin";
}

 

4. 필요한 화면 생성

- IndexController에 메소드 추가

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

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

 

- resources/template/joinForm.html,

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>회원가입 페이지</title>
</head>
<body>
<h1>회원가입 페이지</h1>
<hr>
<form action="/join" method="POST">
  <input type="text" name="username" placeholder="Username"><br>
  <input type="password" name="password" placeholder="Password"><br>
  <input type="text" name="email" placeholder="Email"><br>
  <button>회원가입</button>
</form>
</body>
</html>

 

 loginForm 생성

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr>
<form action="/login" method="POST">
    <input type="text" name="username" placeholder="Username"><br>
    <input type="password" name="password" placeholder="Password"><br>
    <button>로그인</button>
</form>
<a href="/joinForm">회원가입을 아직 하지 않으셨나요?</a>
</body>
</html>

 

5. 스프링 시큐리티 설정

@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록된다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated() // 인증만 되면 접근 가능
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해준다.
                .defaultSuccessUrl("/")
        ;
        return http.build();
    }
}

 

- 메소드의 설명은 기존 포스팅을 참고하면 좋을 것 같다.

https://devhooney.tistory.com/17?category=1052331 

 

[Spring] 스프링 시큐리티 (1)

7.3 스프링 시큐리티 - 인증, 인가 기능의 공통 기반 제공 - 웹앱에서 인증, 인가를 구현하기 위한 각종 필터 클래스 제공 - Bean 정의 파일이나 프로퍼티 파일, 데이터베이스, LDAP 등, 여러 리소스로

devhooney.tistory.com

https://devhooney.tistory.com/19?category=1052331 

 

[Spring] 스프링 시큐리티 (2)

7.4 스프링 시큐리티의 기본 구조 7.4.1 SecurityContext, Authentication, GrantedAuthority - SecurityContext는 인증, 인가 정보를 관리하는 오브젝트 - SecurityContext는 Authentication 오브젝트를 소유하고..

devhooney.tistory.com

 

6. Model, Repository 생성

- Jpa를 사용할 것이기 때문에 라이브러리를 설치한다.

- 편리하게 Lombok도 설치.

 

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
compileOnly 'org.projectlombok:lombok'

 

- model 패키지 안에 User 클래스 생성

 

@Entity
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String username;
    private String password;
    private String email;
    private String role; // ROLE_ADMIN, ROLE_USER
    @CreationTimestamp
    private Timestamp createDate;
}

 

- repository 패키지 안에 UserRepository 인터페이스 생성

 

// CRUD 함수를 JpaRepository가 들고 있음.
// @Repository라는 어노테이션이 없어도 IoC 된다.
// 이유는 JpaRepository를 상속했기 때문에.
public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUsername(String username);

}

 

- 회원 가입 시 비밀번호 암호화를 위해 설정 (SecurityConfig)

@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록된다.
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // secured 어노테이션 활성화, preAuthorize 어노테이션 활성화
public class SecurityConfig {

    // 해당 메소드의 리턴되는 오브젝트를 IoC로 등록해준다.
    @Bean
    public BCryptPasswordEncoder encodePwd() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated() // 인증만 도면 접근 가능
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/loginForm")
                .loginProcessingUrl("/login") // /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해준다.
                .defaultSuccessUrl("/")
        ;
        return http.build();
    }
}

 

- IndexController에 join 메소드 추가

@PostMapping("/join")
public String join(User user) {
    user.setRole("ROLE_USER");

    String rawPassword = user.getPassword();
    String encPassword = bCryptPasswordEncoder.encode(rawPassword);

    user.setPassword(encPassword);

    userRepository.save(user); 

    return "redirect:/loginForm";
}

회원가입

 

7. 로그인

- auth 패키지에 PrincipalDetails 파일 생성

- UserDetails 인터페이스를 상속받아 메소드 오버라이드

- 기본 생성자 만들어주기

package com.cos.security1.auth;

// 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
// 로그인을 진행이 완료가 되면 session에 만들어준다. (Security ContextHolder)
// 오브젝트 타입 => Authentication 타입 객체
// Authentication 안에 User 정보가 있어야 된다.
// User 오브젝트 타입 => UserDetails 타입 객체

// Security Session => Authentication => UserDetails(PrincipalDetails)


import com.cos.security1.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

public class PrincipalDetails implements UserDetails {

    private User user; // 컴포지션

    public PrincipalDetails(User user) {
        this.user = user;
    }

    // 해당 User의 권한을 리턴하는 곳!
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add((GrantedAuthority) () -> user.getRole());
        return collect;
    }

    @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() {

        // 사이트에서 1년동안 회원이 로그인을 안했을 경우 휴면 계정으로 하기로 함.
        // 현재 시간 - 로그인 시간 => 1년을 초과하면 return false;

        return true;
    }
}

 

- PrincipalDetailsService 생성

- /login 요청 시 실행된다.

 

@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    // 시큐리티 session = Authentication = UserDetails
    // 실행되면 ->
    // 시큐리티 session(내부 Authentication(내부 UserDetails))
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        if (userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
}

 

- 여기까지하면 로그인이 잘 된다.

 

728x90