일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Oracle
- 파워서플라이
- ORA-01407
- for문
- 무결성 제약조건
- 전화번호부
- 오라클
- Ajax
- 백준문제
- spring
- 자바
- 이클립스
- 별 찍기
- 이클립스단축기
- 설정
- 백준문제풀이
- 인터페이스
- 순환문
- 깃허브 블로그
- 백준
- 오류모음
- 환경설정
- jsp
- while
- 티스토리 블로그
- ORA-02292
- 오류
- 공부
- MSI
- 스프링
- Today
- Total
danDevlog
Spring - 19 (스프링 시큐리티 - 2) 본문
home.jsp를 수정한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ include file="includes/header.jsp"%>
<script>
self.location = "/board/list";
</script>
<!-- 로그아웃 했을때 / 로 주소접근하게 된다.
hom.jsp로 넘어온다면, 다시 /board/list로 이동 -->
<%@ include file="includes/footer.jsp"%>
CommonController.java 새로운 매핑을 추가해준다.
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class CommonController {
@GetMapping("/customLogin")
public void loginInput(String error, String logout, Model model) {
if(error != null)
model.addAttribute("error","계정을 확인해 주세요");
if(logout != null)
model.addAttribute("logout","로그아웃");
}
@GetMapping("/customLogout")
public void logoutGet() {
log.info("custom logout");
}
}
header.jsp에 taglib을 추가해주고 기존 내용들을 수정 및 추가한다.
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<!-- Nav Item - User Information -->
<li class="nav-item dropdown no-arrow">
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<sec:authorize access="isAuthenticated()">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">
<sec:authentication property="principal.username"/>
</span>
<img class="img-profile rounded-circle"
src="/resources/img/undraw_profile.svg">
</sec:authorize>
<sec:authorize access="isAnonymous()">
<i class="fas fa-cogs fa-sm fa-fw mr-2 text-gray-400"></i>
</sec:authorize>
<span class="mr-2 d-done d-lg-inline text-gray-600 small"></span>
</a>
<!-- Dropdown - User Information -->
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="userDropdown">
<a class="dropdown-item" href="#">
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
Profile
</a>
<a class="dropdown-item" href="#">
<i class="fas fa-cogs fa-sm fa-fw mr-2 text-gray-400"></i>
Settings
</a>
<a class="dropdown-item" href="#">
<i class="fas fa-list fa-sm fa-fw mr-2 text-gray-400"></i>
Activity Log
</a>
<div class="dropdown-divider"></div>
<sec:authorize access="isAuthenticated()">
<a class="dropdown-item" href="/customLogout">
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
Logout
</a>
</sec:authorize>
<sec:authorize access="isAnonymous()">
<a class="dropdown-item" href="/customLogin">
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
Login
</a>
</sec:authorize>
</div>
</li>
회원정보가 담길 테이블과 자식테이블인 tbl_member_auth 테이블을 만들어준다.
create table tbl_member(
userid varchar2(50) not null primary key,
userpw varchar2(100) not null,
username varchar2(100) not null,
regdate date default sysdate,
updatedate date default sysdate,
enabled char(1) default '1');
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)
);
commit;
domain패키지에 MemberVO와 AuthVO 클래스를 생성한다.
package kr.icia.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;
// 하나의 아이디는 여러개의 권한 소유 가능.
}
package kr.icia.domain;
import lombok.Data;
@Data
public class AuthVO {
private String userid; // 사용자 아이디
private String auth; // 권한.
// 즉, 사용자별 권한 등록.
}
mapper패키지에 MemberMapper 인터페이스를 생성한다.
package kr.icia.mapper;
import kr.icia.domain.MemberVO;
public interface MemberMapper {
public MemberVO read(String userid);
// 사용자가 아이디를 입력하면, 그에 해당하는 계정 정보를 디비에서 추출
}
쿼리를 사용하기위해 MemberMapper와 매칭되는 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="kr.icia.mapper.MemberMapper">
<!-- resultType : 자동으로 설정된 리턴 타입. resultMap : 수동으로 설정된 리턴 타입. -->
<resultMap type="kr.icia.domain.AuthVO" id="authMap">
<result property="userid" column="userid" />
<result property="auth" column="auth" />
</resultMap>
<resultMap type="kr.icia.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"/>
</resultMap>
<!-- 회원 정보 테이블과 회원 권한 테이블을 조인하여 1개의 타입으로 회원 관련 정보를 리턴 -->
<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>
<!-- left outer join : 좌 테이블, 우 테이블 이 있을때,
좌 테이블을 기준으로 레코드 추출. 좌 테이블은 모두 추출, 우 테이블은 일치하는 값들 추출.
로그인 창에서 입력한 사용자 계정을 넘겨 받아서 일치하는 데이터 검색. -->
</mapper>
kr.icia.security.domain 이라는 새로운 패키지의 경로에 CustomUser 클래스를 생성해준다.
package kr.icia.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 kr.icia.domain.MemberVO;
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);
// 상속을 받으면서 의무적으로 구현한 생성자.
// <? extends 클래스명> : 제너릭 타입의 상위 제한.
// <? super 클래스명> : 제너릭 타입의 하위 제한.
// <?> : 제너릭 타입 제한 없음.
}
public CustomUser(MemberVO vo) {
super(vo.getUserid(), vo.getUserpw()
, vo.getAuthList().stream()
.map(auth -> new SimpleGrantedAuthority(
auth.getAuth())).collect(Collectors.toList()));
this.member = vo;
// 사용자 아이디, 패스워드, 권한 목록으로 초기화.
}
// 사용자가 로그인 창에서 아이디와 패스워드를 입력하면,
// 해당 아이디를 가지고 일치하는 회원 정보를 찾기. (서비스 처리)
}
kr.co.icia.security라는 새로운 패키지 경로에 CustomUserDetailService 클래스를 생성한다.
package kr.co.icia.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 kr.icia.domain.MemberVO;
import kr.icia.mapper.MemberMapper;
import kr.icia.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);
// 전달된 id로 사용자 정보를 검색.
return vo == null ? null : new CustomUser(vo);
// 검색되지 않으면 널, 검색되면 해당 정보 리턴.
}
}
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">
<!-- 암호화 처리, 사용자 계정 정보 처리 -->
<bean id="bcryptPasswordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!-- BCrypt 암호화 객체 생성. -->
<bean id="customUserDetailService"
class="kr.co.icia.security.CustomUserDetailsService" />
<!-- 아이디 패스워드로 로그인 처리 객체 -->
<security:http> <!-- 웹 접근에 대해서, -->
<security:form-login login-page="/customLogin"/>
<security:logout logout-url="/customLogout"
invalidate-session="true"/>
</security:http>
<security:authentication-manager> <!-- 인증 처리 관리자 -->
<security:authentication-provider
user-service-ref="customUserDetailService"> <!-- 인증처리 공급자 -->
<security:password-encoder ref="bcryptPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
</beans>
테스트를 위하여 src/test/java 경로에 kr.icia.security 패키지를 만들고 MeberTests.java 클래스를 만들어준다.
package kr.icia.security;
import java.sql.Connection;
import java.sql.PreparedStatement;
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 kr.icia.mapper.MemberMapper;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
{"file:src/main/webapp/WEB-INF/spring/root-context.xml",
"file:src/main/webapp/WEB-INF/spring/security-context.xml"})
@Log4j
public class MemberTests {
@Setter(onMethod_ = @Autowired)
private PasswordEncoder pwencoder;
@Setter(onMethod_ = @Autowired)
private javax.sql.DataSource ds;
@Setter(onMethod_ = @Autowired)
private MemberMapper memberMapper;
@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) {}
}
}
}
}
}
여기서 글쓴이는 USERNAME을 UESRNAME으로 테이블을 만들때 오타를 내어,
ORA-00904 오류가 발생하여 오타를 찾을려고 2시간을 해매였다.
이후 해당 ID와 PW로 로그인페이지에서 로그인을 시도해도 처리되지않는다.
왜냐하면 아직 검증 및 권한을 주지 않았기 때문이다.
따라서 테스트코드를 추가해서 권한을 얻는다.
MemberTests 파일에 권한을 주는 코드를 추가한다.
@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) {}
}
}
}
}
권한까지 주었다면 이제 로그인을 해보자
테스트코드로 만들었던 아이디와 비밀번호로 로그인을 하면된다.
로그아웃도 잘 되었다.
'Spring 게시판 만들기' 카테고리의 다른 글
Spring - 20(스프링 시큐리티-3) (0) | 2022.05.08 |
---|---|
Spring - 18 (스프링 시큐리티) (0) | 2022.04.29 |
Spring - 17 (첨부파일-3) (0) | 2022.04.29 |
Spring - 16 (첨부파일 - 2) (0) | 2022.04.29 |
Spring - 15 (첨부파일) (0) | 2022.04.26 |