-
스프링부트 시큐리티 02 - 시큐리티 기본(회원가입/로그인/권한처리)스프링/시큐리티 2021. 9. 7. 22:21
유투버 '데어프로그래밍'님 강의 참조
01 - 로그인 페이지 설정
1) IndexController에 @Responsebody를 다 지운 후 템플릿 세팅
@GetMapping("/loginForm") public String loginForm() { return "loginForm"; }
- loginForm.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <h1>Login Page</h1> <hr> <form> <input type="text" name="username" placeholder="Username" /><br /> <input type="password" name="password" placeholder="Password" /><br /> <button>Login</button> </form> </body> </html>
굿! 02 - 로그인 진행을 위한 모델 세팅 및 DB테스트
- 모델 세팅
@Data @AllArgsConstructor @NoArgsConstructor @Builder @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String username; private String password; private String email; private String role; private String provider; private String providerId; @CreationTimestamp private Timestamp createDate; }
- 로그인 진행을 위한 회원가입 세팅
@GetMapping("/joinForm") public String joinForm() { return "joinForm"; }
- loginForm.html 에 회원가입 링크 추가
<a href="/joinForm">Not a member yet? Sign up!</a>
- JoinForm 추가
<body> <h1>Sign-Up Page</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="email" name="email" placeholder="Email" /><br /> <button>Sign-up</button> </form> </body>
- 3개의 값을 들고 컨트롤러에서 회원가입 처리
@PostMapping("/join") public @ResponseBody String join(User user) { System.out.println(user); return "joined Successfully!"; }
굿! - DB에 넣기위한 Repository 설정
public interface UserRepository extends JpaRepository<User, Integer>{}
→ JpaRepository 가 기본 CRUD 기능을 들고 있고 상속으로 어노테이션 생략 가능
- 패스워드 암호화를 꼭 해야만 시큐리티에서 넘어간다!
public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Bean public BCryptPasswordEncoder encodePwd() { return new BCryptPasswordEncoder(); } ...
→ @Bean을 통해 메서드의 리턴되는 오브젝트를 IoC로 등록하게 됨
- IndexController
@Controller public class IndexController { @Autowired private UserRepository userRepository; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; .. @PostMapping("/join") public @ResponseBody String join(User user) { System.out.println(user); user.setRole("ROLE_USER"); String rawPassword = user.getPassword(); String encPassword = bCryptPasswordEncoder.encode(rawPassword); user.setPassword(encPassword); userRepository.save(user); return "redirect:/loginForm"; } }
→ 테스트!
- 로그인 해보기
- SecurityConfig에 코드 추가해 시큐리티가 로그인을 낚아채서 진행하도록 설정
즉 컨트롤러에 로그인 함수를 안만들어도 됨
.loginProcessingUrl("/login") .defaultSuccessUrl("/");
- loginForm에서 로그인 처리 되도록 POST형식 추가
<form action="/login" method="post">
- 시큐리티에서 로그인을 낚아채서 자기가 하도록 클래스를 따로 설정 → PrincipalDetails
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<GrantedAuthority>(); collect.add(new GrantedAuthority() { @Override public String getAuthority() { return 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() { return true; } }
- 시큐리티가 로그인 주소 요청이오면 낚아채는데 로그인 진행이 정상적으로 진행되면 시큐리티만의 세션이 생김
(Security ContextHolder) - 이때 세션에 들어 갈 수 있는 오브젝트 정보는 'Authentication' 타입이어야 함
- Authentication안에는 User정보가 있어야하는데 User정보를 들고있는 객체의 타입은 반드시 'UserDetails' 타입의 객체이어야 함. 그래서 Implements를 통해 PrincipalDetails(UserDetails)가 되는 것
- 그래서 User 객체를 꺼낼때 PrincipalDetails에 접근해 가능 한 것.
- 나머지 코드는 스프링에서 정해논것이니 따라치기만 하면 되고, 해당 유저 권한 리턴시에는 컬렉션으로 만들어놨고 현재 권한은 String 타입이므로 타입을 맞추기위한 코드라고 보면 됨
- 계정에 대한 유효성은 휴먼계정을 만든다던지 할때 False로 바꿔서 사용
- 정리하면 Security Session에 정보를 저장할때 정보는 반드시 Authentication 객체여야하고 이 객체안에 User정보를 저장할때는 반드시 UserDetails 타입이어야 한다는 것!
즉 현재 Authentication에 넣을 유저 객체를 만든 것
- 이제는 Authentication 객체를 만들어서 세션에 넣을 수 있도록 만들어야 한다 → PrincipalDetailsSerivce
@Service public class PrincipalDetailsSerivce implements UserDetailsService{ @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User userEntity = userRepository.findByUsername(username); if(userEntity != null) { return new PrincipalDetails(userEntity); } return null; } }
- 로그인 요청이 오면 스프링이 데이터로 들어온 username을 들고 UserDetailsService 타입으로 IoC되어있는 loadUserByUsername함수를 실행 함 → 이때 username이 파라미터로 들어감
- 이후 레파지토리를 통해서 username 체크를 하게되고 정상이면 PrincipalDetails(UserDetails 타입)에 user정보를 넣어서 리턴하게 됨
- 즉 지금까지 프로세스는 Authentication 객체를 만들어 사용하기 위한 것! 위의 두가지 프로레스를 통해 시큐리티 세션에 Authentication 객체가 들어가지게 되는 것
→ 테스트
굿! - 이제 /user 라고 주소를 쳐서 들어가도 로그인 성공하면 잘 들어가 진다!
- 시큐리티 권한 처리 해보기 (Manager/Admin)
- 권한을 주기 위해 매니저/어드민 아이디 생성
- 권한을 MySQL을 통해 각각 부여
update user set role = 'ROLE_MANAGER' where id=2; update user set role = 'ROLE_ADMIN' where id=3; commit;
굿! - SecurityConfig에 설정되있는데로 어드민은 모든 곳 가능, 메니저는 어드민 제외 가능하게 된다.
- 특정 페이지에 인증 및 권한을 걸고 싶을 때!
@EnableGlobalMethodSecurity(securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Secured("ROLE_ADMIN") @GetMapping("/info") public @ResponseBody String info() { return "Personal Information"; }
→ 로그인을 요구하게되지만 ROLE이 ADMIN이 아닌사람은 못들어가게 된다! 참 편리하다
- 또 다른 시큐리티 기능
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") @GetMapping("/data") public @ResponseBody String data() { return "Data Information"; }
→ Secured과 같지만 여러개를 지정 할 수 있음. @PreAuthorize는 함수 실행 전에 실행 됨 (@PostAuthorieze는 잘 안씀)
'스프링 > 시큐리티' 카테고리의 다른 글
스프링부트 시큐리티 04 - 최종 JWT 서버 구축 (0) 2021.09.11 스프링부트 시큐리티 04 - JWT 토큰 세팅까지의 개념 (1) 2021.09.09 스프링부트 시큐리티 03 - OAuth (구글/페이스북/네이버 로그인) (0) 2021.09.08 스프링부트 시큐리티 01 - 환경설정 (0) 2021.09.07