[B -2-54] 스프링 시큐리티 1

2019. 10. 10. 23:41Project B (SPMS)/Project B 파트7

반응형

스프링 시큐리티 작업

  • 로그인과 회원 가입 페이지 작성
  • 기존 화면과 컨트롤러에 시큐리티 관련 내용 추가
  • Ajax 부분 변경

스프링 시큐리티 관련 설정 파일 확인

 

controller

CommonController.java

 

domain

AuthVO.java

MemberVO.java

 

mapper

MemberMapper.java

 

security

CustomAccessDeniedHandler.java

CustomLoginSuccessHandler.java

CustomNoOpPasswordEncoder.java

CustomUserDetailsService.java

 

security.domain

CustomUser.java

 

mapper 리소스

MemberMapper.xml

 


로그인 페이지 처리

 

로그인 페이지 기본 HTML 코드는 webapp/resources 내의 pages/login.html 페이지를 이용해서 만든다.

 

views

customLogin.jsp

 

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html lang="en">

<head>

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">

<title>SB Admin 2 - Bootstrap Admin Theme</title>

<!-- Bootstrap Core CSS -->
<link href="/resources/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">

<!-- MetisMenu CSS -->
<link href="/resources/vendor/metisMenu/metisMenu.min.css" rel="stylesheet">

<!-- Custom CSS -->
<link href="/resources/dist/css/sb-admin-2.css" rel="stylesheet">

<!-- Custom Fonts -->
<link href="/resources/vendor/font-awesome/css/font-awesome.min.css"
	rel="stylesheet" type="text/css">

<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

</head>

<body>

	<div class="container">
		<div class="row">
			<div class="col-md-4 col-md-offset-4">
				<div class="login-panel panel panel-default">
					<div class="panel-heading">
						<h3 class="panel-title">Please Sign In</h3>
					</div>
					<div class="panel-body">
						<form role="form" method='post' action="/login">
							<fieldset>
								<div class="form-group">
									<input class="form-control" placeholder="userid"
										name="username" type="text" autofocus>
								</div>
								<div class="form-group">
									<input class="form-control" placeholder="Password"
										name="password" type="password" value="">
								</div>
								<div class="checkbox">
									<label> <input name="remember-me" type="checkbox">Remember Me
									</label>
								</div>
								<!-- Change this to a button or input when using this as a form -->
								<a href="index.html" class="btn btn-lg btn-success btn-block">Login</a>
							</fieldset>
							<input type="hidden" name="${_csrf.parameterName}"
								value="${_csrf.token}" />
						</form>

					</div>
				</div>
			</div>
		</div>
	</div>



	<!-- jQuery -->
	<script src="/resources/vendor/jquery/jquery.min.js"></script>

	<!-- Bootstrap Core JavaScript -->
	<script src="/resources/vendor/bootstrap/js/bootstrap.min.js"></script>

	<!-- Metis Menu Plugin JavaScript -->
	<script src="/resources/vendor/metisMenu/metisMenu.min.js"></script>

	<!-- Custom Theme JavaScript -->
	<script src="/resources/dist/js/sb-admin-2.js"></script>
  <script>
  
  
  
  
  $(".btn-success").on("click", function(e){
    
    e.preventDefault();
    $("form").submit();
    
  });
  
  </script>
  
<c:if test="${param.logout != null}">
      <script>
      $(document).ready(function(){
      	alert("로그아웃하였습니다.");
      });
      </script>
</c:if>  

</body>

</html>

 

customLogin.jsp 작성 시 주의점

 

  • <form> 태그 내의 <input> 태그의 name 속성스프링 시큐리티에 맞게 수정
  • JSTL 이나 스프링 시큐리티 태그를 사용할 수 있게 선언
  • CSS 파일이나 JS 파일의 링크는 절대 경로를 쓰도록 수정
  • CSRF 토큰 항목 추가
  • 자바스크립트를 통한 로그인 전송

로그인 화면표시 테스트

 

http://localhost/customLogin

 

 

 


세큐리티 환경 파일

 

src/main/java

com.spms.config

SecurityConfig.java

 

더보기
package com.spms.config;


import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.spms.security.CustomLoginSuccessHandler;
import com.spms.security.CustomUserDetailsService;

import lombok.Setter;
import lombok.extern.log4j.Log4j;

@Configuration
@EnableWebSecurity
@Log4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Setter(onMethod_ = { @Autowired })
	private DataSource dataSource;

	@Bean
	public UserDetailsService customUserService() {
		return new CustomUserDetailsService();
	}

	// in custom userdetails
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

		auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
	}

	@Bean
	public AuthenticationSuccessHandler loginSuccessHandler() {
		return new CustomLoginSuccessHandler();
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {

		http.authorizeRequests()
			.antMatchers("/sample/all").permitAll()
			.antMatchers("/sample/admin").access("hasRole('ROLE_ADMIN')")
			.antMatchers("/sample/member").access("hasRole('ROLE_MEMBER')");

		http.formLogin()
			.loginPage("/customLogin")
			.loginProcessingUrl("/login");

		http.logout()
			.logoutUrl("/customLogout")
			.invalidateHttpSession(true)
			.deleteCookies("remember-me","JSESSION_ID");
		
		http.rememberMe()
			.key("spms")
			.tokenRepository(persistentTokenRepository())
			.tokenValiditySeconds(604800);
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
		repo.setDataSource(dataSource);
		return repo;
	}
}

 


게시물 작성 시 스프링 시큐리티 처리

 

1. 게시글 작성 버튼 클릭

2. 로그인 하지 않은 상태라면, 로그인 화면 표시

3. 로그인 되어있는 상태라면, 게시글 작성 폼 

 


src/main/java

com.spms.controller

BoardController.java

 

더보기
package com.spms.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.spms.domain.BoardVO;
import com.spms.domain.Criteria;
import com.spms.domain.PageDTO;
import com.spms.service.BoardService;

import lombok.AllArgsConstructor;
import lombok.extern.log4j.Log4j;

@Controller
@Log4j
@RequestMapping("/board/*")
@AllArgsConstructor
public class BoardController {

	private BoardService boardService;

	@GetMapping("/list")
	public void list(Criteria cri, Model model) {
		log.info("list" + cri);
		boardService.getList(cri).forEach(board -> log.info(board));
		model.addAttribute("list", boardService.getList(cri));
		
		int total = boardService.getTotal(cri); 
		log.info("total : " + total);
		model.addAttribute("pageMaker", new PageDTO(cri, total));

	}
	
	
	@GetMapping("/register")
	@PreAuthorize("isAuthenticated()")
	public void register() {

	}

	@PostMapping("/register")
	@PreAuthorize("isAuthenticated()")
	public String register(BoardVO board, RedirectAttributes rttr) {
		
		log.info("register : " + board);
		boardService.register(board);
		rttr.addFlashAttribute("result", board.getBno());
		return "redirect:/board/list";
	}

	@GetMapping({ "/get", "/modify" })
	public void get(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, Model model) {
		log.info("/get or modify");
		model.addAttribute("board", boardService.get(bno));
	}

	@PostMapping("/modify")
	public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
		log.info("modify:" + board);

		if (boardService.modify(board)) {
			rttr.addFlashAttribute("result", "success");
		}
		
		return "redirect:/board/list" + cri.getListLink();
	}

	@PostMapping("/remove")
	public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
		log.info("remove..." + bno);
		if (boardService.remove(bno)) {
			rttr.addFlashAttribute("result", "success");
		}
		
		return "redirect:/board/list" + cri.getListLink();
	}

}

 

@PreAuthorize 이용시 표현식은 isAuthenticated() 로 어떠한 사용자든 로그인이 성공한 사용자만 해당 기능을 사용할 수 있게 처리한다.

 


웹 환경설정 파일에 스프링 시큐리티 환경설정 추가

com.spms.config

WebConfig.java

 

더보기
package com.spms.config;

import javax.servlet.Filter;

import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer   {

	@Override
	protected Class<?>[] getRootConfigClasses() {
	    return new Class[] { RootConfig.class, SecurityConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		// TODO Auto-generated method stub
		return new Class[] { ServletConfig.class };
	}

	@Override
	protected String[] getServletMappings() {
		// TODO Auto-generated method stub
		return new String[] { "/" };
	}

	@Override
	protected Filter[] getServletFilters() {
		// TODO Auto-generated method stub
		CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
		characterEncodingFilter.setEncoding("UTF-8");
		characterEncodingFilter.setForceEncoding(true);
		return new Filter[] { characterEncodingFilter };
	}
}

 


시큐리티 초기화 작업자

com.spms.config

SecurityIntializer.java

 

더보기
package com.spms.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityIntializer extends AbstractSecurityWebApplicationInitializer {
	

}

 

 


게시글 작성 시 로그인한 사용자의 아이디 출력

 

views/board

register.jsp

 

더보기
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>

<%@ include file="../includes/header.jsp"%>
<div class="row">
	<div class="col-lg-12">
		<h1 class="page-header">게시판 등록</h1>
	</div>
	<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
	<div class="col-lg-12">
		<div class="panel panel-default">
			<div class="panel-heading">게시글 등록 페이지</div>
			<!-- /.panel-heading -->
			<div class="panel-body">
				<form action="/board/register" role="form" method="post">
				<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
					<div class="form-group">
						<label>작성자</label>
						<input class="form-control" name="writer" value='<sec:authentication property="principal.username"/>' readonly="readonly" >
					</div>
					<div class="form-group">
						<label>제목</label>
						<input class="form-control" name="title">
					</div>
					<div class="form-group">
						<label>내용</label>
						<textarea class="form-control" rows="10" name="content"></textarea>
					</div>
					<button type="submit" class="btn btn-primary">등록</button>
					<a href="/board/list">
						<button type="button" class="btn btn-success">목록</button>
					</a>
				</form>
			</div>
			<!-- /.panel-body -->
		</div>
		<!-- /.panel -->
	</div>
	<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
</div>
<!-- /#page-wrapper -->
<%@ include file="../includes/footer.jsp"%>

게시글 작성은 로그인한 사용자들만 허가되므로, 작성자 항목에는 현재 사용자 아이디가 출력될 수 있게 수정한다.

등록 페이지와 같이 스프링 시큐리티의 영향을 받는 JSP 파일에는 반드시 시큐리티 관련 태그 라이브러리 설정을 하도록 주의한다.

작성자에 현재 사용자는 현재 로그인한 사용자 아이디를 출력한다.

(스프링 시큐리티에서는 username이 사용자의 아이디이다.)

 

 


유저(admin90)로 로그인한 상태

(*) /customLogin 로 가서 로그인하면, 에러가 떨어지는데, 당황하지말고 바로 /board/list 화면으로 가서 등록버튼을 누르면 로그인 되어있음을 확인할 수 있다.

 

유저 계정 : admin90 / pw90

 

 

작성자명이 자동으로 로그인 아이디로 들어가있음을 알 수 있다.

 

반응형