The Origin:Peer Group:Study04(Spring Security)

xddongx-hyeon2·2022년 3월 24일
0

spring_boot

목록 보기
11/11

Spring Security란?

Spring Security는 스프링 기반 애플리케이션의 보안을 담당해주는 스프링 하위 프레임워크이다.

1. 접근 주체(Principal)

보호되어 있는 리소스에 접근하고자 하는 대상

2. 인증(Authentication)

유저를 확인하는 작업, 접근한 대상이 어떤 종류의 유저인지 확인하는 과정

3. 인가(Authorize)

해당 리소스에 접근한 대상이 권한이 있는지 확인한는 과정

4. 권한(Authorization)

어떤 리소스에 대한 접근 제한을 의미, 모든 리소스는 각각 권한이 걸려있으며, 인가 과정에서 최소한의 권한을 확인하는 것

Spring Security가 제공하는 기능

Spring Security를 활용하여 WebSecurityConfig 클래서 생성

  • 애플리케이션의 모든 URL에 대해 인증을 요구함
  • 로그인 양식을 생성해줌
  • 아이디 및 암호를 가진 사용자의 양식 기반 인증 기능
  • 로그아웃 기능
  • CSRF 공격 방지
  • Session Fixation 보호해줌
  • 보안 헤더 통합
    • HTTP Strict Transport Security(보안 요청 위함)
    • X-Content-Type-Options 통합
    • 캐시 컨트롤(정적 리소스 캐싱 기능)
    • X-XSS-Protection 통합
    • 클릭재킹 예방을 위한 X-Frame-Options
  • 아래의 서블릿 API 메소드들과 통합
    • HttpServletRequest #getRemoteUser()
    • HttpServletRequest.html #getUserPrincipal()
    • HttpServletRequest.html #isUserInRole(java.lang.String role)
    • HttpServletRequest.html #login(java.lang.String username, java.lang.String password)
    • HttpServletRequest.html #logout()

Config 파일 작성

WebSecurityConfig.class

@Configuration          // Bean 관리하는 어노테이션
@EnableWebSecurity      // Spring Security를 활성화하는 어노테이션
// 해당 클래스가 Spring Security 설정 파일로 역할을 하기 위해 WebSecurityConfigurerAdapter 클래스 상속
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserService userService;              // 유저 정보를 가져오는 클래스

    @Autowired
    public WebSecurityConfig(UserService userService) {
        this.userService = userService;
    }

    /**
     * 인증을 무시할 경로 설정<br>
     * 인증없이 무조건 필요하거나 누구나 접근할 수 있는 경로
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/h2-console/**");
    }

    /**
     * http 관련 인증 설정 가능
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable().headers().frameOptions().disable()        //  h2-console 화면 사용하기 위함
                .and()
                    .authorizeRequests()
                    .antMatchers("/login", "/signup").permitAll()      // 누구나 접근 가능
                    .antMatchers("/").hasAnyRole("USER", "ADMIN")       // 여러개 권한 가능 USER, ADMIN 만 접근 가능
                    .antMatchers("/admin").hasRole("ADMIN")     // ADMIN 만 접근 가능
                    .anyRequest().authenticated()       // 나머지는 권한이 있기만 하면 접근 가능
                .and()
                    .formLogin()        // 로그인에 대한 설정
                        .loginPage("/login")        //로그인 페이지 링크
                        .defaultSuccessUrl("/")         // 로그인 성공시 연결되는 주소
                .and()
                    .logout()       // 로그아웃 관련
                        .logoutSuccessUrl("/login")        // 로그안웃 성공시 연결되는 주소
                        .invalidateHttpSession(true)        // 로그아웃시 지정해 둔 세션 날리기
        ;
    }

    /**
     * 로그인 시 필요한 정보를 가져온다.
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService)        // 유저정보는 userService에서 가져온다.
                .passwordEncoder(new BCryptPasswordEncoder());      // 패스워드 인코더는 passwordEncoder(BCrypt 사용)
    }
}

configure(WebSecurity web)

  • 인증을 무시할 경로 설정
  • static(css, js, img, h2-console) 정적 파일은 인증을 하지 않아도 접근이 가능해야 하므로 설정

configure(AuthenticationManagerBuilder auth) throws Excetion

  • 로그인 시 필요한 정보를 가져옴

    userDetailsService()

    • 유저 정보를 어느 서비스에서 가져올지 결정

    passwordEncoder()

    • 패스워드 인코더 설정, BCryptPasswordEncoder 이용
    • BCryptPasswordEncoder는 Spring Security에서 제공하는 클래스 중 하나
    • 비밀번호 인코딩 메소드, 제출된 비밀번호와 저장되어있는 비밀번호 일치여부 알려주는 메소드 제공

configure(HttpSecurity http) throws Exception

  • http 관련한 인증 설정 가능

    csrf().disable().headers().frameOptions().disable()

    • h2-console 화면을 사용하기 위해 해당 옵션들을 disable() 해줌

    authorizeRequests()

    • URL별 권한 관리를 선정하기 위한 시작점 지정
    • authorizeRequests()가 선언되어야 antMatchers 옵션 사용 가능

    formLogin()

    • 로그인 관련 설정하는 옵션
    • loginPage(): 로그인 페이지 링크
    • defaultSuccessUrl(): 로그인 성공시 연결되는 주소

    loggout()

    • 로그아웃 관련 설정하는 옵션
    • logoutSuccessUrl(): 로그아웃 성공시 연결되는 주소
    • invalidateHttpSession(true): 로그아웃 성공시 저장해 두었던 세션 모두 날리기

    antMatchers()

    • 권한 관리 대상을 지정하는 옵션

    • URL 혹은 HTTP 메소드 별로 관리를 가능하게 해 줌

      permitAll() or denyAll(): 누구나 접근 가능하게 함
      anonymous(): 인증되지 않은 사용자가 접근할 수 있음
      authenticated(): 권한이 있기만 하면 접근 가능
      fullyAuthenticated(): 완전히 인증된 사용자만 접근할 수 있음

      hasRole() or hasAnyRole(): 특정 권한을 가지는 사용자만 접근할 수 있음
      hasAuthority() or hasAnyAuthority(): 특정 권한을 가지는 사용자만 접근할 수 있음
      hasIpAddress(): 특정 아이피 주소를 가지는 사용자만 접근할 수 있음
      access(): SpEL 표현식에 의한 결과에 따라 접근할 수 있음
      not(): 접근 제한 기능을 해제 함
      rememberMe(): 리멤버 기능을 통해 로그인한 사용자만 접근할 수 있음
      anyRequest(): antMatchers()에서 설정하지 않은 나머지 주소들

User Entity

UserEntity는 UserDetails를 상속받아 진행

UserDetails 란?
Spring Security에서 사용자의 정보를 담는 인터페이스이다. Spring Security에서 사용자의 정보를 불러오기 위해서 구현해야 하는 인터페이스로 기본 오버라이드 메서드들은 아래와 같다.

메소드return 타입설명default
getAuthorities()Collection<? extends grantedAuthority>계정의 권한 목록 return-
getPassword()String계정의 비밀번호 return-
getUsername()String계정의 고유값 return
(unique한 값이어야함)
(보통 DB의 PK값)
-
isAccountNonExpired()boolean계정의 만료 여부 returntrue(만료 X)
isaccountNonLocked()boolean계정의 잠김 여부 returntrue(잠김 X)
isCredentialsNonExpired()boolean비밀번호 만료 여부 returntrue(만료 X)
isEnabled()boolean계정의 활성화 여부 returntrue(활성화 됨)

UserEntity.class

@Entity
public class UserEntity implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;            // PK

    @Column
    private String userId;      // 회원 ID

    @Column
    private String password;    // 비밀번호

    @Column
    private String username;    // 회원 이름

    @Column
    private String email;       // 이메일

    @Column
    private String auth;        // role, 형태로 저장

    public UserEntity() {
    }

    public UserEntity(Long id, String userId, String password, String username, String email, String auth) {
        this.id = id;
        this.userId = userId;
        this.password = password;
        this.username = username;
        this.email = email;
        this.auth = auth;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAuth() {
        return auth;
    }

    public void setAuth(String auth) {
        this.auth = auth;
    }

    /**
     * 필수 Override 메소드 구현<br>
     *
     * 사용자의 권한이 ","로 구분되어 있는 auth을 활용, 콜렉션 형태로 반환시킴<br>
     * 단, 자료형은 GrantedAuthority를 구현해야 함
     * */

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<GrantedAuthority> roles = new HashSet<>();

        for (String role : auth.split(",")) {
            roles.add(new SimpleGrantedAuthority(role));
        }

        return roles;
    }

    /**
     * 사용자의 unique한 값 return(보통 pk or id)
     */
	@Override
    public String getUsername() {
        return userId;
    }

    /**
     * 사용자의 password 반환
     */
    @Override
    public String getPassword() {
        return password;
    }

    /**
     * 계정 만료 여부 반환
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;        // 만료되지 않음(false: 만료)
    }

    /**
     * 계정 잠김 여부 반환
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;       // 계정이 잠기지 않음(false: 계정 잠김)
    }

    /**
     * 비밀번호 만료 여부 반환
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;            // 만료되지 않음(false: 비밀번호 만료)
    }

    /**
     * 계정의 활성화 여부 반환
     */
    @Override
    public boolean isEnabled() {
        return true;            // 활성화 됨(false: 비 활성화)
    }
}

UserService

UserService는 UserDetailsService를 상속받아 진행

Spring Security에서 유저의 정보를 가져오는 인터페이스이다. Spring Security에서 유저의 정보를 불러오기 위해서 구현해야하는 인터페이스로 기본 오버라이드 메서드는 아래와 같다.

메소드return 타입설명
loadUserByUsername()UserDetails유저의 정보를 불러와서 UserDetails로 리턴

UserService.class

@Service
public class UserService implements UserDetailsService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * UserDetailsService 상속시 필수로 구현해야 하는 메소드<br>
     * UserDetails가 기본 반환 타입, UserEntity가 이를 상속하고 있으므로 자동으로 다운 캐스팅 됨?
     */
    @Override
    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
        return userRepository.findByUserId(userId).orElseThrow(() -> new UsernameNotFoundException(userId));
    }

    public Long save(UserDto userDto) {
		// 비밀번호 암호화
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        userDto.setPassword(encoder.encode(userDto.getPassword()));

        UserEntity userEntity = new UserEntity();
        userEntity.setUserId(userDto.getUserId());
        userEntity.setPassword(userDto.getPassword());
        userEntity.setUsername(userDto.getUsername());     
        userEntity.setEmail(userDto.getEmail());
        userEntity.setAuth(userDto.getAuth());

        this.userRepository.save(userEntity);

        return userEntity.getId();
    }
}

UserController

@Controller
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/signup")
    public String signup(){
        return "signup";
    }

    @PostMapping("/signup") // signup api
    public String signup(UserDto userDto) {
        userService.save(userDto);
        return "redirect:/login";
    }

    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @GetMapping("/logout") // logout by GET 요청
    public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
        new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder
                .getContext().getAuthentication());
        return "redirect:/login";
    }


    @GetMapping("/")
    public String user(Model model, @AuthenticationPrincipal UserEntity userEntity) {
        // @AuthenticationPrincipal: UserDetailsService에서 return한 객체를 파라미터로 직접 받아 사용할 수 있다.
        model.addAttribute("user", userEntity);
        return "user";
    }

    @GetMapping("/admin")
    public String admin(Model model, @AuthenticationPrincipal UserEntity userEntity) {
        // @AuthenticationPrincipal: UserDetailsService에서 return한 객체를 파라미터로 직접 받아 사용할 수 있다.
        model.addAttribute("user", userEntity);
        return "admin";
    }
}

Template

...

ID : <span sec:authentication="name"></span><br>
소유 권한 : <span sec:authentication="authorities"></span><br>

<br>
<h2>모델 객체와 @AuthenticationPrincipal 사용</h2>
ID : <span th:text="${user.userId}"></span><br>
이름 : <span th:text="${user.username}"></span><br>
Email : <span th:text="${user.email}"></span><br>
소유 권한 : <span th:text="${user.auth}"></span><br>

...

참조

0개의 댓글