2020. 1. 21. 22:10ㆍProject C (SBMS)/Project C 파트3
src/main/java
com.sbms.domain
AuthVO.java
package com.sbms.domain;
import lombok.Data;
@Data
public class AuthVO {
private String userid;
private String auth;
}
src/main/java
com.sbms.domain
MemberVO.java
package com.sbms.domain;
import java.util.Date;
import java.util.List;
import lombok.Data;
@Data
public class MemberVO {
private String userid;
private String userpw;
private String userName;
private boolean enabled;
private Date regDate;
private Date updateDate;
private List<AuthVO> authList;
}
src/main/java
com.sbms.mapper
MemberMapper.java
package com.sbms.mapper;
import com.sbms.domain.MemberVO;
public interface MemberMapper {
public MemberVO read(String userid);
public void createMember(MemberVO member);
public void createMemberAuth(MemberVO member);
}
src/main/resources
com
sbms
mapper
MemberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sbms.mapper.MemberMapper">
<resultMap type="com.sbms.domain.MemberVO" id="memberMap">
<id property="userid" column="userid"/>
<result property="userid" column="userid"/>
<result property="userpw" column="userpw"/>
<result property="userName" column="username"/>
<result property="regDate" column="regdate"/>
<result property="updateDate" column="updatedate"/>
<collection property="authList" resultMap="authMap">
</collection>
</resultMap>
<resultMap type="com.sbms.domain.AuthVO" id="authMap">
<result property="userid" column="userid"/>
<result property="auth" column="auth"/>
</resultMap>
<select id="read" resultMap="memberMap">
SELECT
mem.userid, userpw, username, enabled, regdate, updatedate, auth
FROM
tbl_member mem LEFT OUTER JOIN tbl_member_auth auth on mem.userid = auth.userid
WHERE mem.userid = #{userid}
</select>
<!-- 등록 (회원) -->
<insert id="createMember">
insert into tbl_member (
USERID, USERPW, USERNAME, REGDATE, UPDATEDATE, ENABLED
)
values (
#{userid}, #{userpw}, #{userName}, SYSDATE, SYSDATE, '1'
)
</insert>
<!-- 등록 (회원 권한) -->
<insert id="createMemberAuth">
insert into TBL_MEMBER_AUTH (
USERID, AUTH
) values (
#{userid}, 'ROLE_USER'
)
</insert>
</mapper>
src/main/java
com.spms.security.domain
CustomUser.java
package com.spms.security.domain;
import java.util.Collection;
import java.util.stream.Collectors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import com.sbms.domain.MemberVO;
import lombok.Getter;
@Getter
public class CustomUser extends User {
private static final long serialVersionUID = 1L;
private MemberVO member;
public CustomUser(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public CustomUser(MemberVO member) {
super(member.getUserid(), member.getUserpw(), member.getAuthList().stream().map(auth -> new SimpleGrantedAuthority(auth.getAuth())).collect(Collectors.toList()));
this.member = member;
}
}
src/main/java
com.sbms.security
CustomAccessDeniedHandler.java
package com.sbms.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.log4j.Log4j;
@Log4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response, AccessDeniedException accessException)
throws IOException, ServletException {
log.error("Access Denied Handler");
log.error("Redirect....");
response.sendRedirect("/accessError");
}
}
src/main/java
com.sbms.security
CustomLoginSuccessHandler.java
package com.sbms.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.log4j.Log4j;
@Log4j
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
log.warn("Login Success");
List<String> roleNames = new ArrayList<>();
auth.getAuthorities().forEach(authority -> {
roleNames.add(authority.getAuthority());
});
log.warn("ROLE NAMES: " + roleNames);
if (roleNames.contains("ROLE_ADMIN")) {
response.sendRedirect("/sample/admin");
return;
}
if (roleNames.contains("ROLE_MEMBER")) {
response.sendRedirect("/sample/member");
return;
}
response.sendRedirect("/");
}
}
src/main/java
com.sbms.security
CustomNoOpPasswordEncoder.java
package com.sbms.security;
import org.springframework.security.crypto.password.PasswordEncoder;
import lombok.extern.log4j.Log4j;
@Log4j
public class CustomNoOpPasswordEncoder implements PasswordEncoder {
public String encode(CharSequence rawPassword) {
log.warn("before encode :" + rawPassword);
return rawPassword.toString();
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
log.warn("matches: " + rawPassword + ":" + encodedPassword);
return rawPassword.toString().equals(encodedPassword);
}
}
src/main/java
com.sbms.security
CustomUserDetailsService.java
package com.sbms.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.sbms.domain.MemberVO;
import com.sbms.mapper.MemberMapper;
import com.spms.security.domain.CustomUser;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j
public class CustomUserDetailsService implements UserDetailsService {
@Setter(onMethod_ = { @Autowired })
private MemberMapper memberMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
log.warn("Load User By UserName : " + userName);
MemberVO vo = memberMapper.read(userName);
log.warn("queried by member mapper: " + vo);
return vo == null ? null : new CustomUser(vo);
}
}
src/main/java
com.spms.config
SecurityConfig.java
package com.sbms.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 org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;
import com.sbms.security.CustomLoginSuccessHandler;
import com.sbms.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("sbms")
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(604800);
// UTF-8 캐릭터 인코딩 처리
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.addFilterBefore(filter, CsrfFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
repo.setDataSource(dataSource);
return repo;
}
}
src/main/java
com.spms.config
SecurityInitializer.java
package com.sbms.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
src/main/java
com.spms.config
WebConfig.java
package com.sbms.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() {
return new Class[] { ServletConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[] { characterEncodingFilter };
}
}
src/main/java
com.sbms.controller
CommonController.java
package com.sbms.controller;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class CommonController {
@GetMapping("/accessError")
public void accessDenied(Authentication auth, Model model) {
log.info("access Denied : " + auth);
model.addAttribute("msg", "Access Denied");
}
@GetMapping("/customLogin")
public void loginInput(String error, String logout, Model model) {
log.info("error: " + error);
log.info("logout: " + logout);
if (error != null) {
model.addAttribute("error", "Login Error Check Your Account");
}
if (logout != null) {
model.addAttribute("logout", "Logout!!");
}
}
@GetMapping("/customLogout")
public void logoutGET() {
log.info("custom logout");
}
@PostMapping("/customLogout")
public void logoutPost() {
log.info("post custom logout");
}
}
src/main/java
com.sbms.controller
SampleController.java
package com.sbms.controller;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.log4j.Log4j;
@Log4j
@RequestMapping("/sample/*")
@Controller
public class SampleController {
@GetMapping("/all")
public void doAll() {
log.info("do all can access everybody");
}
@GetMapping("/member")
public void doMember() {
log.info("logined member");
}
@GetMapping("/admin")
public void doAdmin() {
log.info("admin only");
}
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_MEMBER')")
@GetMapping
public void doMember2() {
log.info("logined annocation member");
}
@Secured({"ROLE_ADMIN"})
@GetMapping("/annoAdmin")
public void doAdmin2() {
log.info("admin annotation only");
}
}
권한 테이블 생성
create table TBL_MEMBER_AUTH (
USERID varchar2(50) not null,
AUTH varchar2(50) not null,
constraint FK_MEMBER_AUTH foreign key(USERID) references TBL_MEMBER(USERID)
);
테스트 데이터(회원/권한) 준비
1. testInsertMember() : 먼저 JUnit 실행
2. testInsertAuth() : 1번이 끝나면, JUnit 실행
src/test/java
com.spms.security
MemberTests.java
package com.spms.security;
import java.sql.Connection;
import java.sql.PreparedStatement;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.sbms.config.RootConfig;
import com.sbms.config.SecurityConfig;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RootConfig.class, SecurityConfig.class })
@Log4j
public class MemberTests {
@Setter(onMethod_ = @Autowired)
private PasswordEncoder pwencoder;
@Setter(onMethod_ = @Autowired)
private DataSource ds;
@Test
public void testInsertMember() {
String sql = "insert into tbl_member(userid, userpw, username) values (?,?,?)";
for (int i = 0; i < 100; i++) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ds.getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(2, pwencoder.encode("pw" + i));
if (i < 80) {
pstmt.setString(1, "user" + i);
pstmt.setString(3, "일반사용자" + i);
} else if (i < 90) {
pstmt.setString(1, "manager" + i);
pstmt.setString(3, "운영자" + i);
} else {
pstmt.setString(1, "admin" + i);
pstmt.setString(3, "관리자" + i);
}
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (Exception e) {
}
}
if (con != null) {
try {
con.close();
} catch (Exception e) {
}
}
}
} // end for
}
//@Test
public void testInsertAuth() {
String sql = "insert into tbl_member_auth (userid, auth) values (?,?)";
for (int i = 0; i < 100; i++) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ds.getConnection();
pstmt = con.prepareStatement(sql);
if (i < 80) {
pstmt.setString(1, "user" + i);
pstmt.setString(2, "ROLE_USER");
} else if (i < 90) {
pstmt.setString(1, "manager" + i);
pstmt.setString(2, "ROLE_MEMBER");
} else {
pstmt.setString(1, "admin" + i);
pstmt.setString(2, "ROLE_ADMIN");
}
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (Exception e) {
}
}
if (con != null) {
try {
con.close();
} catch (Exception e) {
}
}
}
} // end for
}
}
views/includes
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Simple Book Manager System</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="/sbms/resources/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript" src="/sbms/resources/js/book-info.js"></script>
<link href="/sbms/resources/vendor/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href="/sbms/resources/vendor/metisMenu/metisMenu.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<a class="navbar-brand" href="/sbms/book/list">SBMS</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor01">
<ul class="navbar-nav mr-auto">
<li class="nav-item active"><a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a></li>
<li class="nav-item"><a class="nav-link" href="/sbms/book/list">BookManagement</a></li>
<li class="nav-item">
<a class="nav-link" href="/sbms/customLogout">Logout</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/sbms/customLogin">Login</a>
</li>
<li class="nav-item"><a class="nav-link" href="#">About</a></li>
</ul>
</div>
</nav>
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"%>
<%@ include file="includes/header.jsp"%>
<div style="margin : 200px 600px 100px 600px;">
<form role="form" method='post' action="/sbms/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>
<a href="/customSignup" class="btn btn-lg btn-primary btn-block">Sign Up</a>
</fieldset>
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form>
</div>
<!-- jQuery -->
<script src="/sbms/resources/vendor/jquery/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="/sbms/resources/vendor/bootstrap/js/bootstrap.min.js"></script>
<!-- Metis Menu Plugin JavaScript -->
<script src="/sbms/resources/vendor/metisMenu/metisMenu.min.js"></script>
<!-- Custom Theme JavaScript -->
<script src="/sbms/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>
로그인 화면 : http://localhost/sbms/customLogin
'Project C (SBMS) > Project C 파트3' 카테고리의 다른 글
[C-3-5] 게시물 작성 시 스프링 시큐리티 처리 (0) | 2020.01.22 |
---|---|
[C-3-4] 로그아웃 (0) | 2020.01.21 |
[C-3-3] 회원가입 (0) | 2020.01.21 |
[C-3-2] 스프링 웹 시큐리티를 이용한 로그인 처리 2 (0) | 2020.01.21 |