작성 중인 '블로그 프로젝트' 에서는 기본적으로 '관리자' 가 작성한 글들을 '사용자' 가 읽을 수 있도록 합니다.
사용자는 주로 조회를 할 수 있고, 관리자는 카테고리 추가/수정/삭제, 글 추가/수정/삭제 등의 블로그 전체를 관리 할 수 있도록 합니다.
사용자가 아닌 관리자를 식별하기 위해서 로그인 기능이 필요합니다.
로그인 페이지를 구현하고, Spring boot Web Security 기능을 이용하여 어떤 기능으로 어떻게 처리 하는 지 소개하도록 하겠습니다.
참조 URL
데이터베이스
users
authorities
CREATE TABLE `users` (
`username` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '사용자 아이디',
`password` varchar(500) COLLATE utf8_bin NOT NULL COMMENT '비밀번호',
`enabled` tinyint(1) NOT NULL COMMENT '사용 여부',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE `authorities` (
`username` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '사용자 아이디',
`authority` varchar(50) COLLATE utf8_bin NOT NULL COMMENT '권한',
UNIQUE KEY `ix_auth_username` (`username`,`authority`),
CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO users
(username, password, enabled)
VALUES('happygram', '$2a$10$Ev7ngM/5BdtA7iRI9PkmduvXQFYA9T0vz0zl3KFIbe5JTQ7aN2Fuu', 1);
System.err.println(new BCryptPasswordEncoder().encode("happygram"));
INSERT INTO authorities
(username, authority)
VALUES('happygram', 'ROLE_ADMIN');
Dependencies
dependencies {
compile("org.springframework.boot:spring-boot-starter-security")
}
Spring boot 라이브러리 중 'security' 를 사용합니다.
로그인 및 로그아웃 처리에 유용하고, 보안 관련한 기능도 설정할 수 있습니다.
Spring boot Security 참조 문서
소스
<!DOCTYPE html> <html lang="ko" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="context-path" th:content="@{/}"/> <title>HM & SB Blog</title> <!-- login --> <link th:href="@{/css/login.css}" rel="stylesheet"> <!-- Common header --> <th:block th:include="common/include-link"></th:block> </head> <body> <!-- Common script --> <th:block th:include="common/include-script"></th:block> <div class="container centered text-center"> <form class="form-signin" method="POST" th:action="@{/login}"> <h1 class="h3 mb-3 font-weight-normal">[[${login_message}]]</h1> <label for="username" class="sr-only">ID</label> <input type="text" id="username" name="username" class="form-control" placeholder="ID" required="" autofocus=""> <label for="password" class="sr-only">Password</label> <input type="password" id="password" name="password" class="form-control" placeholder="Password" required=""> <div class="checkbox mb-3"> <label><input type="checkbox" value="remember-me"> Remember me</label> </div> <div align="center" th:if="${param.error}"> <p style="font-size: 20; color: #FF1C19;">ID or Password invalid, please verify</p> </div> <button class="btn btn-primary btn-block" type="submit">Sign in</button> </form> </div> </body> </html> | cs |
templates 패키지에 'login.html' 파일을 추가 하였습니다.
앞서 소개한 글에서는 'index.html' 파일을 추가 하였고, 해당 파일이 레이아웃이 되어서, 다른 페이지를 포함하는 형태로 소개 하였습니다.
'login.html' 은 'index.hmlt' 과 별개의 파일로 로그인 시도 시 별도로 로딩 되도록 구성 하였습니다. 즉, 단일 페이지 입니다.
공통적으로 사용하는 'include-link.html', 'include-script.html' 페이지들을 포함 시켰고, 로그인 페이지에서 필요한 파일은 'login.css' 파일에 기술하고 포함 시켰습니다.
'username', 'password' 를 입력하고, 'Sign in' 버튼을 누르면 '/login' 'POST' 형태로 로그인을 시도를 합니다.
[[${login_message}]] : thymeleaf 문법으로, 서버에서 받은 정보를 출력합니다.
th:if="${param.error}" : 로그인 실패 시 '/login?error' 형태로 리다이렉트 되고, 해당 영역을 보여줍니다.
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/login")
public class LoginController {
@GetMapping
public String viewLogin(Model model) {
// View attribute
model.addAttribute("login_message", "로그인이 필요합니다.");
return "login";
}
}
'/login' 의 'GET' 요청이 있는 경우 'login.html' 을 로딩 합니다.
해피그램의 '블로그 만들기' 프로젝트에서는 '/login' 을 요청하기 위한 버튼을 따로 만들어두지 않았습니다.
사용자 입장에서는 필요한 기능이 아니기 때문입니다.
필요한 경우 URL 에 직접 타이핑 하여 접근하도록 하였습니다.
model.addAttribute("login_message", "로그인이 필요합니다.") : 'login_message' 변수에 담아서 Front-End 로 전달합니다.
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/write", "/write/*").authenticated()
.antMatchers("/admin", "/admin/*").authenticated()
.antMatchers("/users", "/users/*").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder())
;
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/favicon.ico", "/css/**", "/image/**", "/js/**", "/webjars/**");
}
}
Spring boot Web Security 기능을 이용하여 로그인 처리를 합니다.
로그인 Service 기능을 따로 구현하여 처리할 수도 있지만, Spring boot 에서 제공하는 좋은 기능을 이용합니다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/write", "/write/*").authenticated()
.antMatchers("/admin", "/admin/*").authenticated()
.antMatchers("/users", "/users/*").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.permitAll();
}
Front-End 에서 '/write', '/admin', '/users' 에 대한 요청이 있는 경우 모두 인증을 진행하도록 하고, 그 외의 경우에는 모두 허용합니다.
'login' 요청이 있는 경우 인증을 진행하여, 인증이 성공한 경우 기본 경로로 설정한 '/' 으로 이동합니다.
'logout' 요청이 있는 경우 로그아웃 처리를 하고, 성공한 경우 기본 경로로 설정한 '/' 으로 이동합니다.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(new BCryptPasswordEncoder())
;
}
인증 방식을 설정할 수 있습니다.
해피그램의 '블로그 만들기' 프로젝트에서는 데이터베이스를 이용하여 인증을 진행합니다.
기본적으로 'users', 'authorities' 테이블과 비교하여, 인증을 진행합니다.
('users' 테이블 - username, password, enabled, 'authorities' 테이블 - username, authority)
사용자 테이블을 이용하여 비교하고 싶은 경우 'usersByUsernameQuery', 'authoritiesByUsernameQuery' 메소드를 추가 호출하여, 쿼리를 지정할 수 있습니다.
비밀번호 인증 방식은 'BCrypt' 를 이용한 방식입니다. 그 외에도 여러가지 방식이 있습니다.
BCryptPasswordEncoder : 해시 문자열을 이용한 암호화 방식
BCrypt 관련 자료
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/favicon.ico", "/css/**", "/image/**", "/js/**", "/webjars/**");
}
인증에서 제외할 경로를 설정합니다.
인증 대상이 아닌 resources 역할을 하는 자원들을 주로 제외 시킵니다.
CSS, Images, Javascript, Third Party Library 등이 될 수 있습니다.
결과
username, password 를 입력하고 'Sing In' 버튼을 클릭해서 로그인을 시도합니다.
실패하는 경우
param 변수에 error 담고, 로그인 페이지로 리다이렉트 합니다.
그리고 필요한 영역을 보여줍니다.
성공하는 경우
인증을 성공하면 '로그아웃' 버튼, 관리자 전용 메뉴, 버튼이 보입니다.
참조 도구 목록 - 도움에 감사를 드립니다.
'IT 프로젝트 > 블로그 만들기' 카테고리의 다른 글
[Spring Boot] 스프링 부트 프로젝트/블로그 만들기 - 관리자(admin) 카테고리(category) 페이지 만들기 (0) | 2018.11.19 |
---|---|
[Spring Boot] 스프링 부트 프로젝트/블로그 만들기 - 관리자(admin) index 페이지 만들기 (0) | 2018.11.18 |
[Spring Boot] 스프링 부트 프로젝트/블로그 만들기 - 사이드바(sidebar) 만들기 (0) | 2018.11.16 |
[Spring Boot] 스프링 부트 프로젝트/블로그 만들기 - Welcome 페이지 만들기 (3) | 2018.11.15 |
[Spring Boot] 스프링 부트 프로젝트/블로그 만들기 - Resource 경로 지정 (2) | 2018.11.15 |