게시판 만들어보기 2일차

박세건·2023년 6월 21일
0

security를 이용한 로그인처리

SecurityConfig 설정

package com.example.board_project.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

//빈 등록 : 스프링 컨테이너에서 객체를 관리할 수 있게 하는 것

@Configuration  //빈에 등록(IoC관리)
@EnableWebSecurity  //시큐리티 필터가 등록이 된다(필터 설정을 여기서 해주겠다).
@EnableGlobalMethodSecurity(prePostEnabled = true)  //특정 주소로 접근을 하면 권한 및 인증을 미리 체크하겠다
public class SecurityConfig {
    @Bean   //IoC 가능 비밀번호를 암호화 하기위함
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder(); //이 값을 스프링이 관리
    }
    //시큐리티가 대신 로그인을 해줄때에 password를 가로채기를 하는데 해당 password가 뭘로 해쉬가 되어 회원가입이 되었었는지를 알아야
    //같은 해쉬로 암호화해서 DB에 있는 해쉬랑 비교가 가능하다.

    //새로 도입된 방식 사용하자
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()   //csrf 토큰 비활성화 (테스트시 걸어두는 게 좋음)
                .authorizeRequests()    //인증요청이들어올때
                .antMatchers("/", "/auth/**","/userJoin")    //auth/밑으로 들어오면 + 폴더 허용도 해줘야한다
                .permitAll()        //누구나 허용
                .anyRequest()   //그밖에는
                .authenticated()   //인증이 되어야된다
                .and()
                .formLogin()
                .loginPage("/auth/loginForm")  //auth/ 밑이 아닌 주소들은 인증이필요하기때문에 /aut/loginForm으로 전달된다
                .loginProcessingUrl("/auth/loginProc") //시프링 시큐리티가 해당 주소요청오는 로그인을 가로채고 대신 로그인해준다
                .defaultSuccessUrl("/");    //정상적일때 이 주소로 보낸다.
        return http.build();
    }
    @Bean
    AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

}

중요!
antMathcers()로 허용해주는 주소를 설정해줄때에 url 주소만 포함인줄 알았는데 파일(html, css, js 등등)인증없이 사용하기위해서는 이부분에 넣어주어야한다

허용된 주소가 아니라면 /auth/loginForm 으로 이동시키기때문에 해당 컨트롤러를 만들어준다.
/auth/loginForm 와 연결된 loginForm.html 에서는 action="/auth/loginProc" 을 갖는 폼태그로 정보를 전달하면 해당 주소로 요청오는 정보를 security 가 loadUserByUsername()함수로 가져온다.

<!DOCTYPE html>
<html lang="en">
<head>
   <title>로그인</title>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
   <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.slim.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>

<div id="header"></div>



<div class="container">
   </br>
   </br>
   <h2>LOGIN</h2>
   </br>
   </br>
   <form action="/auth/loginProc" method="post">
       <div class="form-group">
           <label for="loginId"> 아이디 : </label>
           <input type="text" class="form-control" id="loginId" placeholder="Enter ID" name="loginId">
       </div>
       <div class="form-group">
           <label for="password"> 비밀번호 : </label>
           <input type="password" class="form-control" id="password" placeholder="Enter password" name="password">
       </div>
       </br></br>
       <button type="submit" class="btn btn-primary"  >로그인</button>
   </form>
</div>



<div id="footer"></div>

<script
       src="https://code.jquery.com/jquery-3.7.0.js"
       integrity="sha256-JlqSTELeR4TLqP0OG9dxM7yDPqX1ox/HfgiSLBj8+kM="
       crossorigin="anonymous">
</script>
<script>
     $(function() {
       $("#header").load("/templates/header.html");
       $("#footer").load("/templates/footer.html");
     });
</script>


</body>
</html>

loadUserByUsername() 함수를 사용해서 로그인을 진행해야하기때문에 UserDetails 인터페이스를 상속받는 클래스 설정 -> PrincipalDetail

package com.example.board_project.config.auth;

import com.example.board_project.model.User;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;

//스프링 시큐리티가 로그인 요청을 가로채서 로그인을 진행하고 완료가 되면 UserDetails타입의 오브젝트를
//스프링 시큐리티의 고유한 세션저장소에 저장을 해준다.
@Getter
public class PrincipalDetail implements UserDetails {
    private User user;  //컴포지션관계
    public PrincipalDetail(User user) {
        this.user = user;
    }


    public User getUser() {
        return 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();}); //기본 형식

        return collectors;
    }

}

UserDetailsService 인터페이스를 상속받아서 loadUserByUsername() 함수 사용

package com.example.board_project.config.auth;

import com.example.board_project.model.User;
import com.example.board_project.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@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 user = userRepository.findByUsername(username).orElseThrow(() -> {
            return new UsernameNotFoundException("해당 아이디를 찾을수 없습니다!");
        });
        return new PrincipalDetail(user);
    }
}

로그인할때 입력했던 username 값을 갖는 User가 존재하는지를 확인해주고 그 user를 이전에 만들었던 클래스인 PrincipalDetail로 만들어주고 리턴하면 security가 알아서 비밀번호까지 조회한다.

profile
멋있는 사람 - 일단 하자

0개의 댓글