본문 바로가기
Programming/Spring Security

[Spring Boot] Spring Security (3) - 로그인 시 사용자 역할 조회 및 부여

by 주리니e 2022. 8. 17.
728x90

[Spring Boot] Spring Security (3) - 로그인 시 사용자 역할 조회 및 부여

 

개발환경

  • Eclipse IDE 2022-06
  • Spring Boot 2.7.2
  • Gradle 7.0
  • Lombok
  • PostgreSQL

지난 시간에 이어서 이번에는 로그인 시 사용자가 가지고 있는 역할을 데이터베이스에서 조회하여 부여하고 이를 확인하는 페이지를 만들어보려고 한다. 추가/수정된 파일의 소스코드만 업로드 하니 지난 시간에 쓴 글을 참조하여 따라하길 바란다. 우선 테이블과 기초데이터를 등록하자. 

-- TB_USER
CREATE TABLE public.tb_user (
	id varchar(255) NOT NULL,
	name varchar(255) NULL,
	"password" varchar(255) NULL,
	CONSTRAINT tb_user_pkey PRIMARY KEY (id)
);

-- TB_ROLE
CREATE TABLE public.tb_role (
	id int8 NOT NULL,
	"name" varchar(255) NULL,
	CONSTRAINT tb_role_pkey PRIMARY KEY (id)
);

-- TB_USER_ROLES
CREATE TABLE public.tb_user_roles (
	user_id varchar(255) NOT NULL,
	role_id int8 NOT NULL,
	CONSTRAINT tb_user_roles_pkey PRIMARY KEY (user_id, role_id)
);

-- TB_USER_ROLES foreign keys
ALTER TABLE public.tb_user_roles ADD CONSTRAINT fk19t64ocsol5x06fy2cytp7gey FOREIGN KEY (user_id) REFERENCES tb_user(id);
ALTER TABLE public.tb_user_roles ADD CONSTRAINT fkft1jmfcluls775jqp5142wvl8 FOREIGN KEY (role_id) REFERENCES tb_role(id);
-- 사용자 정보 등록 (비밀번호는 BCryptPasswordEncoder로 인코딩 된 값을 넣었다. 로그인 비밀번호 : 123)
INSERT INTO public.tb_user
(id, "name", "password")
VALUES('system', 'system', '$2a$10$6iJv7gmGXdP2Sf0ojKBfwuUgxaHqv0c3f50SfDNdxNxq.dSymAAlW');

INSERT INTO public.tb_user
(id, "name", "password")
VALUES('user', 'user', '$2a$10$6iJv7gmGXdP2Sf0ojKBfwuUgxaHqv0c3f50SfDNdxNxq.dSymAAlW');


-- 역할 등록
INSERT INTO public.tb_role
(id, "name")
VALUES(1, 'ROLE_SYSTEM');

INSERT INTO public.tb_role
(id, "name")
VALUES(2, 'ROLE_USER');

INSERT INTO public.tb_role
(id, "name")
VALUES(3, 'ROLE_BOARD');


-- 사용자 별 역할 등록
INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('system', 1);

INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('user', 2);

INSERT INTO public.tb_user_roles
(user_id, role_id)
VALUES('user', 3);

기초데이터 기반으로 해당 시스템에 세가지 역할(SYSTEM, USER, BOARD) 을 두었다. system 계정은 SYSTEM 역할을, user 계정에는 USER 역할과 BOARD 역할을 부여하였다.

패키지 구조

나중에는 시스템 관리자가 역할 및 권한을 생성 및 부여 페이지를 관리할 수 있고 사용자가 여러개의 역할 및 권한을 가질 수 있도록 User.java와 Role.java를 이용하여 다대 다 구조로 엔티티를 생성하였다.

  • User.java
package com.example.security.persistence.model;

import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import org.hibernate.annotations.DynamicUpdate;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "tb_user")
@DynamicUpdate
public class User {

	@Id
	@Column(name = "id")
	private String id;

	@Column(name = "password")
	private String password;

	@Column(name = "name")
	private String name;

	@ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
	@JoinTable(name = "tb_user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
	private Set<Role> roles;

	@Builder
	public User(String id, String password, String name, Set<Role> roles) {
		this.id = id;
		this.password = password;
		this.name = name;
		this.roles = roles;
	}
}

 

  • Role.java
package com.example.security.persistence.model;

import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@Table(name = "tb_role")
public class Role {

	@Id
	@GeneratedValue(strategy= GenerationType.IDENTITY)
	@Column(name = "id")
	private Long id;

	@Column(name = "name")
	private String name;

	@ManyToMany(mappedBy = "roles")
	private Set<User> users;
}

 

  • CustomUserDetailsService.java
package com.example.security.service;

import java.util.HashSet;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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;
import org.springframework.transaction.annotation.Transactional;

import com.example.security.persistence.dao.UserRepository;
import com.example.security.persistence.model.Role;

import lombok.AllArgsConstructor;

@Service
@AllArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

	private final UserRepository userRepository;

	@Override
	@Transactional(readOnly = true)
	public UserDetails loadUserByUsername(String id) {

		Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
		com.example.security.persistence.model.User user = userRepository.findOneById(id);

		if (user != null) {
			for (Role role : user.getRoles()) {
				grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())); // DB에 저장되어 있는 권한을 부여한다.
			}
			return new User(user.getId(), user.getPassword(), grantedAuthorities);
		} else {
			throw new UsernameNotFoundException("can not find User : " + id);
		}
	}
}

로그인 시 user.getRoles() 메소드를 호출함으로써 사용자가 가지고 있는 역할을 조회하여 부여한다.

 

  • UserRole.java
package com.example.security.enums;

public enum UserRole {
	SYSTEM, BOARD, USER
}

 

  • hello.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<title>Hello World!</title>
</head>
<body>
	<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
	<br>

	<!--SYSTEM 역할을 갖는다면 이 글이 보임-->
	<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).SYSTEM)">Has SYSTEM Role</h3>

	<!--USER 역할을 갖는다면 이 글이 보임-->
	<h3 sec:authorize="hasRole(T(com.example.security.enums.UserRole).USER)">Has USER Role</h3>

	<!--USER 역할을 갖는다면 이 글이 보임-->
	<h3 sec:authorize="hasRole('BOARD')">Has BOARD Role</h3>

	<!--어떤 역할이건 상관없이 인증이 되었다면 이 글이 보임-->
	<h3 sec:authorize="isAuthenticated()">Only Authenticated user can see this Text</h3>

	<div>
		Authorities : <span sec:authentication="principal.authorities"></span>
	</div>
	<br>
	<form th:action="@{/logout}" method="post">
		<input type="submit" value="Sign Out" />
	</form>
</body>
</html>

여기서 한가지 짚고 넘어가야할 부분이 있다. 테이블 TB_ROLE 테이블의 SYSTEM 역할의 값은 ROLE_SYSTEM이다. 그렇지만 위에 값을 보면 hasRole('SYSTEM')을 비교하고 있으며 hasRole 메소드의 경우에는 접두어로써 'ROLE_' 값을 붙여 조회하기 때문에 매칭이 된다. 흔히 Spring Security에서 제공하는 역할(Role)과 권한(Authority)을 혼동하는 사용하는 경우가 많은데 비슷하면서도 다르다. 예를 들어 A계정과 B계정은 둘다 시스템관리자로서 SYSTEM 역할을 가지고 있지만 원칙상 A 계정은 게시판의 글을 등록할 수만 있으며, B 계정은 등록된 글을 수정만 할 수 있다고 했을 때 역할은 같지만 권한은 다르게 설정해야 할 것이다. 이 부분에 대해선 향후 자세히 알아보자.

system 계정 hello 화면
user 계정 hello 화면

지난시간에 생성한 playground 계정 로그인 화면

system 계정은 SYSTEM 역할을 가지고 있고 user 계정은 USER 역할과 BOARD 역할을 가지고 있다. 지난 시간에 생성한 playground 계정은 아무 역할을 가지고 있지 않는다. 기본 역할을 주기 위해서 앞으로 회원가입 하는 사용자에게 USER 역할을 부여해보자.

  • UserService.java
package com.example.security.service;

import java.util.HashSet;
import java.util.Set;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import com.example.security.persistence.dao.RoleRepository;
import com.example.security.persistence.dao.UserRepository;
import com.example.security.persistence.model.Role;
import com.example.security.web.dto.UserDto;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class UserService {

	private final UserRepository userRepository;

	private final RoleRepository roleRepository;

	private final BCryptPasswordEncoder bCryptPasswordEncoder;

	public void insert(UserDto userDto) {
		userDto.setPassword(bCryptPasswordEncoder.encode(userDto.getPassword()));
		Set<Role> rolesSet = new HashSet<Role>();
		rolesSet.add(roleRepository.findQneById(2L)); // id : 2 ROLE_USER 역할
		userRepository.save(userDto.toEntity(rolesSet));
	}
}

 

  • UserDto.java
package com.example.security.web.dto;

import java.util.Set;

import com.example.security.persistence.model.Role;
import com.example.security.persistence.model.User;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class UserDto {
	private String id;

	private String password;

	private String name;

	private Set<Long> roles;

	public User toEntity(Set<Role> roles) {
		return User.builder().id(id).password(password).name(name).roles(roles).build();
	}
}

 

  • RoleRepository.java
package com.example.security.persistence.dao;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.security.persistence.model.Role;

public interface RoleRepository extends JpaRepository<Role, Long> {
	Role findQneById(Long id);
}

 

playground1 회원가입 화면
playground1 계정의 hello 화면

위 화면을 보면 앞으로는 회원가입 시 자동으로 USER라는 역할을 부여하여 자동으로 회원가입이 완료된다. 다음 시간에는 사용자 별 부여한 역할 및 권한을 바탕으로 사용자 페이지 접근제어를 해보자.

 

이전글 : [Spring Boot] Spring Security (2) - 회원가입 및 로그인을 위한 데이터베이스 연동

 

[Spring Boot] Spring Security (2) - 회원가입 및 로그인을 위한 데이터베이스 연동

Spring Security (2) - 회원가입 및 로그인을 위한 데이터베이스 연동 개발환경 Eclipse IDE 2022-06 Spring Boot 2.7.2 Gradle 7.0 Lombok PostgreSQL 지난 시간에 이어서 이번에는 사용자 정보를 메모리에..

jiurinie.tistory.com

다음글 : [Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어

 

[Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어

[Spring Boot] Spring Security (4) - 역할 별 페이지 접근제어 개발환경 Eclipse IDE 2022-06 Spring Boot 2.7.2 Gradle 7.0 Lombok PostgreSQL 지난 시간에 이어서 이번에는 사용자가 가지고 있는 역할을 확인..

jiurinie.tistory.com

 

728x90

댓글