일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- ibatis
- 반복문
- 자바
- Homebrew
- nodejs
- 대덕인재개발원
- spring
- Java
- servlet
- python
- 자바문제
- FastAPI
- 생활코딩
- 객체지향
- jsp
- pyqt
- 맥
- 단축키
- crud
- Error
- Mac
- 컬렉션프레임워크
- 이클립스
- html
- Oracle
- 배열
- API
- Android
- JDBC
- ddit
- Today
- Total
romworld
Spring 24 - security (테이블을 생성해 실습해보자 DB사용), LEFT OUTER JOIN, 구글 카멜변환, 로그인, 로그아웃 본문
Spring 24 - security (테이블을 생성해 실습해보자 DB사용), LEFT OUTER JOIN, 구글 카멜변환, 로그인, 로그아웃
inderrom 2023. 2. 14. 17:19데이터 베이스 생성
- COMMENT로 설명을 꼭 설정해주는 게 좋다! 필수!
- AUTHORITIES에서 기본키를 복합키로 해주는 이유는 한 사용자당 권한을 여러개 줄 수 있기 때문
--사용자
CREATE TABLE USERS(
USERNAME VARCHAR2(50),
PASSWORD VARCHAR2(300),
ENABLED VARCHAR2(1),
CONSTRAINT PK_USERS PRIMARY KEY(USERNAME)
);
COMMENT ON TABLE USERS IS '사용자';
COMMENT ON COLUMN USERS.USERNAME IS '아이디';
COMMENT ON COLUMN USERS.PASSWORD IS '비밀번호';
COMMENT ON COLUMN USERS.ENABLED IS '사용여부';
CREATE TABLE AUTHORITIES(
USERNAME VARCHAR2(50),
AUTHORITY VARCHAR2(300),
CONSTRAINT PK_AUTHORITIES PRIMARY KEY(USERNAME, AUTHORITY),
CONSTRAINT FK_AUTHORITIES FOREIGN KEY(USERNAME) REFERENCES USERS(USERNAME)
);
- ENABLED 컬럼 기본값 1로 설정
USER테이블 INSERT
AUTHORITIES 테이블 INSERT
- ROLE_(언더바 있음)
- admin은 member와 admin 권한이 있다.
<security-context.xml>
- 기존에는 사용자 정의로 설정해줬다면
- 이제는 DB를 사용해보자
- <security:jdbc-user-service data-source-ref="dataSource"/>
- ref에 root-context에서 설정한 데이터 소스를 적어준다.
- <security:password-encoder ref="customPasswordEncoder" />
- - 암호화를 안 쓰겠다는 의미
- <bean id="customPasswordEncoder"></bean>
- 자바빈 객체 등록
<!-- 사용자가 정의한 비밀번호 암호화 처리기를 빈으로 등록함 -->
<bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></bean>
<security:authentication-manager>
<!-- 지정된 아이디와 패스워드로 로그인이 가능하도록 설정 -->
<security:authentication-provider>
<!-- DB를 사용하겠다는 의미. 데이터 소스(root-context.xml)를 지정. -->
<security:jdbc-user-service data-source-ref="dataSource"/>
<!-- 사용자가 정의한 비밀번호 암호화 처리기를 지정(암호화를 안쓰겠다는 의미) -->
<security:password-encoder ref="customPasswordEncoder"/>
<!-- <security:user-service> -->
<!-- 메모리상에 아이디와 패스워드를 지정하고 로그인을 처리함
스프링 시큐리티 5버전부터는 패스워드 암호화 처리기를 반드시 이용해야 함
암호화 처리기를 사용하지 않도록 noop 문자열을 비밀번호 앞에 사용함
-->
<!-- <security:user name="member" password="{noop}1234" authorities="ROLE_MEMBER"/> -->
<!-- <security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/> -->
<!-- </security:user-service> -->
</security:authentication-provider>
</security:authentication-manager>
<CustomNoPasswordEncoder.java>
- security 패키지에 클래스 생성
package kr.or.ddit.security;
import org.springframework.security.crypto.password.PasswordEncoder;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomNoOpPasswordEncoder implements PasswordEncoder {
/*
스프링 시큐리티 5부터 기본적으로 PasswordEncoder를 지정해야 함
제대로 하려면 USERS 테이블의 PASSWORD 컬럼에 들어갈 비밀번호를 암호화하여 저장해야 함
테스트를 위해서 생성한 데이터는 암호화를 처리하지 않았으므로 아무 처리를 하지 않고 로그인하면 당연히
로그인 오류가 발생할 것임
그래서, 암호화를 하지 않는 PasswordEncoder를 직접 구현하여 지정하면
로그인 시 암호화를 고려하지 않으므로 로그인이 잘 됨
*/
@Override
public String encode(CharSequence rawPassword) {
log.warn("before encode : " + rawPassword);
// 인코딩 프로세스가 없음. 인코딩은 안하겠다라는 의미
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
log.warn("matches : " + rawPassword + " : " + encodedPassword);
return rawPassword.toString().equals(encodedPassword);
}
}
<security:intercept-url pattern="/board/list" access="permitAll" /> <security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" /> <security:intercept-url pattern="/notice/list" access="permitAll" /> <security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
url를 입력해서 주소를 들어가면 권한을 준 사용자만 접근 가능하다.
MEM 테이블 생성
CREATE TABLE MEM(
USER_NO NUMBER,
USER_ID VARCHAR2(50),
USER_PW VARCHAR2(300),
USER_NAME VARCHAR2(300),
COIN NUMBER,
REG_DATE DATE,
UPD_DATE DATE,
ENABLED VARCHAR2(1),
CONSTRAINT PK_MEM PRIMARY KEY(USER_NO)
);
- COMMENTS 추가
- 기본값 설정: UPD_DATE : SYSDATE , ENABLED : '1'
- 컬럼에 _ (언더바) 두 단어를 이어준 것
MEM 테이블 INSERT
- COIN까지 값을 넣어주고 나머지 컬럼들은 NULL
MEM_AUTH 테이블 생성
CREATE TABLE MEM_AUTH(
USER_NO NUMBER,
AUTH VARCHAR2(50),
CONSTRAINT PK_MEM_AUTH PRIMARY KEY(USER_NO, AUTH),
CONSTRAINT FK_MEM_AUTH FOREIGN KEY (USER_NO) REFERENCES MEM(USER_NO)
);
MEM_AUTH 테이블 INSERT
<security-context.xml>
암호화를 처리기를 사용하는 경우
- <security:password-encoder ref="bcryptPasswordEncoder"/>
- 자바빈 객체 등록
<!-- 스프링 시큐리티에서 제공하는 BCrypPasswordEncoder 클래스를 자바빈으로 등록함 -->
<bean id="bcryptPasswordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
** 다만 지금은 사용하지 않을 거라 다시 주석처리 해줌
- <!-- <security:jdbc-user-service data-source-ref="dataSource"/> -->
** <!-- 지정된 아이디와 패스워드로 로그인이 가능하도록 설정
authentication-provider : 인증(로그인) 제공자
-->
<security:authentication-provider user-service-ref="customUserDetailsService">
<!-- 사용자정의 사용자 상세 기능 -->
<bean id="customUserDetailsService" class="kr.or.ddit.security.CustomUserDetailsService"></bean>
<?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">
<!-- customAccessDenied를 자바빈(객체)으로 등록함 -->
<bean id = "customAccessDenied"
class="kr.or.ddit.security.CustomAccessDeniedHandler"></bean>
<bean id="customLoginSuccess"
class="kr.or.ddit.security.CustomLoginSuccessHandler"></bean>
<!-- 사용자가 정의한 비밀번호 암호화 처리기를 빈으로 등록함 -->
<bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></bean>
<!-- 사용자정의 사용자 상세 기능 -->
<bean id="customUserDetailsService" class="kr.or.ddit.security.CustomUserDetailsService"></bean>
<!-- 스프링 시큐리티에서 제공하는 BCrypPasswordEncoder 클래스를 자바빈으로 등록함 -->
<!-- <bean id="bcryptPasswordEncoder" -->
<!-- class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean> -->
<!-- xmlns : xml namespace의 약자
Role : 권한(authorization)
permitAll : 누구나 접근 가능 / hasRole : 권한을 갖은자인가?
-->
<security:http>
<!-- URI 패턴으로 접근 제한을 설정 -->
<security:intercept-url pattern="/board/list" access="permitAll" />
<security:intercept-url pattern="/board/register"
access="hasRole('ROLE_MEMBER')" />
<security:intercept-url pattern="/notice/list" access="permitAll" />
<security:intercept-url pattern="/notice/register"
access="hasRole('ROLE_ADMIN')" />
<!-- 폼 기반 인증 기능을 사용 -->
<!-- 접근 제한에 걸리면 시큐리티가 기본적으로 제공하는 로그인 페이지로 이동 -->
<!-- <security:form-login/> -->
<!-- 사용자가 정의한 로그인 페이지의 URI를 지정함 -->
<!-- customLoignSuccess를 인증(로그인) 성공 처리자로 지정함 -->
<security:form-login login-page="/login"
authentication-success-handler-ref="customLoginSuccess"
/>
<!--
로그인이 된 회원중에 권한이 없을 때..
접근access 거부 denided 처리자 handler 의 URI를 지정 -->
<!-- <security:access-denied-handler error-page="/accessError" /> -->
<!-- 등록한 CustomAccessDeniedHandler를 접근 거부 처리자로 지정함
customAccessDenied 객체를 reference(참조-바라본다)함-->
<security:access-denied-handler ref="customAccessDenied"/>
</security:http>
<!-- authentication : 인증(로그인)
1) 회원 게시판(Board)
가) 목록("/board/list") : 모두가 접근 가능
나) 등록 ("/board/register"): 로그인한 회원만 접근 가능
2) 공지사항 게시판(Notice)
가) 목록 ("/notice/list"): 모두가 접근 가능
나) 등록 ("/notice/register"): 로그인한 관리자만 접근 가능
1 회원(USERS)테이블 : USERNAME, PASSWORD, ENABLED
N 권한(AUTH)테이블 : USERNAME, AUTHORIZE
-->
<security:authentication-manager>
<!-- 지정된 아이디와 패스워드로 로그인이 가능하도록 설정
authentication-provider : 인증(로그인) 제공자
-->
<security:authentication-provider user-service-ref="customUserDetailsService">
<!-- DB를 사용하겠다는 의미. 데이터 소스(root-context.xml)를 지정. -->
<!-- <security:jdbc-user-service data-source-ref="dataSource"/> -->
<!-- 사용자가 정의한 비밀번호 암호화 처리기를 지정(암호화를 안쓰겠다는 의미) -->
<security:password-encoder ref="customPasswordEncoder"/>
<!-- BCryptPasswordEncoder 비밀번호 암호화 처리기를 사용하겠다는 의미 -->
<!-- <security:password-encoder ref="bcryptPasswordEncoder"/> -->
<!-- <security:user-service> -->
<!-- 메모리상에 아이디와 패스워드를 지정하고 로그인을 처리함
스프링 시큐리티 5버전부터는 패스워드 암호화 처리기를 반드시 이용해야 함
암호화 처리기를 사용하지 않도록 noop 문자열을 비밀번호 앞에 사용함
-->
<!-- <security:user name="member" password="{noop}1234" authorities="ROLE_MEMBER"/> -->
<!-- <security:user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/> -->
<!-- </security:user-service> -->
</security:authentication-provider>
</security:authentication-manager>
</beans>
결국 나중에는 주석처리한 부분을 빼고 사용한다.
LEFT OUTER JOIN ( 쿼리문 작성 )
- mem과 mem_auth 테이블에서 mem을 추가했을 때 일반 내부 조인을 쓴다면
- 원하는 값이 출력이 되지 않는다
- mem_auth 테이블 데이터에 권한이 없기 때문에
- 그래서 LEFT OUTER JOIN으로 묶어줘야지 원하는 값을 출력할 수 있다.
SELECT A.USER_NO, A.USER_ID, A.USER_PW, A.USER_NAME, A.COIN, A.REG_DATE, A.UPD_DATE, A.ENABLED
, B.USER_NO, B.AUTH
FROM MEM A LEFT OUTER JOIN MEM_AUTH B
ON(A.USER_NO = B.USER_NO)
WHERE A.USER_ID = 'member1';
--> mem 테이블의 member1에 admin 권한을 추가해주자!
-->mem_auth 테이블에 member1에 admin 추가
<emp_SQL.xml>
- 편의상 전에 작성해 두었던 매퍼 이용
<resultMap type="memVO" id="memMap">
<result property="userNo" column="USER_NO" />
<result property="userId" column="USER_ID" />
<result property="userPw" column="USER_PW" />
<result property="userName" column="USER_NAME" />
<result property="coin" column="COIN" />
<result property="regDate" column="REG_DATE" />
<result property="updDate" column="UPD_DATE" />
<result property="enabled" column="ENABLED" />
<collection property="memAuthVOList" resultMap="memAuthMap"></collection>
</resultMap>
<resultMap type="memAuthVO" id="memAuthMap">
<result property="userNo" column="USER_NO" />
<result property="auth" column="AUTH" />
</resultMap>
<!-- 회원 로그인 -->
<select id="memLogin" parameterType="memVO" resultMap="memMap">
SELECT A.USER_NO, A.USER_ID, A.USER_PW, A.USER_NAME, A.COIN, A.REG_DATE, A.UPD_DATE, A.ENABLED
, B.USER_NO, B.AUTH
FROM MEM A LEFT OUTER JOIN MEM_AUTH B
ON(A.USER_NO = B.USER_NO)
WHERE A.USER_ID = #{userId}
</select>
<EmpMapper.java>
// 회원 로그인
// <select id="memLogin" parameterType="memVO" resultMap="memMap">
public MemVO memLogin(MemVO memVO);
<EmpServiceImple.java>
// 회원 로그인
@Override
public MemVO memLogin(MemVO memVO) {
return this.empMapper.memLogin(memVO);
}
<EmpService.java>
// 회원로그인
public MemVO memLogin(MemVO memVO);
<mybatisAlias.xml> 추가
<typeAlias type="kr.or.ddit.vo.MemVO" alias="memVO" />
<typeAlias type="kr.or.ddit.vo.MemAuthVO" alias="memAuthVO" />
구글 카멜변환(VO에 들어갈 변수명 카멜표기법으로 출력)
-- 구글카멜변환
SELECT COLUMN_NAME
, DATA_TYPE
, CASE WHEN DATA_TYPE='NUMBER' THEN 'private int ' || FN_GETCAMEL(COLUMN_NAME) || ';'
WHEN DATA_TYPE IN('VARCHAR2','CHAR') THEN 'private String ' || FN_GETCAMEL(COLUMN_NAME) || ';'
WHEN DATA_TYPE='DATE' THEN 'private Date ' || FN_GETCAMEL(COLUMN_NAME) || ';'
ELSE 'private String ' || FN_GETCAMEL(COLUMN_NAME) || ';'
END AS CAMEL_CASE
, '' RESULTMAP
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME = 'MEM';
<MemVO.java>
vo패키지에 생성
- 1 : N 관계 추가
package kr.or.ddit.vo;
import java.util.Date;
import java.util.List;
import lombok.Data;
//자바빈 클래스
@Data
public class MemVO {
private String enabled;
private int userNo;
private String userId;
private String userPw;
private String userName;
private int coin;
private Date regDate;
private Date updDate;
//MemVO : MemAuthVO = 1 : N
private List<MemAuthVO> memAuthVOList;
}
<MamAuthVO.java>
package kr.or.ddit.vo;
import lombok.Data;
//자바빈 클래스
@Data
public class MemAuthVO {
private int userNo;
private String auth;
}
***<CustomUserDetailsService.java> ****
- security 패키지 안에 클래스 생성
- CustomUser 클래스를 만들어주자
package kr.or.ddit.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.or.ddit.mapper.EmpMapper;
import kr.or.ddit.vo.MemVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {
//DI(의존성 주입)
@Autowired
EmpMapper empMapper;
//요청파라미터 : <input type="text" name="username"...
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 파라미터 준비(public MemVO memLogin(MemVO memVO))
MemVO memVO = new MemVO();
//WHERE A.USER_ID = #{userId}
memVO.setUserId(username);
//<resultMap type="memVO" id="memMap">
memVO = empMapper.memLogin(memVO);
log.warn("EmpMapper에 의해 쿼리를 실행할 것임 : " + memVO );
//삼항 연산자. memVO가 null이면 null을 리턴하고, null이 아니면 USER를 리턴
return memVO==null?null:new CustomUser(memVO);
}
}
<CustomUser.java>
- security 패키지에 클래스 생성
- 생성자, getter/setter
package kr.or.ddit.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
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.or.ddit.vo.MemAuthVO;
import kr.or.ddit.vo.MemVO;
//User : 스프링 시큐리티가 제공하고 있는 사용자 정보 클래스
public class CustomUser extends User {
//이 memVO 객체는 JSP에서 사용할 수 있음
private MemVO memVO;
public CustomUser(String username, String password,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
//생성자
//memVO : select한 사용자 및 권한 정보가 들어있음
//MemVO 타입의 객체 memVO를 스프링 시큐리티에서 제공해주고 있는 UsersDetails 타입으로 변환
//회원 정보를 주면 스프링이 관리
public CustomUser(MemVO memVO) {
//1) username / 2) password / 3) authorities
//memVO.getMemAuthVOList() :
// 2 ROLE_ADMIN
// 2 ROLE_MEMBER
//memVO.getMemAuthVOList().stream() : (stream -> 나란히 출력)
// 2 ROLE_ADMIN 2 ROLE_MEMBER
/*
* 이 방법을 써도 되고
memVO.getMemAuthVOList().stream()
.map(auth->new SimpleGrantedAuthority(auth.getAuth()))
.collect(Collectors.toList())
*/
super(memVO.getUserId(), memVO.getUserPw(),
getCollect(memVO));
this.memVO = memVO;
}
// 이 방법을 써도 됨 . 이해하기 위해
public static List<SimpleGrantedAuthority> getCollect(MemVO memVO){
List<SimpleGrantedAuthority> authorities
= new ArrayList<SimpleGrantedAuthority>();
/*
2 ROLE_ADMIN
2 ROLE_MEMBER
*/
List<MemAuthVO> memAuthVOList = memVO.getMemAuthVOList();
//memAuthVO : 2 ROLE_ADMIN
for(MemAuthVO memAuthVO : memAuthVOList) {
//memAuthVO.getAuth() : ROLE_ADMIN
SimpleGrantedAuthority authority =
new SimpleGrantedAuthority(memAuthVO.getAuth());
authorities.add(authority);
}
return authorities;
}
public MemVO getMemVO() {
return memVO;
}
public void setMemVO(MemVO memVO) {
this.memVO = memVO;
}
}
- localhost/notice/register
- member1 , 1234 입력하면 admin으로 접근 가능
연습이므로 비밀번호 암호를 테이블 생성시 해주지 않는다.
원래면 이런 식으로 설정을 해줘야함.
타일즈의 <header.jsp> 수정
- 로그인 했을 때와 안 했을 때 권한 주는 것
- <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> 추가
- <sec:authorize access="isAuthenticated()">
- <sec:authorize access="isAnnoymous()">
- <sec:authentication property="principal.memVO" var="memVO" />
- 이미지 바꾸기
- 로그아웃
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
<!-- Sidebar Toggle (Topbar) -->
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
<i class="fa fa-bars"></i>
</button>
<!-- Topbar Search -->
<form
class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
<div class="input-group">
<input type="text" class="form-control bg-light border-0 small" placeholder="Search for..."
aria-label="Search" aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-primary" type="button">
<i class="fas fa-search fa-sm"></i>
</button>
</div>
</div>
</form>
<!-- Topbar Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Nav Item - Search Dropdown (Visible Only XS) -->
<li class="nav-item dropdown no-arrow d-sm-none">
<a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-search fa-fw"></i>
</a>
<!-- Dropdown - Messages -->
<div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in"
aria-labelledby="searchDropdown">
<form class="form-inline mr-auto w-100 navbar-search">
<div class="input-group">
<input type="text" class="form-control bg-light border-0 small"
placeholder="Search for..." aria-label="Search"
aria-describedby="basic-addon2">
<div class="input-group-append">
<button class="btn btn-primary" type="button">
<i class="fas fa-search fa-sm"></i>
</button>
</div>
</div>
</form>
</div>
</li>
<!-- Nav Item - Alerts -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-bell fa-fw"></i>
<!-- Counter - Alerts -->
<span class="badge badge-danger badge-counter">3+</span>
</a>
<!-- Dropdown - Alerts -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="alertsDropdown">
<h6 class="dropdown-header">
Alerts Center
</h6>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-primary">
<i class="fas fa-file-alt text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 12, 2019</div>
<span class="font-weight-bold">A new monthly report is ready to download!</span>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-success">
<i class="fas fa-donate text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 7, 2019</div>
$290.29 has been deposited into your account!
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="mr-3">
<div class="icon-circle bg-warning">
<i class="fas fa-exclamation-triangle text-white"></i>
</div>
</div>
<div>
<div class="small text-gray-500">December 2, 2019</div>
Spending Alert: We've noticed unusually high spending for your account.
</div>
</a>
<a class="dropdown-item text-center small text-gray-500" href="#">Show All Alerts</a>
</div>
</li>
<!-- Nav Item - Messages -->
<li class="nav-item dropdown no-arrow mx-1">
<a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-envelope fa-fw"></i>
<!-- Counter - Messages -->
<span class="badge badge-danger badge-counter">7</span>
</a>
<!-- Dropdown - Messages -->
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="messagesDropdown">
<h6 class="dropdown-header">
Message Center
</h6>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="/resources/sbadmin2/img/undraw_profile_1.svg"
alt="...">
<div class="status-indicator bg-success"></div>
</div>
<div class="font-weight-bold">
<div class="text-truncate">Hi there! I am wondering if you can help me with a
problem I've been having.</div>
<div class="small text-gray-500">Emily Fowler · 58m</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="/resources/sbadmin2/img/undraw_profile_2.svg"
alt="...">
<div class="status-indicator"></div>
</div>
<div>
<div class="text-truncate">I have the photos that you ordered last month, how
would you like them sent to you?</div>
<div class="small text-gray-500">Jae Chun · 1d</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="/resources/sbadmin2/img/undraw_profile_3.svg"
alt="...">
<div class="status-indicator bg-warning"></div>
</div>
<div>
<div class="text-truncate">Last month's report looks great, I am very happy with
the progress so far, keep up the good work!</div>
<div class="small text-gray-500">Morgan Alvarez · 2d</div>
</div>
</a>
<a class="dropdown-item d-flex align-items-center" href="#">
<div class="dropdown-list-image mr-3">
<img class="rounded-circle" src="https://source.unsplash.com/Mv9hjnEUHR4/60x60"
alt="...">
<div class="status-indicator bg-success"></div>
</div>
<div>
<div class="text-truncate">Am I a good boy? The reason I ask is because someone
told me that people say this to all dogs, even if they aren't good...</div>
<div class="small text-gray-500">Chicken the Dog · 2w</div>
</div>
</a>
<a class="dropdown-item text-center small text-gray-500" href="#">Read More Messages</a>
</div>
</li>
<div class="topbar-divider d-none d-sm-block"></div>
<!-- 스프링 시큐리티 표현식
인증 및 권한 정보에 따라 화면을 동적으로 구성할 수 있고, 로그인 한 사용자 정보를 보여줄 수도 있음
- hasRole("ROLE_MEMBER") : ROLE_MEMBER 권한이 있으면 true
- hasAnyRole("ROLE_MEMBER","ROLE_ADMIN") : 여러 권한 중 하나라도 해당하는 권한이 있으면 true
- principal : 인증된 사용자의 사용자 정보(UserDetails 인터페이스를 구현한 클래스(customUser))의 객체를 의미
- authentication : 인증된 사용자의 인증 정보
- permitAll : 모든 사용자에게 허용
- denyAll : 모든 사용자를 거부
- isAnonymous() : 로그인 하지 않은 경우 해당
- isAuthenticated() : 로그인 한 경우 true
- isFullyAuthenticated() : Remember-me(로그인 저장)로 인증된 것이 아닌 일반적인 방법으로 인증된 사용자의 경우 true
-->
<!-------------------------- 로그인 했을 때 시작 -------------------------->
<sec:authorize access="isAuthenticated()">
<!-- CustomUser.java에서 private MemVO memVO 멤버변수를 principal객체를 통해 사용 가능 -->
<sec:authentication property="principal.memVO" var="memVO" />
<!-- 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">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">${memVO.userName}</span>
<img class="img-profile rounded-circle"
src="/resources/sbadmin2/img/타일러더크리에이터.jpg">
</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>
<a class="dropdown-item" href="/logout" data-toggle="modal" data-target="#logoutModal">
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
Logout
</a>
</div>
</li>
</sec:authorize>
<!-------------------------- 로그인 했을 때 끝 -------------------------->
<!-------------------------- 로그인 안되었을 때 시작 -------------------------->
<sec:authorize access="isAnonymous()">
<li class="nav-item dropdown no-arrow">
<a class="nav-link dropdown-toggle" href="/login" id="userDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">로그인 해주세요.</span>
<img class="img-profile rounded-circle"
src="/resources/sbadmin2/img/프랭크.jpg">
</a>
</li>
</sec:authorize>
<!-------------------------- 로그인 안되었을 때 끝 -------------------------->
</ul>
</nav>
<security-context.xml>
- 로그아웃 처리
<!--CustomLogoutSuccessHandler(사용자정의 로그아웃 성공 처리자) -->
<bean id="customLogoutSuccessHandler" class="kr.or.ddit.security.CustomLogoutSuccessHandler"></bean>
<!-- 로그아웃 처리를 위한 URI를 지정하고, 로그아웃한 후에 세션을 무효화 함
customLogoutSuccessHandler(사용자정의 로그아웃 성공 처리자) -->
<security:logout logout-url="/logout" invalidate-session="true"
success-handler-ref="customLogoutSuccessHandler" />
타일즈<index.jsp>에서 로그아웃 부분 Modal 수정
- form 태그
- <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<!DOCTYPE html>
<html class>
<head>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<title>SB Admin 2 - Dashboard</title>
<!-- Custom fonts for this template-->
<link href="/resources/sbadmin2/vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
<link
href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
rel="stylesheet">
<!-- Custom styles for this template-->
<link href="/resources/sbadmin2/css/sb-admin-2.min.css" rel="stylesheet">
</head>
<body id="page-top class="sidebar-mini sidebar-closed sidebar-collapse"">
<!-- Page Wrapper -->
<div id="wrapper">
<!-- Sidebar /////////// aside 시작 ///////////
/WEB-INF/views/tiles/aside.jsp를 include
-->
<tiles:insertAttribute name="aside" />
<!-- End of Sidebar /////////// aside 끝 ///////////-->
<!-- Content Wrapper -->
<div id="content-wrapper" class="d-flex flex-column">
<!-- Main Content -->
<div id="content">
<!-- Topbar /////////// header 시작 ///////////
/WEB-INF/views/tiles/header.jsp를 include
-->
<tiles:insertAttribute name="header" />
<!-- End of Topbar /////////// header 끝 /////////// -->
<!-- Begin Page Content /////////// body 시작 /////////// -->
<div class="container-fluid">
<tiles:insertAttribute name="body" />
</div>
<!-- /.container-fluid /////////// body 끝 /////////// -->
</div>
<!-- End of Main Content -->
<!-- Footer /////////// footer 시작 ///////////
/WEB-INF/views/tiles/footer.jsp를 include
-->
<tilse:insertAttribute name="footer" />
<!-- End of Footer /////////// footer 끝 ///////////-->
</div>
<!-- End of Content Wrapper -->
</div>
<!-- End of Page Wrapper -->
<!-- Scroll to Top Button-->
<a class="scroll-to-top rounded" href="#page-top">
<i class="fas fa-angle-up"></i>
</a>
<!-- Logout Modal-->
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">로그아웃 하시겠습니까?</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">세션을 종료하려면 아래의 로그아웃 버튼을 클릭해주세요</div>
<div class="modal-footer">
<button class="btn btn-secondary" type="button" data-dismiss="modal">취소</button>
<form action="/logout" method="post">
<button type="submit" class="btn btn-primary">로그아웃</button>
<sec:csrfInput />
</form>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript-->
<script src="/resources/sbadmin2/vendor/jquery/jquery.min.js"></script>
<script src="/resources/sbadmin2/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="/resources/sbadmin2/vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="/resources/sbadmin2/js/sb-admin-2.min.js"></script>
<!-- Page level plugins -->
<script src="/resources/sbadmin2/vendor/chart.js/Chart.min.js"></script>
<!-- Page level custom scripts -->
<script src="/resources/sbadmin2/js/demo/chart-area-demo.js"></script>
<script src="/resources/sbadmin2/js/demo/chart-pie-demo.js"></script>
</body>
</html>
<CustomLogoutSuccessHandler.java>
- security 패키지에 클래스 생성
package kr.or.ddit.security;
import java.io.IOException;
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.logout.LogoutSuccessHandler;
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
//auth : 로그인 정보
if(auth != null && auth.getDetails()!=null) {
try {
request.getSession().invalidate();
}catch(Exception e) {
e.printStackTrace();
}
}
response.setStatus(HttpServletResponse.SC_OK);
response.sendRedirect("/");
}
}
'Spring' 카테고리의 다른 글
Spring 27 - 전자정부프레임워크와 부트스트랩 사용해보자(adminlte3) (0) | 2023.02.15 |
---|---|
Spring 26 - security (애너테이션, PL/SQL&패키지를 통한 비밀번호 암호화/복호화 , 상관관계 서브쿼리) (0) | 2023.02.15 |
Spring 23 - security (+ ajax에 csrf값 설정) (0) | 2023.02.13 |
Spring 22 - 트랜잭션, 예외처리 (0) | 2023.02.10 |
Spring 21 - 구글차트 구현하기 (0) | 2023.02.10 |