스프링/시큐리티
스프링부트 시큐리티 02 - 시큐리티 기본(회원가입/로그인/권한처리)
H-V
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는 잘 안씀)