Spring/Security

Spring Security (7) : Spring Security 권한 지정하기 ROLE_

evolutioning 2025. 5. 12. 16:09

 

 애플리케이션에서 사용자의 권한을 구분하는 것은 중요한 보안 기능입니다. 특히, ADMINUSER와 같은 권한을 나누는 이유는, 각 사용자가 접근할 수 있는 리소스를 제한하려고 한다.

 관리자는 모든 데이터에 접근할 수 있지만 일반 사용자는 제한된 기능만 사용해야 할 필요가 있습니다. 이때 Spring Security를 사용하여 사용자 인증 시 적절한 권한을 부여하고, 이를 세션에 저장하여 각 페이지에서 해당 권한에 맞는 행동을 제어할 수 있다.

 세션에 권한을 저장하는 이유는 사용자가 로그인 후 매번 서버에 요청할 때마다 권한을 재확인할 필요 없이, 세션에 저장된 정보를 활용하여 빠르고 효율적으로 권한을 판단하고, 적절한 리소스만 접근하도록 하기 위함이다.

 이번 글에서는 DB에서 저장된 권한 코드(auth 컬럼)를 Spring Security의 ROLE_ 형태로 매핑하고, 이를 세션에 저장하여 화면에 전달하는 방법을 설명합니다. 이를 통해 사용자에게 맞는 권한을 기반으로 화면을 구성할 수 있다.

 

이번 포스팅은 https://ekeprl.tistory.com/31

 

Spring Security (2)

(1)편에 이어서 2편을 작성하려한다.2편은 AuthenticationProvider / Success,Failuer Handler를 다뤄보겠다.  AuthenticationProvider  : 실제 인증 처리 @Componentclass CustomAuthenticationProvider : AuthenticationProvider{ @Autowired

ekeprl.tistory.com

Provider 작성한 (2)편을 보면 더 좋을듯 하다.

회원 테이블 변경점

우선 기존 회원테이블에 MEMAUTH(회원등급 구분자) 컬럼을 추가했다.

당장은 새로 회원가입하는 회원이라면 자동으로 U값을 배정해주겠지만,

추후에 관리자의 권한으로 다른 회원의 권한을 변경하는 기능을 추가할 생각이다.

 

1. CustomAuthenticationProvider

class CustomAuthenticationProvider(private val mapper : CommonMapper) : AuthenticationProvider{

    val logger = LoggerFactory.getLogger(this::class.java)
    private val passwordEncoder = BCryptPasswordEncoder()

    override fun authenticate(authentication: Authentication): Authentication

   {
        val userid = authentication.principal.toString()
        val userpw = authentication.credentials.toString()

        val user = mapper.getuserinfo(userid) ?: throw BadCredentialsException("사용자 정보 없음")
			** 추가 코드
            if(passwordEncoder.matches(userpw, user.mempw)) {

                val authCode = user.memauth

                val role = when (authCode) {

                    "A" -> "ROLE_A"
                    "U" -> "ROLE_U"
                    "S" -> "ROLE_S"
                    else -> throw BadCredentialsException("회원 등급 구분할 수 없음.")

                }
                val authorities = listOf(SimpleGrantedAuthority(role))

                return UsernamePasswordAuthenticationToken(userid,userpw,authorities)
			여기까지 **
            }else {
                throw BadCredentialsException("비밀번호 오류 : BadCredentialsException")
            }
        }
   override fun supports(authentication: Class<*>): Boolean {
        return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)

   }
}

 

추가한 코드를 보면 그리 많지않다.

 

2. mapper.xml

<select id="getuserinfo"                        parameterType="String"
                                                resultType="com.steamchk.ekeprl.www.common.model.LoginModel">
    /*CommonMapper.getuserinfo*/
    SELECT
        USERID      AS userid
        ,PW     AS userpw
        ,EMAIL  AS useremail
        ,NM     AS usernm
        ,MEMAUTH AS memauth
        ,MEMCODE AS memcode
        ,MEMPW AS mempw
    FROM MEMBER
    WHERE
        USERID = #{userid}
</select>

로그인을 확인하기위해 해당 쿼리를 실행하기때문에
테이블에 추가한 MEMAUTH값을 가져오도록 추가로 작성했다.

 

3.LoginModel

data class LoginModel (

    val userid : String? = null,
    val userpw : String? = null,
    val useremail : String? = null,
    val usernm : String? = null,
    val memcode : String? = null,
    val memauth : String? = null,
    val mempw : String? = null, //암호화된 비밀번호
    )

쿼리의 결과값을 담아올 수 있도록

memauth를 Model에 추가했다.

 

이후 다시 Provider로 돌아와서 입력한 pw, 암호화된 mempw의 값이 일치한다면,

 

if(passwordEncoder.matches(userpw, user.mempw)) {

    val authCode = user.memauth

    val role = when (authCode) {

        "A" -> "ROLE_A"
        "U" -> "ROLE_U"
        "S" -> "ROLE_S"
        else -> throw BadCredentialsException("회원 등급 구분할 수 없음.")

    }
    val authorities = listOf(SimpleGrantedAuthority(role))

    return UsernamePasswordAuthenticationToken(userid,userpw,authorities)

 

회원 구분자를 변수에 담고,

A : ROLE_ADMIN

U : ROLE_USER

S : ROLE_SUPER로 나누었다.

그 외 구분자가 없는 회원은 미승인 or 준회원 정도로 따로 추가할 예정이다.

return UsernamePasswordAuthenticationToken(userid,userpw,authorities)

UsernamePasswordAuthenticationToken은 인증된 사용자 정보사용자의 권한 정보를 포함하는 객체입니다. 주로 로그인 과정에서 인증을 진행한 후, 해당 사용자의 정보와 권한을 Spring Security의 컨텍스트에 담기 위해 사용됩니다. -gpt

 

해당 사용자의 정보를 담아서 권한을 나눠줘야하니, 우선 정보가 잘 담겨서 들어오는지 확인을 해야한다.

 

이 프로젝트는 로그인이 정상적으로 실행이되면, main.html로 이동하도록 Controller을 작성했다.

 

4.Controller

//메인페이지 이동
@RequestMapping(value = [("/main")])
@Throws(Exception::class)
fun mainPage(model: Model, authentication: Authentication): String {
    model.addAttribute("userid", authentication.name)
    model.addAttribute("role", authentication.authorities.first().authority)
    return "main"
}

컨트롤러를통해 메인페이지로 이동을할 때,

model객체에 정보를 담아 옮기려도 추가로 작성했다.

 

5.Main.html

<header style="background-color: #1b2838; padding: 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #c7d5e0;">
    <input type="hidden" th:id="${_csrf.parameterName}" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <input type="hidden" id="loginUserId" th:value="${userid}" />
    <input type="hidden" id="loginUserRole" th:value="${role}" />
    <h1 style="margin: 0; font-size: 2em; color: #66c0f4;">
        <a href="/" style="color: #66c0f4; text-decoration: none;">My Blog & Portfolio</a>
    </h1>
    <nav style="float: right; display: flex; gap: 10px;">
        <a href="/logout" class="header-btn" style="background-color: #66c0f4; color: #ffffff; padding: 10px 20px; text-decoration: none; border-radius: 5px; transition: background-color 0.3s ease; font-size: 0.9em;">로그아웃</a>
    </nav>
</header>

헤더에 hidden값으로 받아온 userid, role 값을 추가했다.

 

6.결과

메인페이지
개발자도구 확인

새롭게 추가된 role의값이 ROLE_A로 잘 들어온 것을 확인 할 수 있다.

 

다음 포스팅은 해당 권한에따라 기능을 제한하거나, 추가하는 것을 작성해보면 좋을 것 같다.

반응형