54. 스프링 시큐리티 로그인
by 볼빵빵오춘기loginForm.jsp
- form 태그속성에 action, method 넣어준다.
- submit버튼 form태그안에 넣어준다.
→ ‘/auth/loginProc’ action으로 만들어서 Controller에서 작동하도록 했지만
우리는 Controller에 @PostMapping("/auth/joinProc") 만들었지 ‘/auth/loginProc’ 가 url에 왔을 때 작동하는 Controller는 만들지 않았다.
@PostMapping("/auth/joinProc")
public ReplySaveRequestDto.ResponseDto<Integer> save(@RequestBody User user){
System.out.println("UserApiController save() 호출");
userService.회원가입(user);
return new ReplySaveRequestDto.ResponseDto<Integer>(HttpStatus.OK.value(),1);
}
→ 그러면 url이 ‘/auth/loginProc’ 들어왔을 때 작동하는 Controleller 만들어야겠네 생각이 들겠지만 loginProc는 SpringSecurity가 로그인할 때 가로채게 할 것이다.
로그인 실패시 넘어가는 페이지도 만들 수 있다.
<%@ page language="java" contentType="text/html;charset=UTF-8 " pageEncoding="UTF-8"%>
<%@ include file="../layout/header.jsp"%>
<div class="container">
<h2>Stacked form</h2>
<form action="/auth/loginProc" method="post">
<div class="form-group">
<label for="username">username:</label>
<input type="text" class="form-control" id="username" placeholder="Enter username" name="username">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" class="form-control" id="password" placeholder="Enter password" name="password">
</div>
<button id="btn-login" class="btn btn-primary">로그인</button>
<a href="https://kauth.kakao.com/oauth/authorize?client_id=a32fc77e17469c5118302c27b8e29ee2&redirect_uri=http://localhost:8080/auth/kakao/callback&response_type=code">
<img style="height="38px"" src="/image/kakao_login_button.png">
</a>
</form>
</div>
<%@ include file="../layout/footer.jsp"%>
SecurityConfig
스프링시큐리티가 가로채서 로그인을 하는데 로그인할 때 사용자가 username,password를 가로채서 로그인을 해야하는데 이 때 필요한 클래스가 하나 필요하다.
(⇒ UserDetails 를 가지고 있는 User 오브젝트를 하나 만들어야한다.
⇒ UserDetails 타입의 User 객체를 하나 만들어야한다.)
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/","/auth/**","/js/**","/image/**","/css/**","/dummy/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/auth/loginForm")
.loginProcessingUrl("/auth/loginProc") // 스프링 시큐리티가 해당 주소로 요청오는 로그인을 가로채서 대신 로그인 해준다.
.defaultSuccessUrl("/");
return http.build();
}
PrincipalDetail
auth 패키지 생성 후 그 패키지 하위 클래스로 PrincipalDetail 클래스를 생성한다.
public class PrincipalDetail implements UserDetails {
private User user; // 컴포지션
}
PrincipalDetail 클래스를 생성한 후 User 타입의 객체를 선언한다.
UserDetails 가 갖고있는 메소드를 다 오버라이딩 해야한다.
누른 후 alt+shift+s눌러서 Override/Implement Methods 에서 전체 메소드 오버라이딩한다.
처음에 override를 만들면 return null; 인데 이걸 다 수정하여 밑에 코드처럼 만들어주면된다.
// 스프링 시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료가 되면 UserDetails 타입의 오브젝트를
// 스프링 시큐리티의 고유한 세션저장소에 저장을 해준다.
@Getter
public class PrincipalDetail implements UserDetails {
private User user; // 컴포지션
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정이 만료되지 않았는지 리턴한다. (true: 만료안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않았는지 리턴한다. (true: 잠기지 않음)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 비밀번호가 만료되지 않았는지 리턴한다. (true: 만료안됨)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 활성화(사용가능)인지 리턴한다. (true: 활성화)
@Override
public boolean isEnabled() {
return true;
}
// 계정이 갖고있는 권한 목록을 리턴한다.(권한이 여러 개 있을 수 있어서 루프를 돌아야 하는데 우리는 한 개만)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
collectors.add(()->{return "ROLE_"+user.getRole();}); // "ROLE_"를 붙여주는건 스프링에서 ROLE을 받을때의 규칙이다.
// collectors.add(new GrantedAuthority() {
// @Override
// public String getAuthority() {
// return "ROLE_"+user.getRole();
// }
// });
return collectors;
}
}
SecurityConfig
- 시큐리티가 대신 로그인해주는데 pw를 가로채기를 하는데 해당 pw가 뭘로 해쉬가 되어 회원가입이 되었는지 알아야 같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교할 수 있다.
- null 에 들어가야할 객체에 알려줘야하는데 아직 안만들었으므로 만들어 줘야한다.
@Bean
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(null).passwordEncoder(encodePWD());
}
PrincipalDetailService
- indByUsername() 가 없으므로 UserRepositoy에 만들러간다.(밑에 만들고 다시와서 코드완성시키기)
- userRepository.findByUsername(username) 반환타입이 Optional이기때문에 .orElseThrow()를 붙여 완성시킨다.
- return타입이 PrincipalDetail 이기 때문에 새로운 PrincipalDetail()을 만든다.
이렇게 되면 PrincipalDetail클래스에서 생성자가 없기 때문에 user정보는 null이 된다.
@Service
public class PrincipalDetailService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
// 스프링이 로그인 요청을 가로 챌 때, username, password 변수 2개를 가로채는데
// password 부분 처리는 알아서 함.
// username이 DB에 있는지만 확인해주면 됨.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User principal = userRepository.findByUsername(username).orElseThrow(()->{
return new UsernameNotFoundException("해당사용자를 찾을 수 없습니다. :"+username);
});
return new PrincipalDetail(principal); // 시큐리티 세션의 유저 정보가 저장이 됨.
}
}
UserRepository
naming Query를 이용하여 DB에 보낼 쿼리 작성한다.
Optional<User> findByUsername(String username);
PrincipalDetail
PrincipalDetail() 생성자 추가한다.
public PrincipalDetail(User user){
this.user = user;
}
SecurityConfig
- PrincipalDetailService DI
- configure() 완성시킨다.
@Autowired
private PrincipalDetailService principalDetailService;
@Bean
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(principalDetailService).passwordEncoder(encodePWD());
}
Header.jsp
로그인이 되면 여기에 정보가 등록된다.
그런데 여기서 등록된거는 jstl로 확인이 된거고 java가 인식할 수 있도록 해야한다.
<sec:authorize access="isAuthenticated()">
<sec:authentication property="principal" var="principal" />
</sec:authorize>
BoardController
- @AuthenticationPrincipal PrincipalDetail principal 를 파라미터로 가져감으로써 java에서도 로그인된것을 확인가능하다.
- 파라미터로 @AuthenticationPrincipal PrincipalDetail principal가져온건 그냥 로그인 된 정보 확인용이지 별 의미 없어서 지운다.
@GetMapping({"","/"})
public String index(@AuthenticationPrincipal PrincipalDetail principal){
System.out.println("로그인 사용자 아이디 : "+ principal.getUsername());
return "index";
}
참고
로그아웃은 /logout 이 defatul다.
따라서 contoller에서 처리 안해도 된다.
'강의 따라하기 > blog' 카테고리의 다른 글
56. 글목록 보기 (0) | 2024.01.04 |
---|---|
55. 글쓰기 완료 (0) | 2024.01.04 |
53. XSS와 CSRF (1) | 2024.01.04 |
52. 비밀번호 해쉬 후 회원가입하기 (0) | 2024.01.03 |
51. 스프링시큐리티 로그인 페이지 커스터마이징 (0) | 2024.01.03 |
블로그의 정보
Hello 춘기's world
볼빵빵오춘기