D+61::스프링 시큐리티(springBoot_security)

Am.Vinch·2022년 9월 22일
0
post-thumbnail

20220922_thu

인증과 인가의 차이점

인증(Authentication)

: 웹사이트에 접근 가능여부(로그인)

인가(Authorization)

: 웹사이트에 접근은 가능하나, 권한의 여부(admin_관리자)


security

단, 실제로 프로젝트에서 시큐리티 기능 구현시, 로그인인증과정이 복잡하고 번거롭기 때문에 가장 마지막단계에서 구현하는 것이 좋다.

프로젝트명: SecurityTest

기본 세팅

  • 패키지명: security
  • 기능은 4가지만 우선 설정: lombok,spring tools, web, thymeleaf
  • application파일
    : 아래 코드 작성
    server.port= 8081
    spring.thymeleaf.cache= false
  • porm.xml
      1. 해당 파일 아무곳이나 커서를 두고 마우스 오른쪽 버튼 클릭하면 spring > Add starter 클릭한다.
      1. security 중 Spring Security 기능 추가 선택한다.
      1. 해당 파일이 열리는 것을 확인
    1. 해당 파일에서 좌측과 우측을 연결하는 지점을 선택하면 화살표가 뜨는데 이를 클릭하여 우측으로 추가된 <dependency> 태그를 옮겨준다
    1. finish 클릭시, porm파일에 추가된 것을 확인할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>Kh.study</groupId>
	<artifactId>SecurityTest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SecurityTest</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

  • 주의사항
    ※ 주의 ※ porm파일을 건드릴 때는 다시 프로젝트를 런하고 페이지 실행해야 오류가 안날 수 있다.

  • 차이점

    • security 태그를 추가하기 전에는 기본경로를 주소창에 입력시 index페이지가 바로 뜨지만,
    • security 태그를 추가 후에는 똑같이 기본경로를 입력해도 로그인 창이 자동으로 뜨게된다.
  • 로그인: 아이디와 비밀번호
    : 아이디는 소문자로 user를 입력한다.
    : 프로젝트 run하면 콘솔창에 비밀번호가 뜬다.
    Using generated security password: 4c305450-0f60-4f06-9660-0d4adeb9f6e5

  • 로그인 하면 기본경로인 index.html 페이지가 뜬다.
    : 기본경로 localhost:8081/index


  • home화면은 로그인 인증을 안하고 들어가면, 접근이 거부가 되어 페이지 뜨지 않는다.
    security.authorizeHttpRequests().anyRequest().authenticated();


  • 로그인 실패할 때
    경로이동 http://localhost:8081/login?error

DB (oracle)

  • shop_실습.sql (SHOP_USER 계정)
drop TABLE SECURITY_MEMBER;
CREATE TABLE SECURITY_MEMBER(
    MEMBER_ID VARCHAR2(50) PRIMARY KEY
    , MEMBER_PW VARCHAR2(100) 
    , MEMBER_NAME VARCHAR2(50)
    , ROLE VARCHAR2(100) -- 관리자권한
);
INSERT INTO SECURITY_MEMBER VALUES('member','member123','일반회원','ROLE_MEMBER');
INSERT INTO SECURITY_MEMBER VALUES('manager','manager123','매니저회원','ROLE_MANAGER');
INSERT INTO SECURITY_MEMBER VALUES('admin','admin123','관리자회원','ROLE_ADMIN');
-- 삽입 후 항상 커밋!!

  • DB 연동 porm 파일.
    : CTRL + SPACE 바 + 마우스 오른쪽 클릭 > Add Starters..

    : db기능 선택하여 추가하기
  • 초록색 선을 모두 클릭하면 검은 색선으로 변경이되어 우측으로 이동한다.
  • porm.xml 파일 (db기능 추가)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>Kh.study</groupId>
	<artifactId>SecurityTest</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SecurityTest</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>
		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc8</artifactId>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>
  • 이전 프로젝트에서 log4jdbc,logback 파일 모두 복사하여 가져오기

SecurityTest 프로젝트 파일 생성


Config

Q.Config를 왜 사용하는가?
A. password를 안전하게 보관해야한다. 코드에 password를 써 두면 git push하는 순간 비밀번호가 외부에 노출된다. API secret key나 각종 access token들도 마찬가지로 겪는 문제이다. 이런 key를 담은 config file들은 암호화하여 push하는 등의 방법이 있기때문이다.

@Configuration

@EnableWebSecurity

@bean

csrf()


@Configuration

:해당클래스가 설정파일임을 스프링에게 인지시키는 어노테이션

@EnableWebSecurity

:해당 클래스로부터 만들어진 객체가 security 설정 파일임을 인지시켜주는 어노테이션

@bean

bean 객체 어노테이션

  • 매개변수로 들어온 httpSecurity 객체를 사용해서
  • 인증 및 인가에 대한 제어를 구성할 수 있다.
  • @Bean//(객체) : 자동으로 객체만들어주는 어노테이션
  • SecurityFilterChain securityFilterChain = ""; 필요할 때마다 자동으로 호출해준다.

csrf()

  • security.csrf().disable();
    :사이트 간의 요청 위조를 방지하는 시큐리티의 기본기능 비활성화해준다.
    (스프링 보안공격 방지)
    : 이를 하지 않으면, 스프링보안정책문제로 페이지 오류발생으로
  • config.SercurityConfig
    : 시큐리티를 사용하기위한
package kh.study.security.config;
@Configuration
@EnableWebSecurity
public class SecurityConfig   {
	

	//--------- @bean 만들기 전 예시-------------------------------------------------------//
	// @Bean// 객체만들어주세요
	//  String string = "java"; -> 자동으로 만들어준다. 
	// 메소드를 만들면, 스프링이 필요할때 알아서 호출해준다.
	// 단, 리턴되는 자료형에서 소문자로 만들어 만들어준다.String - > string
	//	public String aaa(String name) {
	//		return name;
	//	}
	//	public void bbb() {
	//	String name	= aaa("java");
	//	System.out.println(name);
	//	}
	//-------------------------------------------------------------------------------------//
	
	@Bean
	public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
		//-------------------------------------------------------------------------//
		// throws Exception :                                                      //
		// 해석) 해당 메소드 예외발생시 예외처리안하고 전가시킨다.                 //
		//-------------------------------------------------------------------------//
		
		//-------------------------------<방법 1_기본 >----------------------------//
		// ① security.authorizeRequests().antMatchers("/index").permitAll();      //
		// 해석) 로그인 없이도 누구에게나 허용하도록하겠다.                        //
		// authorizeHttpRequests() : 권한(인증,인가) 에 대한 설정을 시작하겠다.    //
		// antMatchers("/index") : /index 요청에 대한 설정을 하겠다.               //
		// permitAll() : 인증,인가 절차없이 누구에게나 허용하겠다.                 //
		//-------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// ② security.authorizeRequests().antMatchers("/member").authenticated(); //
		//  해석) /member 요청경로도 인증을 받아야만 접근가능하다.                 //  
		//-------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// ③ security.authorizeRequests().anyRequest().authenticated();           //
		// 해석) 모든 요청은 로그인(인증)을 하지 않으면 접근 거부한다.             //
		// anyRequest() : 어떤 요청이 들어와도                                     //
		// authenticated() : 인증(로그인)을 받아야 접근가능하다.                   //
		//-------------------------------------------------------------------------//
		//----------------------------------------------------------------------------//
		// ④ security.authorizeRequests().antMatchers("/manager").hasRole("MANAGER") //
		// ⑤ security.authorizeRequests().antMatchers("/admin").hasRole("ADMIN")     //
		// hasRole() :                                                                //
		// 해석)                                                                      //
		// /manager 라는 요청으로 MANAGER라는 인가가 있어야 한다.                     //
		// /admin 라는 요청으로 ADMIN라는 인가가 있어야 한다.                         //
		//----------------------------------------------------------------------------//
		//-------------------------------<방법 1_기본 >-------------------------------//

		//-------------------------------<방법 2_요약> -------------------------------//
		// 한줄로 연결하여 요약이 가능한 이유)
		// : 리턴타입이 동일하기 때문에 연결하여 사용 가능하다
		security.authorizeRequests()
				.antMatchers("/index").permitAll()
				.antMatchers("/login").permitAll()
				.antMatchers("/member").authenticated()
				.antMatchers("/manager").hasRole("MANAGER")
				.antMatchers("/admin").hasRole("ADMIN")
				//.anyRequest().authenticated()
				.and().formLogin().loginPage("/login")
									.defaultSuccessUrl("/index")
									.failureUrl("/loginFail");
		//----------------------------------------------------------------------------------------------//
        // 1) 인증이 되지 않았을 경우 스프링이 기본 제공하는 login 페이지로 이동										
		//							.and().formLogin();
		//----------------------------------------------------------------------------------------------//
		// 2) 인증이 되지 않았을 경우 /login 페이지로 이동
		// (주의사항) 단, 로그인 페이지를 가는데 전체허용 permitAll()을 하지않은채로 페이지이동하면,
		// 로그인하러가는 페이지이동을 하는데에도 로그인인증을 받아야하는게 무한반복 호출하여 오류가 발생한다.
		//							.and().formLogin().loginPage("/login");
		//----------------------------------------------------------------------------------------------//
		//-------------------------------------------------------------------------//
		// security.formLogin();                                                   //
		// 해석) 인증이 되지 않을 경우, 자동으로 로그인 페이지로 이동한다.         //     
		// 로그인 인증방법)                                                        //
		// 콘솔창 가장 마지막 비밀번호 값을 입력하고 아이디도 user입력 시 가능하다.//
		//-------------------------------------------------------------------------//
		//.defaultSuccessUrl("/index") : 로그인 성공시 이동경로 
		//.failureUrl("/loginFail"); : 로그인 실패시 이동경로
		//-------------------------------<방법 2_요약> -------------------------------------------------//
		
		//------------------ <  manager 와 admin 로그인 할 때, 비교 >-----------------------------------------------------------//
		// 공통점) 둘 다 페이지 이동시, 로그인 인증을 해야 페이지 창이 뜬다.
		// 차이점) admin 은 접근권한을 가진 인가와 인증 둘다 가능하고, manager는 인증만 가능하다.
		//         admin으로 로그인 후, manager 로그인 가능하지만,
		//         manager 로그인 후, admin 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
		//------------------------------------------------------------------------------------------------------------------//
		
		
		//---------------------------------------------------------------------------//
		security.exceptionHandling().accessDeniedPage("/accessDenied");
		// 위의 manager 페이지에서 admin 페이지 이동시,
		// 권한 문제로  접근거부 될 때 기본적으로 이동할 페이지를 지정 할 수 있다.
		// exceptionHandling().accessDeniedPage("/페이지명") 
		// : 페이지명으로 이동하면서 오류페이지가 아닌 접근페이지로 이동 가능하다.
		//---------------------------------------------------------------------------//
		
		//---------------------------------------------------------------------------//
		security.logout().invalidateHttpSession(true)
						 .logoutSuccessUrl("/index");
		// 로그아웃하면 세션에 저장된 데이터를 자동 삭제한다.
		// 로그아웃하면 자동으로 /index 페이지로 이동한다.
		//---------------------------------------------------------------------------//
		
		//---------------------------------------------------------------------------//
		security.csrf().disable();
		// 사이트 간의 요청 위조를 방지하는 시큐리티의 기본기능 비활성화해준다.
		// 스프링 보안공격 방지
		// : 이를 하지 않으면, 스프링보안정책문제로 페이지 오류발생으로 
		//---------------------------------------------------------------------------//
		
		return security.build();
	}
	
	
	
////////////// [ 인증 및 인가 테스트를 위한 계정 생성 메소드 ] ///////////////////////////////////////////
//	@Bean
//	public InMemoryUserDetailsManager userDetailService() {
//		List<UserDetails> userList = new ArrayList<>();
//		
//		//----------리스트 데이터 추가하기 : 방법 .add()-------------//
//		
//		userList.add(User.withUsername("manager")
//				.password("{noop}manager123")
//				.roles("MANAGER")
//				.build());
//		
//		userList.add(User.withUsername("admin")
//				.password("{noop}admin123")
//				.roles("ADMIN")
//				.build());
//		//------------------------------------------------------------//
//
//		//-------------------------------------------------------------------------//
//		// Q. manager 도 로그인 후 admin 페이지 접근가능하도록 하려면? ------------//
//		// "메소드 오버로딩" 사용하면 가능하다.
//		// 메소드명이 동일하면 중복되어 사용 불가능하지만,
//		// 매소드명이 다르거나 매개변수가 다르면 사용가능하다.
//		// roles(" "," ",...," ") : 무한으로 매개변수가 들어갈 수 있다.
//		//-------------------------------------------------------------------------//
//		// userList.add(User.withUsername("admin")
//		//		.password("{noop}admin123")
//		//		.roles("ADMIN","MANAGER")
//		//		.build());
//		//-------------------------------------------------------------------------//
//		
//		return new InMemoryUserDetailsManager(userList);
//}
		
		
		//------------------ 위의 manager 와 admin 로그인의 비교 ---------------------------------------------------------//
		// 공통점) 둘 다 페이지 이동시, 로그인 인증을 해야 페이지 창이 뜬다.
		// 차이점) admin 은 접근권한을 가진 인가와 인증 둘다 가능하고, manager는 인증만 가능하다.
		//         admin으로 로그인 후, manager 로그인 가능하지만,
		//         manager 로그인 후, admin 페이지로 이동시, 로그인은 가능하지만(인증) 접근권한이 없어서 오류페이지가 뜬다.
		//----------------------------------------------------------------------------------------------------------------//
	
	
	
}

Controller

  • SecurityController
package kh.study.security.controller;

@Controller
public class SecurityController {
	
	@GetMapping("/index")
	public String index() {
		return "index";
	}
	
	@GetMapping("/home")
	public String home() {
		return "home";
	}
	@GetMapping("/manager")
	public String manager() {
		return "manager";
	}
	@GetMapping("/admin")
	public String admin() {
		return "admin";
	}
	@GetMapping("/accessDenied")
	public String accessDenied() {
		return "accessDenied";
	}
	@GetMapping("/login")
	public String login() {
		return "login";
	}
	@GetMapping("/loginFail")
	public String loginFail(Model model) {
		//---------------------------------------------------------------------------//
		model.addAttribute("failMsg", "아이디 혹은 비밀번호가 잘못 입력되었습니다.");
		// login 페이지에, 뒤의 문자열로 failMsg값을 던져준다.
		// 로그인 실패시, div영역으로 failMsg값이 화면에 나타난다
		//---------------------------------------------------------------------------//
		return "login";
	}
}

Service/ServiceImpl

  • MemberService
package kh.study.security.service;
public interface MemberService {
	//로그인
	MemberVO login(String memberId);
}
  • MemberServiceImpl
package kh.study.security.service;

@Service("/memberService")
public class MemberServiceImpl implements MemberService {
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	//로그인
	@Override
	public MemberVO login(String memberId) {
		return sqlSession.selectOne("memberMapper.login",memberId);
	}
}

UserDetailsService

  • UserDetailsService
    : 로그인 메소드가 정의되어 있는 인터페이스
    (스프링에서 만들어준 서비스)
  • UserDetailServiceImpl
package kh.study.security.service;

@Service("userDetailsService")
public class UserDtailsServiceImpl implements UserDetailsService  {
	@Resource(name = "memberService")
	private MemberService memberService;
	//해당 메소드가 로그인 시 자동으로 실행된다.
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		//---------- 우리가 만든 로그인쿼리 실행 --------//
		MemberVO loginInfo = memberService.login(username);
		// UserDetails : 리턴값 자료형_인터페이스 
		return null;
	}
}

VO

  • MemberVO
package kh.study.security.vo;
@Setter
@Getter
@ToString
public class MemberVO {
	private String	memberId;
	private String	memberPw;
	private String	memberName;
	private String	role;
}

mappers

스프링 시큐리티 로그인 조회

  • 스프링 시큐리티 로그인 조회
    :스프링 시큐리티에서는 조건절에 비밀번호 사용하는 것을 허용하지 않는다!
    :단, 비밀번호도 같이 조회해야 한다.
    비밀번호가 조회가 되지만, 암호화 된채로 비밀번호 데이터를 가져온다
    그래서 암호화된 비밀번호를 디코딩(복호화) 한 뒤,
    조회된 비밀번호를 비교하여 맞으면 로그인을 시켜준다.
  • member-mapper
<?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="memberMapper">
	<resultMap type="kh.study.security.vo.MemberVO" id="member">
		<id column="MEMBER_ID" property="memberId"/>
		<result column="MEMBER_PW" property="memberPw"/>
		<result column="MEMBER_NAME" property="memberName"/>
		<result column="ROLE" property="role"/>
	</resultMap>

	<select id="login" resultMap="member">
		SELECT MEMBER_ID
			, MEMBER_NAME
			, ROLE
			, MEMBER_PW
		FROM SECURITY_MEMBER
		WHERE MEMBER_ID = #{memberId}
	</select>


</mapper>

templates(html)

  • index
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
index페이지입니다
</body>

</html>
  • home
  • 로그아웃 기능
    시큐리티를 활용해서 로그아웃을 진행하려면
    post방식으로 /logout 으로 요청을 보내주기만 하면된다.
    컨트롤러에 굳이 /logout 요청을 받는 post 방식메소드를 만들 필요가 없다.(자동생성)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>홈 화면</h1>
<form action="/logout" method="post">
	<input type="submit" value="로그아웃">
</form>
</body>
</html>
  • admin
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
admin 페이지입니다.
</body>

</html>
  • manager
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
manager 페이지입니다.
</body>

</html>
  • login

로그인 기능

  • [ 스프링 시큐리티 로그인 할 때 ]
    : form태그 name값 아이디/비밀번호 표기법
    아이디 : name="username"
    비밀번호 : name="password"
  • 시큐리티를 활용해서 로그인 진행하려면
    post방식으로 /login으로 요청을 보내주기만 하면된다.
    컨트롤러에 굳이 /login 요청을 받는 post방식 메소드를 만들 필요가 없다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="/login" method="post">
	id :<input type="text" name="username"><br>
	pw :<input type="password" name="password"><br>
	<div th:text="${failMsg}"></div>
	<input type="submit" value="로그인">
</form>
</body>
</html>
  • accessDenied
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
accessDenied
</body>
</html>
profile
Dev.Vinch

0개의 댓글