본문 바로가기

Spring

SpringSecurity - jdbc를 이용한 인증/권한 처리

- 오라클 사용

- XML 설정 사용

- JDBC를 위한 DriverManaberDataSource 사용

 

1. 권한 처리를 위한 테이블 생성

CREATE TABLE authorities(
    username VARCHAR2(50) NOT NULL PRIMARY KEY,
    password VARCHAR2(50) NOT NULL,
    enabled char(1) DEFAULT '1'
);
CREATE TABLE authorities(
    username VARCHAR2(50) NOT NULL,
    authority VARCHAR2(50) NOT NULL,
    CONSTRAINT fk_authorities_users FOREIGN KEY(username) REFERENCES users(username)
);
CREATE UNIQUE INDEX ix_auth_username on authorities(username, authority);
INSERT INTO USERS (username, password) VALUES('user00', 'pw00');
INSERT INTO USERS (username, password) VALUES('member00', 'pw00');
INSERT INTO USERS (username, password) VALUES('admin00', 'pw00');
INSERT INTO authorities (username, authority) VALUES('user00', 'ROLE_USER');
INSERT INTO authorities (username, authority) VALUES('member00', 'ROLE_MANAGER');
INSERT INTO authorities (username, authority) VALUES('admin00', 'ROLE_MANAGER');
INSERT INTO authorities (username, authority) VALUES('admin00', 'ROLE_ADMIN');
COMMIT;

2. JDBC연결을 위한 Bean생성(DriverManaberDataSource사용)

root-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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Root Context: defines shared resources visible to all other web components -->
	
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName"
			value="oracle.jdbc.driver.OracleDriver" />
		<property name="url"
			value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="scott" />
		<property name="password" value="tiger" />
	</bean>
	
</beans>

 

3. 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">
	
    <!-- 1. 로그인/에러 처리를 위한 빈 설정(4에서 등록)-->
    <bean id="customLoginSuccess" class="org.zerock.security.CustomLoginSuccessHandler"></bean>
	<bean id="customAccessDenied" class="org.zerock.security.CustomAccessDeniedHandler"></bean>
	
    <!-- 2. password encoder를 위한 빈 설정 -->
    <bean id="customPasswordEncoder" class="org.zerock.security.CustomNoOpPasswordEncoder"></bean>
	
	<security:http auto-config="true">
    	<!-- 3. 각 url 패턴에 따른 access 조건 설정 -->
		<security:intercept-url pattern="/sample/all" access="permitAll"></security:intercept-url>
		<security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"></security:intercept-url>
		<security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"></security:intercept-url>
        
        <!-- 4. 로그인 안되어 있을 경우 로그인 페이지 이동 설정 및 로그인 성공/실패 Bean 등록(1번에서 만듦) -->
		<security:form-login login-page="/customLogin" authentication-success-handler-ref="customLoginSuccess" />
		<security:access-denied-handler ref="customAccessDenied" />

	</security:http>
    
	<security:authentication-manager
		alias="authenticationManager">
		<security:authentication-provider>
        	<!-- 5-1. jdbc를 위한 dataSource빈 등록(root-context.xml에서 만들었음) -->
            <!-- 5-2. DB에서 사용자와 권한 정보를 가져오는 쿼리 작성 -->
			<security:jdbc-user-service
				data-source-ref="dataSource"
				users-by-username-query="SELECT USERNAME, PASSWORD, ENABLED FROM USERS WHERE USERNAME=?"
				authorities-by-username-query="SELECT USERNAME, AUTHORITY FROM AUTHORITIES WHERE USERNAME=?" />
        	
            <!-- 6. password Encoder 등록(2에서 만듦) -->
            <security:password-encoder ref="customPasswordEncoder"/>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

1. 로그인 성공/ 에러 처리를 위한 Bean 생성 - 각 class는 만들어야함.

2. password Encoder를 위한 Bean등록 - 각 class는 만들어야하며(PasswordEncoder 구현), Security5부터 필수임. 

3. url패턴에 따라 권한 조건 설정

4. 1번에서 생성한 로그인 성공/에러 처리를 위한 Bean등록

5. jdbc를 위한 dataSource등록 및 사용자/권한 정보를 가져오기 위한 쿼리 등록

6. 2에서 생성한 password Encoder를 등록

 

 

 

4. 로그인 성공/에러를 위한 클래스

로그인 거부 처리 : CustomAccessDeniedHandler.java

package org.zerock.security;

import java.io.IOException;

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

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import lombok.extern.java.Log;

@Log
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		log.info("Access Denied Handler");
		log.info("redirect....");
		response.sendRedirect("/security/accessError");
	}

}

 

 

로그인 성공처리 CustomLoginSuccessHandler.java

package org.zerock.security;

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;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import lombok.extern.java.Log;

@Log
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication auth) throws IOException, ServletException {
		log.info("Login Success");
		
		List<String> roleNames = new ArrayList<>();
		
		auth.getAuthorities().forEach(authority -> {
			roleNames.add(authority.getAuthority());
		});
		
		log.info("ROLE NAME : " + roleNames);
		
		if(roleNames.contains("ROLE_ADMIN")) {
			 response.sendRedirect("/security/sample/admin");
			 return;
		}
		
		if(roleNames.contains("ROLE_MEMBER")) {
			response.sendRedirect("/security/sample/member");
			return;
		}
		response.sendRedirect("/");
	}

}

 

5. PasswordEncoder 클래스

CustomNoOpPasswordEncoder.java

아래 클래스는 rawPassword 그냥 사용.

package org.zerock.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;

public class CustomNoOpPasswordEncoder implements PasswordEncoder {

    Logger log = LoggerFactory.getLogger(CustomNoOpPasswordEncoder.class);

	@Override
	public String encode(CharSequence rawPassword) {
		log.warn("before encode : " + rawPassword);
		return rawPassword.toString();
	}

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		log.warn("rawPass : " + rawPassword + " : " + encodedPassword);

		return rawPassword.toString().equals(encodedPassword);
	}

}

 

 

6. 기타 jsp파일들

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>Custom Login Page</h1>	
	<form method='post' action='login'>
		<div>
			<input type='text' name='username' value='admin'>
		</div>
		<div>
			<input type='password' name='password' value='admin'>
		</div>
		<div>
			<input type='submit'>
		</div>
		<input type='hidden' name="${_csrf.parameterName}" value="${_csrf.token}"/>
	</form>


</body>
</html>

input에 "${_csrf.parameterName}"'과 "${_csrf.token}"필요.(csrf공격 때문)