티스토리 뷰

1. model 생성

- User

@Data
@Entity
@Table(	name = "users", 
		uniqueConstraints = { 
			@UniqueConstraint(columnNames = "username"),
			@UniqueConstraint(columnNames = "email") 
		})
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@NotBlank
	@Size(max = 20)
	private String username;

	@NotBlank
	@Size(max = 50)
	@Email
	private String email;

	@NotBlank
	@Size(max = 120)
	private String password;

	@ManyToMany(fetch = FetchType.LAZY)  
	@JoinTable(	name = "user_roles", 
				joinColumns = @JoinColumn(name = "user_id"),  
				inverseJoinColumns = @JoinColumn(name = "role_id"))
	private Set<Role> roles = new HashSet<>();

- 테이블 이름은 user
- username, email은 동일한 값을 넣지 않기 위해서 @UniqueConstraint 추가
- 데이터에 null , "" , " " 값을 넣지 않기 위해서 @NotBlank 추가
- id는 데이터가 한개씩 생성될 때마다 +1씩 증가
- 다른 테이블을 생성하고 조인하기 위해서 @JoinTable
- @ManyToMany(fetch = FetchType.LAZY)는
부모를 조회하면 자식은 조회되지 않는다. 자식은 프록시 객체로 된다.
실제 사용될 때까지 DB를 조회하지 않고 데이터 로딩을 미뤄 지연로딩하게 된다.
데이터가 필요한 순간이 되어서야 DB에 조회해서 프록시 객체를 초기화한다.

- ERole

public enum ERole {
	ROLE_USER,
	ROLE_MODERATOR,
    	ROLE_ADMIN
}

enum는 서로 연관된 상수들의 집합

- Role

@Data
@Entity
@Table(name = "roles")
public class Role {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;

	@Enumerated(EnumType.STRING)
	@Column(length = 20)
	private ERole name;

- roles 테이블에 각 유저 권한을 주기위한 테이블

 

2. repository 생성

- RoleRepository

@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
	Optional<Role> findByName(ERole name);
}

사용자의 권한을 찾기 위한 findByName

- UserRepository

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
	Optional<User> findByUsername(String username);

	Boolean existsByUsername(String username);

	Boolean existsByEmail(String email);
}

 

3. UserDetails 생성

- UserDetailsImpl

public class UserDetailsImpl implements UserDetails {
	private static final long serialVersionUID = 1L;

	private Long id;

	private String username;

	private String email;

	@JsonIgnore
	private String password;

	private Collection<? extends GrantedAuthority> authorities;

	public UserDetailsImpl(Long id, String username, String email, String password,
			Collection<? extends GrantedAuthority> authorities) {
		this.id = id;
		this.username = username;
		this.email = email;
		this.password = password;
		this.authorities = authorities;
	}

	public static UserDetailsImpl build(User user) {
		List<GrantedAuthority> authorities = user.getRoles().stream()
				.map(role -> new SimpleGrantedAuthority(role.getName().name()))
				.collect(Collectors.toList());

		return new UserDetailsImpl(
				user.getId(), 
				user.getUsername(), 
				user.getEmail(),
				user.getPassword(), 
				authorities);
	}

-  UserDetails 상속
- private static final long serialVersionUID = 1L 는
serialVersionUID는 직렬화와 역직렬화를 가능
클래스에 새로운 맴버(authorities)를 추가해도 오류가 발생하지 않는다. -> null 값으로 초기화

 

- UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
	@Autowired
	UserRepository userRepository;

	@Override
	@Transactional
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		System.out.println("아이디 확인중");
		User user = userRepository.findByUsername(username)
				.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));

		return UserDetailsImpl.build(user);
	}

}

- AuthenticationManager 로그인 시도를 하면 UserDetailsServiceImpl의 loadUserByUsername 실행
- userRepository.findByUsername(username) DB의 username을 확인
- 회원이 없을 경우 "User Not Found with username" 호출
- 리턴을 통해 username의 값을 UserDetailsImpl 보냄
- password 체크는 나중에 controller에서 AuthenticationManager.authenticate(Authentication)을 호출하면 내부적으로 처리를 함

 

3.  생성

- WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
......
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.cors().and().csrf().disable()
			.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
			.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
			.authorizeRequests().antMatchers("/api/auth/**").permitAll()
			.antMatchers("/api/test/**").permitAll()
			.anyRequest().authenticated();

		http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
	}
}

WebSecurityConfig는 사용자 인증에 대한 정보를 포함한다. 
- 사용자가 인증을 하기 위해 조건을 추가
- 로그인 폼을 사용/미사용
- Http기반 인증/비인증
- CORS 정책 설정
- antMachers 경로 설정 
- sessionManagement 세션 설정
- addFilterBefore 필터 설정