----

스프링 시큐리티 보안 공부

https://spring.io/guides/gs/securing-web/

 

Securing a Web Application

this guide is designed to get you productive as quickly as possible and using the latest Spring project releases and techniques as recommended by the Spring team

spring.io

------

세션

Security-Context (key) 밑에 방대한 값(value)들이 있다
그 중에
Authentication( 인증에 필요한 권한, 유효한 이용자인지 등 모든 필드가 정해져 있다 )
필드들이 정해져 있다는 것은 만들어서 넣어야 한다는 뜻
Authentication Manager의 도움을 받아서 Authentication을 만든다
Authentication의 필수 입력 값은 (username, password)
Authentication의 구성은 정해진 필드를 따라 만들어야 한다 ( User, UserDetails )
UserDetails ( 유저네임, 패스워드, 권한 )를 만들 때 컴포지션하거나
내 커스텀 유저정보 java 파일에 UserDetails을 extends 하면 된다
접근할 때 DI를 사용하면 된다

 

----------------

시큐리티

 

요약

1. 프로젝트 생성
2. Spring Security 디펜던시 추가
3. yml 파일 설정
4. DB 생성
5. 서버 가동 및 로그인 로그아웃 테스트 (비밀번호는 콘솔창의 해시값)
6. SecurityConfig파일 생성 및 @Configuration @EnableWebSecurity 추가
6-1. WebSecurityConfigurerAdapter 상속
6-2. @EnableGlobalMethodSecurity(prePostEnabled = true) 추가
6-3. configure(HttpSecurity http) 오버라이딩
6-4. http객체에 설정하기
6-5. 폼태그의 post 요청시 거부를 당하지 않기 위해 http.csrf().disable(); 추가

 

새 스프링부트 프로젝트 생성

 

 

파일명 그룹 패키지 설정

 

 

spring security / spring data JPA를 세팅해서 연습해본다

템플릿을 바로추가하려면 여기서 머스태치나 타임리프를 사용

 

 

참고사항 : com.jaybon.securityEx01 아래에 만들어야 컴포넌트 스캔이 된다

 

 

properties파일을 yml파일로 변경

 

 

포트 / 컨텍스트패스 / 캐릭터셋을 설정하고 enabled (인코딩 활성화) / force(인코딩 강제활성화)

server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: utf-8
      enabled: true
      force: true

 

 

 

 

jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul

(security라는 데이터베이스에 접속한다는 뜻)

 

 

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
    username: cos
    password: cos1234

 

 

(mysql DB 생성 및 연결을 전체적으로 보려면 아래의 링크 확인)

https://blog.naver.com/getinthere/221708608489

 

JSP AWS 배포 하기 - MySQL + Tomcat

https://youtu.be/BH7De5EEoU4​만약에 AWS에 배포한 프로젝트 내부의 META-INF의 context...

blog.naver.com

 

 

유저와 데이터

create user 'cos'@'%' identified by 'cos1234';
GRANT ALL PRIVILEGES ON *.* TO cos@'%';
create database security;
use security;

 

 

 

실행 해보면 잠겨있다

루트 컨텍스트로 이동했음에도 불구하고 로그인페이지로 강제 이동한다

인터셉터에서 가로채서 막은 것이다

(각종 공격을 다 막아 준다)

 

 

필터 - 디스패쳐 - 인터셉터 - 컨트롤러

 

 

로그에 찍힌 패스워드를 입력

비밀번호는 (해시)로 되어 있다

 

 

 

 

로그아웃 하려면

http://localhost:8080/logout

 

 

 

----------

위의 모양이 맘에 안든다면 로그인 페이지 회원가입 페이지를 커스텀 할 것

-----------

 

config 패키지 생성

 

 

설정파일 하나 생성

 

필터 - 디스패쳐 - 인터셉터 - 컨트롤러

 

필터와 디스패쳐 인터셉터 사이에

시큐리티 필터 체인 ( 많은 필터가 모여있다, xss, 주소, ip, 인증 등) - 체인이 있다는 것을 기억

필터를 낚아채려면 오버라이딩 또는 extends로 재정의

 

 

전체 필터를 관리할 수 있는 클래스 (@EnableWebSecurity)

어댑터가 있다는 것은 일부 원하는 것만 오버라이딩 할 수 있다는 것

 

 

@EnableGlobalMethodSecurity(prePostEnabled = true) 를 추가하여 컨트롤러 접근 전에 낚아채기

 

 

http요청을 제어하는 함수 추가

 

 

루트컨텍스트(/)로 가는 것은 모두 허용하도록 설정

 

 

로그인 없이 바로 들어가진다

 

 

controller 패키지 추가

 

 

연습용 라우팅들 추가

 

 

다른페이지로 이동하면 권한이 없어서 페이지가 뜨지 않는다(403)

 

 

파즈티브 방식(열것만 열고 나머지는 다 잠그기)

 

네거티브 방식(다열고 잠글것만 잠그기) - 우리가 만들 방식

loginPage를 이용하여 인증이 필요한 상황이면 /login으로 리다이렉트한다

 

 

http://localhost:8080/admin -> http://localhost:8080/login

(파일이 없어서 404)

 

 

로그인을 리턴해주는 라우팅을 만들면

 

 

 

 

머스태치 

 

메이븐 리파지토리에서 spring mustache 검색

 

 

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-mustache</artifactId>
		</dependency>

 

 

추가해주고 서버끄고 메이븐리파지토리 업데이트 무조건!

 

 

만약 템플레이트가 적용이 안된다면 yml 파일에 아래코드 추가 (안해도 작동되지만 혹시 안된다면 사용)

  mvc:
    view:
      prefix: /templates/
      suffix: .mustache

 

 

로그인 페이지에서 로그인 요청이 오면 loginProc

 

 

템플릿에서 name을 잘 적어줘야한다

 

 

loginProc는 컨트롤러에서 낚아채는 것이 아니라 loginProcessingUrl에서 낚아채서 수행

(formLogin이라면 템플릿에서 form타입으로 날려야한다)

 

 

성공하면 해당주소로 이동 defaultSuccessUrl (Ajax와 같이 쓰지말 것 : 응답이 두번되기 때문에 꼬임)

로그인만! Ajax 쓰지말자 (회원가입은 써도됨)

 

 

@RestController를 Controller로 변경하고 데이터를 리턴하려면 @ResponseBody 추가

 

 

컨트롤러에 회원가입 라우팅 추가

 

 

템플릿에 회원가입 추가 (회원가입은 ajax로 바꾸어도 무방)

 

 

 

컨트롤러에 회원가입 프로세스 라우팅 추가

(SecurityConfig에서 ProcessingUrl을 안 만들었기 때문)

 

 

@RequestBody는 제이슨(또는 데이터) 일때만 사용

 

 

model 패키지 추가

 

 

유저모델추가

 

 

입력 테스트

 

 

포비든이 뜬다 무엇이 문제일까

 

 

POST일때만 폼태그로 요청 토큰(csrf-token)이 필요하다!

(csrf = 해커가 임의대로 요청을 해서 공격)

csrf-token을 임시로 만들어줘서 줘야한다

(스프링에서 csrf 토큰 만들기 검색 공부)

 

방법(1. csrf토큰 비활성화 // 2. 토큰 만들기)

 

현대 프로그래밍은 자바스크립트로 하기 때문에 csrf를 잘 당하지 않는다 (수업에선 비활성화)

SecurityConfig.java

 

 

회원가입을 하면 인덱스페이지로 이동된다

 

 

-------

 

JPA

연습 용이라 서비스 안만들고 레파지토리로 끝냄

 

JPA와 Spring Data JPA

https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/

 

JPA, Hibernate, 그리고 Spring Data JPA의 차이점

개요 Spring 프레임워크는 어플리케이션을 개발할 때 필요한 수많은 강력하고 편리한 기능을 제공해준다. 하지만 많은 기술이 존재하는 만큼 Spring 프레임워크를 처음 사용하는 사람이 Spring 프레�

suhwan.dev

 

 

모델에서 @Entity를 추가(데이터베이스의 모델)

 

 

IDENTITY로 설정 (오라클은 SEQUENCE를 사용, IDENTITY는 해당 DB특유의 전략 사용)

 

 

 

자바모델 만들어 질 때 테이블 생성

자바모델 그대로 데이터베이스에 테이블이 생성된다

(생성 create 업뎃만 update 아무것도안하려면 none)

update 만 선택해도 테이블생성까지 가능하다!

 

 

mysql에서 셀렉트해보면 만들지도 않은 테이블이 생성되어 있다

 

 

모델에서 createDate를 만들고 저장만 하면!

@CreationTimestamp 세팅 (시간 자동입력)

(java.security -> java.sql 로 만들어야한다)

 

 

DB에서 속성이 자동으로 생성되어있다

 

 

 

 

회원가입 해보면

 

 

서버를 켜보면 하이버네이트 Hibernate 

(서버를 켤때 마다 지워지고 다시 만들어진다)

디폴트는 언더바 전략

 

네이밍 전략

 

 

커멜 표기법 전략으로 변경

  jpa:
    hibernate:
      ddl-auto: update #create update none 3개 중 하나 선택
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true

 

 

레파지토리

jpa는 어노테이션이나 컨피그 데이터 소스가 아니라 JpaRepository를 상속

제네릭 첫번째 인자는 모델클래스, 두번째 인자는 모델의 프라이머리 키의 타입을 의미한다

 

 

컨트롤러에서 DI

 

 

한줄추가

 

 

입력해보면 들어가있다

(로그인시 오류가 날것이다 암호화가 되어있지않음)

 

BCryptPasswordEncoder (안좋은방법 다른방법 쓸 것)

encode

 

 

SecurityConfig.java

 

 

IndexController.java

 

 

bCryptPasswordEncoder를 이용해 해시화 시켜줌

 

 

로그인

10. Authentication 객체 생성

Authentication은 AuthenticationFilter의 자식

AuthenticationFilter는 loginProc가 호출될 때 작동

UsernamePasswordAuthenticationToken 토큰이 있어야 로그인됨

 

https://getinthere.tistory.com/29

 

스프링부트 with JPA 블로그 13강 - 시큐리티 동작 원리

1. 스프링 mvc request life cycle 2. Filter와 Interceptor는 실행 시점이 다르다. Filter는 WebApplication에 등록 - web.xml Interceptor는 Spring의 Context에 등록 필터체인 예제와 인터셉터 예제하기 3. In..

getinthere.tistory.com

 

 

1아이디패스워드받아서

2토큰을받고

3유저디테일서비스가 받음

4유저디테일서비스는 유저디테일즈를 리턴

5어썬티케이션매니저가 받아서 어썬티케이션에 넣는다

6유저디테일은 시큐리티에 꼭 필요한 필드들이 있기 때문에 무조건 유저디테일로

7내가 만든 유저 모델을 유저디테일 타입으로 바꿔줘야한다

8어썬티케이션은 세션에 담긴다
(Map<String, Authentication> 객체이름 = new HashMap<>();)
(aa.put("Security-Context", auth))
(session.setAttribute("Security-Context-Holder", 해시맵))
쉽게 접근 못하도록 싸매져 있다

AuthenticationManager를 이용해서 Authentication를 저장할 수 있다(강제로 저장)

 

 

패키지 생성

 

 

유저디테일즈를 리턴해주기 위해서 클래스 하나 생성

모델 유저에 유저디테일즈를 상속받으면 유저모델이 망가지기 때문에 새로만들어줌

 

 

UserDetails를 임플리먼트해주고 오버라이딩

Authentication 객체에 저장할 수 있는 유일한 타입 - UserDetails

시큐리티 입장에서 관리해줄 수 있게 정해져 있다

 

 

User 모델 콤포지션

더보기
package com.jaybon.securityEx01.config.auth;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.jaybon.securityEx01.model.User;

import lombok.Data;


//Authentication 객체에 저장할 수 있는 유일한 타입
@Data
public class PrincipalDetails implements UserDetails{
	
	private User user;
	
	public PrincipalDetails(User user) {
		super();
		this.user = user;
	}
	
	

	@Override // 사용자의 비밀번호를 알고 싶으면 호출
	public String getPassword() {
		return user.getPassword();
	}

	@Override // 사용자의 유저네임를 알고 싶으면 호출
	public String getUsername() {
		return user.getUsername();
	}

	@Override // 사용자가 만료된 지를 알고 싶으면 호출
	public boolean isAccountNonExpired() { // 만료안됐니?
		//접속시간확인하여 true false 리턴
		return true;
	}

	@Override 
	public boolean isAccountNonLocked() { // 락 안걸렸니?
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isEnabled() { // 계정활성화 되어있니?
		return true;
	}
	
	@Override // 어떤 권한을 가졌니?
	public Collection<? extends GrantedAuthority> getAuthorities() {
		// TODO Auto-generated method stub
		return null;
	}

}

 

 

 

 

UserDetailsService를 만들어야함

 

 

JPA 쿼리 짜는법

 

jpa 쿼리 생성룰

https://papababo.tistory.com/272

 

[Spring Data Rest] Query creation - 쿼리 생성 룰

Spring Data Rest 사용시, 또는 JPA 기본 검색기능활용시, 아래의 네이밍 룰 따르면 끝! https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation Table 4. Suppo..

papababo.tistory.com

 

 

로그인 해보면 성공

 

-------

권한리턴하기

유저 권한 리턴하기 (주석은 Arrays)

스프링 내부에서 원하는 GrantedAuthority 타입으로만 설정해서 리스트에 저장하여 리턴

----------

 

구조

 

PrincipalDetails.java

더보기
package com.jaybon.securityEx01.config.auth;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.jaybon.securityEx01.model.User;

import lombok.Data;


//Authentication 객체에 저장할 수 있는 유일한 타입
@Data
public class PrincipalDetails implements UserDetails{
	
	private User user;
	
	public PrincipalDetails(User user) {
		super();
		this.user = user;
	}
	
	

	@Override // 사용자의 비밀번호를 알고 싶으면 호출
	public String getPassword() {
		return user.getPassword();
	}

	@Override // 사용자의 유저네임를 알고 싶으면 호출
	public String getUsername() {
		return user.getUsername();
	}

	@Override // 사용자가 만료된 지를 알고 싶으면 호출
	public boolean isAccountNonExpired() { // 만료안됐니?
		//접속시간확인하여 true false 리턴
		return true;
	}

	@Override 
	public boolean isAccountNonLocked() { // 락 안걸렸니?
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isEnabled() { // 계정활성화 되어있니?
		return true;
	}
	
	// Arrays.asList(new SimpleGrantedAuthority(user.getRole()));
	@Override // 어떤 권한을 가졌니?
	public Collection<? extends GrantedAuthority> getAuthorities() {
		
		Collection<GrantedAuthority> authList = new ArrayList<>();
		authList.add(new SimpleGrantedAuthority(user.getRole()));
		return authList;
		
	}

}

PrincipalDetailsService.java

더보기
package com.jaybon.securityEx01.config.auth;

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 org.springframework.stereotype.Service;

import com.jaybon.securityEx01.model.User;
import com.jaybon.securityEx01.repository.UserRepository;

// UserDetailsService는  IoC로 찾음

@Service // UserDetailsService타입으로 메모리에 뜬다 (덮어씌워짐)
public class PrincipalDetailsService implements UserDetailsService{
	
	@Autowired
	private UserRepository userRepository;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 어썬티케이션 매니저가 낚아챔
		// JPA는 기본적인 CRUD만 있어서 다른걸 쓰려면 만들어줘야함 
		
		User user = userRepository.findByUsername(username);
		
		if(user == null) {
			return null;
		}
		
		return new PrincipalDetails(user);
	}

}

SecurityConfig.java

더보기
package com.jaybon.securityEx01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration // IoC 빈(bean, 인스턴스)을 등록
@EnableWebSecurity // 필터 체인 관리 시작 어노테이션
@EnableGlobalMethodSecurity(prePostEnabled = true) // 컨트롤러 접근 전에 낚아챔, 특정 주소 접근시 권한 및 인증 미리체크
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Bean // IoC에 등록되어 컨피그가 호출될 때 생성, 메서드를 IoC하는 방법
	public BCryptPasswordEncoder enc() { // 마땅히 둘 곳이 없어서 둔 것 Controller를 제외한 곳에 둠
		return new BCryptPasswordEncoder();
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		http.csrf().disable(); // csrf 비활성화
		
		http.authorizeRequests()
			.antMatchers("/user/**", "/admin**") 
			.authenticated()
			.anyRequest()
			.permitAll()
			.and()
			.formLogin()
			.loginPage("/login") // 인증이 필요한 곳에 /login 으로 리다이렉트
			.loginProcessingUrl("/loginProc") // 필터체인에서 인지하고 있다가 시큐리티가 낚아채서 Authentication Manager
			.defaultSuccessUrl("/"); // 성공하면 해당 주소로 이동 / 슬래시만 달면 이전 주소로 이동
	}
}

IndexController.java

더보기
package com.jaybon.securityEx01.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.jaybon.securityEx01.config.auth.PrincipalDetails;
import com.jaybon.securityEx01.model.User;
import com.jaybon.securityEx01.repository.UserRepository;

@Controller
public class IndexController {
	
	@Autowired
	private UserRepository userRepository;
	
	@Autowired
	private BCryptPasswordEncoder bCryptPasswordEncoder;

	@GetMapping({ "", "/" })
	public @ResponseBody String index() {
		return "인덱스 페이지입니다";
	}

	@GetMapping("/user")
	public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
		System.out.println("확인"+principalDetails);
		System.out.println(principalDetails.getUser().getRole());
		System.out.println(principalDetails.getAuthorities()); // 출력 했을 때 사용자의 모든 권한을 리턴
		return "유저 페이지입니다";
	}

	@GetMapping("/admin")
	public @ResponseBody String admin() {
		return "어드민 페이지입니다";
	}

	@GetMapping("/login")
	public String login() {
		return "login"; // 머스태치를 pom에 추가했으니 서픽스는 templates 프리픽스는 .mustatche
	}

	@GetMapping("/join")
	public String join() {
		return "join";
	}

	@PostMapping("/joinProc")
	public String joinProc(User user) {
		System.out.println("회원가입 진행" + user);
		String rawPassword = user.getPassword();
		String encPassword = bCryptPasswordEncoder.encode(rawPassword);
		user.setPassword(encPassword);
		user.setRole("ROLE_USER");
		userRepository.save(user);
		return "redirect:/";
	}

}

User.java

더보기
package com.jaybon.securityEx01.model;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.hibernate.annotations.CreationTimestamp;

import lombok.Data;

// ORM - Object Relation Mapping - 알아서 데이터베이스 테이블 만들어 준다

@Data
@Entity // 이것을 토대로 데이터베이스 모델을 만들 수 있다.
public class User {
	@Id // primary key를 걸어주는 어노테이션
	@GeneratedValue(strategy = GenerationType.IDENTITY) // 오라클은 시퀀스 전략, mysql은 오토인크리먼트 전략
	private int id;
	private String username;
	private String password;
	private String email;
	private String role; // ROLE_USER / ROLE_ADMIN
	@CreationTimestamp
	private Timestamp createDate;
}

UserRepository.java

더보기
package com.jaybon.securityEx01.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.jaybon.securityEx01.model.User;

// JpaRepository를 상속하면 자동 스캔됨.
public interface UserRepository extends JpaRepository<User, Integer>{

	//Jpa Naming 전략
	// SELECT * FROM user WHERE username = ?
	User findByUsername(String username); // 함수이름에 맞게 쿼리가 동작한다
	
//	// SELECT * FROM user WHERE username = ? AND password = ?
//	User findByUsernameAndPassword(String username, String password);
//	
//	@Query(value = "SELECT * FROM user", nativeQuery = true)
//	User 내맘대로();
	
}

join.mustache

더보기
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입 페이지</title>
</head>
<body>
	<h1>회원가입 페이지</h1>
	<hr/>
	<form action="/joinProc" method="post">
		<input type="text" name="username" placeholder="username">
		<input type="password" name="password" placeholder="password">
		<input type="email" name="email" placeholder="email">
		<button>회원가입</button>
	</form>
</body>
</html>

login.mustache

더보기
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
	<h1>로그인 페이지</h1>
	<hr/>
	<!-- 시큐리티는 x-www-form-urlencoded 타입만 인식 -->
	<form action="/loginProc" method="post">
		<input type="text" name="username">
		<input type="password" name="password">
		<button>로그인</button>
	</form>
</body>
</html>

application.yml

더보기
server:
  port: 8080
  servlet:
    context-path: /
    encoding:
      charset: utf-8
      enabled: true
      force: true
      
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Seoul
    username: cos
    password: cos1234
    
#  mvc:
#    view:
#      prefix: /templates/
#      suffix: .mustache


  jpa:
    hibernate:
      ddl-auto: update #create update none 3개 중 하나 선택
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true
      
      
      

 

 

 

+ Recent posts