Hello

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

볼빵빵오춘기

활동하기