UserDetails를 구현한 User 객체와 UserDetailsService와 UserDetails를 사용하여 로그인 구동
UserDetails로 구현한 User객체
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name="user")
public class UserEntity implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="user_id", foreignKey =@ForeignKey(name="user_id"))
private Set<UserAuthority> authorities;
/**
* UserDetails의 인터페이스 메소드를 Override해야하지만, 변수 authorities로 해결
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
*/
private String email;
@Override
public String getUsername() {
return email;
}
private String password;
/**
* @Override
public String getPassword() {
return null;
}
*/
private boolean enabled;
/**
* @Override
public boolean isEnabled() {
return false;
}*/
@Override
public boolean isAccountNonExpired() {
return enabled;
}
@Override
public boolean isAccountNonLocked() {
return enabled;
}
@Override
public boolean isCredentialsNonExpired() {
return enabled;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
@Table(name="user_authority")
@IdClass(UserAuthority.class)
public class UserAuthority implements GrantedAuthority {
@Id
@Column(name="user_id")
private Long userId;
@Id
private String authority;
}
public interface UserRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findUserByEmail(String email);
}
UserDetailsService로 구현한 UserService
@Service
@Transactional
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findUserByEmail(username).orElseThrow(() -> new UsernameNotFoundException(username));
}
public Optional<UserEntity> findUser(String email){
return userRepository.findUserByEmail(email);
}
public UserEntity save(UserEntity userEntity){
return userRepository.save(userEntity);
}
public void addAuthority(Long userId, String authority){
userRepository.findById(userId).ifPresent(user->{
UserAuthority newRole = new UserAuthority(user.getUserId(), authority);
if(user.getAuthorities()==null){
HashSet<UserAuthority> authorities = new HashSet<>();
authorities.add(newRole);
user.setAuthorities(authorities);
save(user);
}else if(!user.getAuthorities().contains(newRole)){
HashSet<UserAuthority> authorities = new HashSet<>();
authorities.addAll(user.getAuthorities());
authorities.add(newRole);
user.setAuthorities(authorities);
save(user);
}
});
}
public void removeAutority(Long userId, String authority){
userRepository.findById(userId).ifPresent(user->{
if(user.getAuthorities()==null){ return ; }
UserAuthority targetRole = new UserAuthority(user.getUserId(), authority);
if(user.getAuthorities().contains(targetRole)){
user.setAuthorities(
user.getAuthorities().stream().filter(auth -> !auth.equals(targetRole))
.collect(Collectors.toSet())
);
save(user);
}
});
}
}
Config
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true) // @PreAuthorize 적용
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//CsrfFilter csrfFilter;
//UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter;
private final CustomAuthDetails customAuthDetails;
private final UserManager userManager;
private final AdminManager adminManager;
private final UserService userService;
public SecurityConfig(CustomAuthDetails customAuthDetails, UserManager userManager, AdminManager adminManager, UserService userService) {
this.customAuthDetails = customAuthDetails;
this.userManager = userManager;
this.adminManager = adminManager;
this.userService = userService;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/**
* DB에서 가져오기
* insert into USER values(1, 'ho@google.com', TRUE, '1111');
* insert into USER_AUTHORITY values(1,'ROLE_USER');
* insert into USER_AUTHORITY values(1,'ROLE_ADMIN');
* select * from USER ;
* select * from USER_AUTHORITY;
*/
auth.userDetailsService(userService);
/**
* inMemeory로 user와 password 생성
auth.inMemoryAuthentication()
.withUser(User.builder()
.username("user")
.password(passwordEncoder().encode("1111"))
.roles("USER")
).withUser(User.builder()
.username("v")
.password(passwordEncoder().encode("1"))
.roles("ADMIN")
);
*/
/**
* AuthenticationManagerBuilder 의 authenticationProvider()
* 는 List로 쌓아놓고 사용
*/
auth.authenticationProvider(userManager);
auth.authenticationProvider(adminManager);
}
@Bean
PasswordEncoder passwordEncoder(){
//return new BCryptPasswordEncoder();
return NoOpPasswordEncoder.getInstance();
}
/**
* 권한 계층
* A > B : A는 B의 권한도 적용할 수 있다.
* @return
*/
@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchy;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/**
* formLogin Test
*/
http
.authorizeRequests((requests) -> {
requests
.antMatchers("/").permitAll()
.anyRequest().authenticated()
;
//.antMatchers("/**").permitAll() 모든 페이지 허용
})
//.formLogin() // login 페이지 지정이 없을 시 DefaultLoginPageGenerationFilter 동작하여 Default 페이지 출력
.formLogin(
login-> login.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/", false) // alwaysUse 는 항상 false true일 경우 다시 root 페이지로 이동하게 됨.
.failureUrl("/login-error") // 로그인 실패시
//.authenticationDetailsSource(customAuthDetails) // detailSource 값 커스텀 지정.
)
.logout(logout->logout.logoutSuccessUrl("/"))
.exceptionHandling(exception -> exception.accessDeniedPage("/access-denied"))
;
}
/**
* Web 리소스가 필터에 안걸리도록 해당 경로에 대한 무시 설정
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
//super.configure(web);
web.ignoring()
.requestMatchers(
PathRequest.toStaticResources().atCommonLocations(), // static리소스 경로 지정
PathRequest.toH2Console() // web에서 h2Console 접근 가능하도록 Path 열기
)
;
}
}
Controller
@Controller
public class MainController {
@ResponseBody
@GetMapping("/auth")
public Authentication auth(){
/**
* SecurityContextHolder 에서 해당 Authentication 정보 가져오기
*/
return SecurityContextHolder.getContext().getAuthentication();
}
@GetMapping("/")
public String main(){ return "index";}
@GetMapping("/login")
public String login(){ return "loginForm";}
@GetMapping("/login-error")
public String loginError(Model model){
model.addAttribute("loginError",true);
return "loginForm";
}
@GetMapping("/access-denied")
public String accessDenied(){
return "accessDenied";
}
@PreAuthorize("hasAnyAuthority('ROLE_USER')")
@GetMapping("/user-page")
public String userPage(){
return "userPage";
}
@Autowired
UserManager userManager;
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
@GetMapping("/admin-page")
/**
* inMemory test method
public String adminPage(@AuthenticationPrincipal Admin admin, Model model){
model.addAttribute("userList",userManager.myUserList(admin.getId()));
return "adminPage";
}
*/
public String adminPage(@AuthenticationPrincipal UserEntity userEntity, Model model){
model.addAttribute("userList",userManager.myUserList(userEntity.getUserId()));
return "adminPage";
}
}
'Spring > 1-5. Spring Security' 카테고리의 다른 글
Spring Security란? (0) | 2023.11.24 |
---|---|
OncePerRequestFilter란? (0) | 2023.11.24 |
5) BasicAuthenticationFilter를 사용하여 토큰 인증 (0) | 2023.06.08 |
4) Form Login과 Logout Filter (0) | 2023.06.04 |
3. Spring Security Authentication 구조와 매커니즘 (0) | 2023.06.04 |