coocoo0818 / LookSt

LookSt-Project
0 stars 7 forks source link

[Spring Security] 구현 하기 #191

Open cheeseLupin opened 1 year ago

cheeseLupin commented 1 year ago

시큐리티 구현은 java기반xml기반이 있다.

1. 시큐리티 의존성 추가 - pom.xml

properties 추가

        <spring.maven.artifact.version>4.3.25.RELEASE</spring.maven.artifact.version>
        <egovframework.rte.version>3.10.0</egovframework.rte.version>
        <security.version>5.6.9</security.version>

dependencies 추가

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>${security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>${security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>${security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${security.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>${security.version}</version>
    </dependency>
cheeseLupin commented 1 year ago

2. 필터를 이용하여 스프링 MVC에서 시큐리티 사용하기 - web.xml 설정

시큐리티를 사용하는 가장 큰 이유인 보안 절차를 위해, 웹에서 보내는 모든 요청이 spring security 필터를 거쳐가도록 해야 한다. (이것은 web.xml에 필터를 이용하여 구현이 가능하다.)

  1. security 기능을 담당하는 security-context.xml<context-param>에 추가 하기
  2. spring security 필터 생성 하기

web.xml -> <context-param> 변경 전

    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>

web.xml -> <context-param> 변경 후

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/root-context.xml
            /WEB-INF/spring/security-context.xml
        </param-value>
    </context-param>

스프링 시큐리티 필터 생성

cheeseLupin commented 1 year ago

3. XML 파일 생성 - security-context.xml

security-context에서 시큐리티 기능에 관한 설정을 할 수 있다.

security-context.xml 전체 코드

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<bean id="customUserDetailsService" class="kr.co.lookst.member.CustomUserDetailsService" />
<bean id="loginSuccess" class="kr.co.lookst.member.LoginSuccessHandler" />

<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
    <constructor-arg value="" />
</bean>

    <security:http auto-config="true" use-expressions="true">
        <!-- <security:intercept-url pattern="/member/*" access="hasRole('ROLE_MEMBER')"/> -->
        <security:intercept-url pattern="/member/login" access="permitAll"/>
        <security:intercept-url pattern="/member/loginCheck" access="permitAll"/>
        <security:intercept-url pattern="/member/register" access="permitAll"/>
        <security:intercept-url pattern="/member/idCheck" access="permitAll"/>
        <security:intercept-url pattern="/member/**" access="hasAnyRole('member', 'seller', 'admin')"/>
        <security:intercept-url pattern="/partner/**" access="hasAnyRole('member', 'seller', 'admin')"/>
        <security:intercept-url pattern="/**/write" access="hasAnyRole('member', 'seller', 'admin')"/>
        <security:intercept-url pattern="/seller/**" access="hasRole('seller')"/>
        <security:intercept-url pattern="/admin/**" access="hasRole('admin')"/>

        <security:form-login login-page="/member/login"
                             login-processing-url="/member/login"
                             username-parameter="member_id"
                             password-parameter="member_pw"
                             authentication-success-handler-ref="loginSuccess"
                             />

        <security:csrf disabled="false" />
        <security:access-denied-handler error-page="/member/accessError" />
        <security:logout logout-url="/member/logout"
                         logout-success-url="/" />
    </security:http>

    <security:authentication-manager>
        <security:authentication-provider user-service-ref="customUserDetailsService">
            <security:password-encoder ref="bcryptPasswordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>

</beans>
cheeseLupin commented 1 year ago

3-1. 각 코드는 어떤 기능을 담당하는가?

1) 시큐리티 기능을 이용하기 위한, 시큐리티 bean 설정 하기

2) 비밀번호 인코딩/디코딩을 위한 BcryptPasswordEncoder bean 설정 하기

3) DB를 이용한 시큐리티 로그인을 하기 위해 customUserDetailsService bean 설정 하기

4) 로그인 후 권한에 따른 페이지 이동을 하기 위해 login Success bean 설정 하기

5) 권한에 ROLE_이 붙지 않아도 권한 기능을 이용하도록 grantedAuthorityDefaults bean 설정 하기

cheeseLupin commented 1 year ago

3-2. HTTP 태그에서 시큐리티 설정하기

    <security:http auto-config="true" use-expressions="true">
        ···
    </security:http>  

다음은 3-1, 6)에서 작성한 http 태그 안에 들어가는 코드에 관한 설명이다.

1) 각 url 접근 권한 설정

2) 로그인 페이지 적용

loginSuccess 에서 사용할 클래스는 kr.co.lookst.member 경로에 LoginSuccessHandler로 클래스를 생성해주었다.

package kr.co.lookst.member;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;

public class LoginSuccessHandler implements org.springframework.security.web.authentication.AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication auth) throws IOException, ServletException {

        System.out.println("Login Success");

        List<String> roleNames = new ArrayList<>();
        auth.getAuthorities().forEach(authority -> {
            roleNames.add(authority.getAuthority());
        });

        System.out.println("ROLE NAMES: " + roleNames);

        if(roleNames.contains("admin")) {
            // admin 권한을 가진 사용자가 이동할 url
            response.sendRedirect("/lookst/admin/member_management");
            return;
        }

        if(roleNames.contains("member")) {
                // member 권한을 가진 사용자가 이동할 url
            response.sendRedirect("/lookst");
            return;
        }

        // 선언하지 않은 권한의 회원이 이동할 url
        response.sendRedirect("/lookst");
    }

}

3) csrf토큰 사용 여부 설정

4) 에러페이지 이동

**5) 로그아웃 기능 구현**
    <security:logout logout-url="/member/logout"
                     logout-success-url="/" />
 - logout-url : 로그아웃 버튼을 누를 때 사용하는 url 주소를 기입해주면 된다. (post 방식만 사용 가능하다.)
 - logout-success-url="/" : 로그아웃 시 홈으로 이동하기 위해 사용하는 링크

LookSt에서 사용하는 로그아웃 버튼에 대한 코드이다.
csrf토큰을 input 형태로 넣어주어야 post 방식이 작동된다.
                <form action="${contextPath}/member/logout" method="post" id="logout">
                    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
                    <a class="dropdown-item" type="submit" onclick="document.getElementById('logout').submit();">LOGOUT</a>
                </form>
cheeseLupin commented 1 year ago

4. DB를 이용한 시큐리티 로그인 구현하기

1) DB 연동하기

    <security:authentication-manager>
        <security:authentication-provider user-service-ref="customUserDetailsService">
            <security:password-encoder ref="bcryptPasswordEncoder"/>
        </security:authentication-provider>
    </security:authentication-manager>

2) CustomUserDetailsService 클래스 구현

package kr.co.lookst.member;

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 kr.co.lookst.member.domain.CustomUser;
import kr.co.lookst.member.domain.MemberDto;
import kr.co.lookst.member.service.MemberService;
import lombok.Setter;

public class CustomUserDetailsService implements UserDetailsService {

    @Setter(onMethod_ = {@Autowired })
    private MemberService service;

    @Override
    public UserDetails loadUserByUsername(String member_id) throws UsernameNotFoundException {
        System.out.println("load user by member_id : " + member_id);

        MemberDto dto = service.read(member_id);

        System.out.println("querued by member mapper : " + dto);

        return dto == null ? null : new CustomUser(dto);
    }
}

3) CustomUser 클래스 구현

package kr.co.lookst.member.domain;

import java.util.Collection;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class CustomUser extends User{

    private static final long serialVersionUID = 1L;

    private MemberDto memberDto;

    public CustomUser(String member_id, String member_pw, boolean enabled, Collection<? extends GrantedAuthority> authorities) {
        super(member_id, member_pw, authorities);
    }

    public CustomUser(MemberDto dto) {
        super(dto.getMember_id(), dto.getMember_pw(), dto.getAuthList().stream().map(auth -> new SimpleGrantedAuthority(auth.getMem_auth_auth())).collect(Collectors.toList()));
        this.memberDto = dto;
    }

}