-----
마샬링
------
세션서버
리액트 있는 조는 이렇게 만들면 안됨 - jwt 세팅해야함
템플릿을 이용하는 조는 세션서버를 만들어야 한다 - csrf-token 비활성화 등 세팅
----
옵저버 패턴이 적용 되어 있는 리액티브 스프링
----
스프링 시큐리티 보안 공부
https://spring.io/guides/gs/securing-web/
------
spring security / spring data JPA 로 세팅
com.jaybon.securityEx01 아래에 만들어야 컴포넌트 스캔이 된다
DataSource (DB 풀링 해주는 객체)
yml파일로 변경
포트와 컨텍스트패스 캐릭터셋을 설정하고 enable 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
실행 해보면 잠겨있다
인터셉터에서 막힌 것
각종 공격을 다 막아 준다
필터 - 디스패쳐 - 인터셉터 - 컨트롤러
로그에 찍힌 패스워드를 입력
비밀번호는 (해시)로 되어 있다
세션
Security-Context (key) 밑에 방대한 값(value)들이 있다
그 중에
Authentication( 인증에 필요한 권한, 유효한 이용자인지 등 모든 필드가 정해져 있다 )
필드들이 정해져 있다는 것은 만들어서 넣어야 한다는 뜻
Authentication Manager의 도움을 받아서 Authentication을 만든다
Authentication의 필수 입력 값은 (username, password)
Authentication의 구성은 정해진 필드를 따라 만들어야 한다 ( User, UserDetails )
UserDetails ( 유저네임, 패스워드, 권한 )를 만들 때 컴포지션하거나
내 커스텀 유저정보 java 파일에 UserDetails을 extends 하면 된다
접근할 때 DI를 사용하면 된다
로그아웃 하려면
http://localhost:8080/logout
----------
로그인 페이지 회원가입 페이지를 커스텀 할 것
설정파일 하나 생성
필터 - 디스패쳐 - 인터셉터 - 컨트롤러
필터와 디스패쳐 인터셉터 사이에
시큐리티 필터 체인 ( 많은 필터가 모여있다, xss, 주소, ip, 인증 등) - 체인이 있다는 것을 기억
필터를 낚아채려면 오버라이딩 또는 extends로 재정의
전체 필터를 관리할 수 있는 클래스 (@EnableWebSecurity)
어댑터가 있다는 것은 일부 원하는 것만 오버라이딩 할 수 있다는 것
http요청을 제어
로그인 없이 바로 들어가진다
다른페이지로 이동하면 권한이 없어서 페이지가 뜨지 않는다(403)
위의 방식이 파즈티브 방식(열것만 열고 나머지는 다 잠그기)이고 아래는 네거티브 방식(다열고 잠글것만 잠그기)
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
템플릿에서 name을 잘 적어줘야한다
loginProc는 컨트롤러에서 낚아채는 것이 아니라 loginProcessingUrl에서 낚아채서 수행
(formLogin이라면 템플릿에서 form타입으로 날려야한다)
성공하면 해당주소로 이동 defaultSuccessUrl (Ajax와 같이 쓰지말 것 : 응답이 두번되기 때문에 꼬임)
로그인만! Ajax 쓰지말자 (회원가입은 써도됨)
컨트롤러에 회원가입 라우팅 추가
템플릿에 회원가입 추가 (회원가입은 ajax로 바꾸어도 무방)
컨트롤러에 회원가입 프로세스 라우팅 추가
(시큐리티 컨피그를 안만들었기 때문)
@RequestBody는 제이슨 일때만!
유저모델추가
입력 테스트
포비든이 뜬다 무엇이 문제일까
POST일때만 폼태그로 요청 토큰(csrf-token)이 필요하다!
(csrf = 해커가 임의대로 요청을 해서 공격)
csrf-token을 임시로 만들어줘서 줘야한다
(스프링에서 csrf 토큰 만들기 검색 공부)
방법(1. csrf토큰 비활성화 // 2. 토큰 만들기)
현대 프로그래밍은 자바스크립트로 하기 때문에 csrf를 잘 당하지 않는다 (수업에선 비활성화)
SecurityConfig.java
회원가입을 하면 인덱스페이지로 이동된다
-------
JPA
연습 용이라 서비스 안만들고 레파지토리로 끝냄
모델에서 @Entity를 추가(데이터베이스의 모델)
IDENTITY로 설정
모델 만들어 질 때 테이블 생성
(생성 create 업뎃만 update 아무것도안하려면 none)
mysql에서 셀렉트해보면 만들지도 않은 테이블이 생성되어 있다
모델에서 createDate를 만들고 저장만 하면!
@CreationTimestamp 세팅 (시간 자동입력)
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
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
로그인 해보면 성공
-------
권한리턴하기
유저 권한 리턴하기 (주석은 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
'Server > Spring Boot' 카테고리의 다른 글
spring boot // 리플렉션 (0) | 2020.08.01 |
---|---|
spring boot // 필터 / 인터셉터 (0) | 2020.08.01 |
spring boot // 블로그 연습 (0) | 2020.07.27 |
spring boot // 주의사항 (0) | 2020.07.27 |
spring boot // web.xml (0) | 2020.07.27 |