# 구현 과정
1) Database를 사용하여 Role에 따라 Access Control 하도록 한다.
- User JPA Entity, Role JPA Entity 가 필요하다. (N:M 매핑 관계로 함)
- User와 Role entity와 관계된 Spring Data JPA repository를 만든다.
2) UserDetailsService와 UserDetails를 만든다.
- UserDetailsService 인터페이스에서 정의된, loadUserByUsername()를 구현한다.
- UserDetails 인터페이스에 User Entity, Role Entity를 넣어주는 것이 UserDetailsService의 loadUserByUsername 메서드이다.
3) Spring Security 설정을 커스터마이징 한다.
- UserDetailsService의 구현체의 이름을 넘겨줘서 Authentication을 구현한다.
- 특정 url에 어떤 권한을 갖는지에 대해서 설정한다.
1. DB table 살펴보기
- JPA 사용, email을 username으로 사용함.
- users, roles는 Many to Many관계로 JoinTable을 사용한다.
- id가 hibernate_sequence를 통해 자동으로 생성된다.
2. pom.xml 의존성 추가
- jpa, mysql사용
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3. application.properties에서 DataSource와 JPA설정
> mysql -u root -p
> ~ 비밀번호 입력하여 mysql 접속
mysql> create database member default character set utf8 collate utf8_general_ci; // DB 생성 명령어
# DataSource Setting
spring.datasource.url=jdbc:mysql://localhost:3306/member?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Seoul
spring.datasource.username=root
spring.datasource.password=rootpw
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.initialization-mode=always
spring.datasource.sql-script-encoding= UTF-8
# JPA Setting
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=false
- spring.jpa.hibernate.ddl-auto=create 이므로 entity에 맞는 table을 자동으로 생성해줌.
4. Entity 생성 (User.java, Role.java)
> User.java
@Entity
@Table(name="users")
@Getter
@Setter
@NoArgsConstructor
public class User
{
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
@Column(nullable=false) // Null이 되면 안됨.
private String password;
@Column(nullable=false, unique=true) //unique=true 해야함.
private String email;
@ManyToMany(cascade=CascadeType.MERGE)
@JoinTable(
name="user_role",
joinColumns={@JoinColumn(name="USER_ID", referencedColumnName="ID")},
inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
private List<Role> roles;
}
> Role.java
@Entity
@Table(name="roles")
@Getter
@Setter
public class Role
{
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
@Column(nullable=false, unique=true)
private String rolename;
@ManyToMany(mappedBy="roles")
private List<User> users;
public Role(String rolename) {
this.rolename = rolename;
}
}
5. CRUD를 구현하기 위해 JpaRepository를 상속받은 UserRepository, RoleRepository 구현
> UserRepository.java
public interface UserRepository extends JpaRepository<User, Integer>{
Optional<User> findByEmail(String email);
}
> RoleRepository.java
public interface RoleRepository extends JpaRepository<Role, Integer> {
Optional<Role> findByRolename(String rolename);
}
# 참조 : Spring Security의 동작 방식
- 사용자가 request(username, password)를 보낼 때
- Authenticationfilter 가 받아서, username과 Password와 관련된 Token을 생성한다.
- 토큰 값을 AuthenticationManager가 받아,
- AuthenticationManager의 구현체인 AuthenticationProvider에게 넘긴다.
(AuthenticationProvider은 여러 개 있을 수 있음)
- AuthenticationProvider는 사용자가 보낸 password를 바탕으로 해서 PasswordEncoder를 통해서, Hashed password를 얻어낸다.
- 또한 AuthenticationProvider가 UserDetailsService를 사용하여 DB의 User, Role에 접근한다.
- UserDetailsService에서 loadUserByUsername()을 통해 UserDetails를 리턴 받는다.
- UserDetails의 password와 사용자가 넘겨준 password(Hashed password)를 바탕으로 하여 확인한다.
- 인증이 성공적으로 이루어지면, AuthenticationFilter안에 SecurityContext에 Authentication 정보를 저장하게 된다.
6. UserDetailsService Interface의 loadUserByUsername()를 구현하여 UserDetails를 return 받기
@Service
@Transactional
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
//메서드가 자동으로 불림.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("Email: " + username + " not found"));
return new org.springframework.security.core.userdetails.User(user.getEmail(),
user.getPassword(), getAuthorities(user));
}
private static Collection<? extends GrantedAuthority> getAuthorities(User user){
String[] userRoles = user.getRoles()
.stream()
.map((role) -> role.getRolename())
.toArray(String[]::new);
Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(userRoles);
return authorities;
}
}
7. WebSecurityConfigurerAdapter을 상속받는 Spring Seucirty Config 파일 생성
@Configuration
@EnableWebSecurity //얘가 @Configuration이 들어있음
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//AuthenticationProvider가 PasswordEncoder와 UserDetailsService를 사용한다.
@Autowired
private UserDetailsService customUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // BCryptPasswordEncoder 생성
}
//인증과 관련된 메서드,
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//어떤 UserDetailsService를 사용하고, 어떤 PasswordEncoder를 사용하는지
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
}
}
이전 포스팅 : in-memory에서 Spring Security 기본 설정
'Backend > Spring & SpringBoot' 카테고리의 다른 글
Hypermedia-Driven RESTful Web Services : Spring HATEOAS 구현방법 [2] (0) | 2022.04.18 |
---|---|
Hypermedia-Driven RESTful Web Services : HATEOAS란? [1] (0) | 2022.04.18 |
[Spring Security] Spring Boot에서 Spring Security 설정하기 (in-memory) (0) | 2022.04.12 |
[WEB] URI 규칙 10가지 (0) | 2022.03.09 |
[JPA] JPA / Hibernate Cascade Types 요약 (0) | 2022.03.09 |