@RequiredArgsConstructor가 붙어 있으면 final 이 붙어있는 클래스를 받는 생성자를 자동으로 만들어준다

 

이렇게 안에 매개변수를 넣을 수 있다

 

연구할 프로젝트

https://github.com/codingspecialist/Springboot-Security-JWT

 

codingspecialist/Springboot-Security-JWT

Contribute to codingspecialist/Springboot-Security-JWT development by creating an account on GitHub.

github.com

 

application.yml

ssl의 특징인 포트번호 443이 보임( 맞나? )

키 
키저장소비밀번호
키저장소타입
키별명

 

BootSecurityApplication.java

Connector를 만든 이유는 http와 https를 같이 쓰기 위함인 것 같다

postProcessContext도 마찬가지로 http와 https를 같이 쓰기 위함인 것 같다

 

Connecter 참고 링크

https://velog.io/@max9106/Spring-Boot-%EB%82%B4%EC%9E%A5-%EC%9B%B9-%EC%84%9C%EB%B2%84-z8k66l0suf

 

[Spring Boot] 내장 웹 서버 - 2 (스프링부트 HTTPS / HTTP2)

HTTPS HTTPS(SSL프로토콜 위에서 돌아가는 프로토콜)을 사용하려면 키스토어(인증서)를 만들어야한다. 티미널을 열어 프로젝트 위치에서 아래의 명령어를 입력한다. `keytool -genkey -alias spring -storetype

velog.io

 

postProcessContext 참고 링크

https://kimjongmo.github.io/spring/spring-boot-redirect-http-to-https

 

스프링부트 HTTP요청을 HTTPS 둘다 사용하기 « Kimjongmo's Blog

스프링부트 HTTPS 적용 스프링 부트 HTTPS 적용시키기 프로젝트에 자가 서명을 이용한 후 http요청을 하게 되면 아래와 같은 응답을 받게된다. 당연히도 해당 포트는 이제 더이상 http요청을 받지않��

kimjongmo.github.io

 

 

PublicRestApiController.java

컨트롤러 클래스 위에 @RequestMapping을 이용함(공통 경로)

다른 도메인에서도 접근가능 하도록 @CrossOrigin 사용

 

@RequestMapping 참고 링크

https://woolbro.tistory.com/43

 

Spring MVC 예제 - @RequestMapping 어노테이션 예제

이번 포스팅은 이전에 작성한 직원 관리 예제를 가지고 설명을 하려고 합니다. [Java/Spring-framework] - Spring MVC 예제 - 직원 관리 프로그램 Spring MVC 예제 - 직원 관리 프로그램 이번 포스팅은 Spring MVC.

woolbro.tistory.com

 

@CrossOrigin 참고 링크

http://jmlim.github.io/spring/2018/12/11/spring-boot-crossorigin/

 

스프링 부트에서 크로스도메인 이슈 처리하기. (@CrossOrigin 어노테이션을 사용) · 기억하기 위한 �

스프링 부트에서 크로스도메인 이슈 처리하기. (@CrossOrigin 어노테이션을 사용) 11 Dec 2018 | Spring CORS Cross Domain 크로스도메인 이슈란? Ajax 등을 통해 다른 도메인의 서버에 url(data)를 호출할 경우 XMLH

jmlim.github.io

 

 

DbInit.java

DB를 초기화 하는 것 같은 이름이다

CommandLineRunner는 스프링 구동시 사용할 코드들을 적는 기능을 포함한다

유추해보면 유저데이터를 모두삭제하고 역할별로 3개를 넣은듯 하다

 

 

UserPrincipal.java

getAuthorities에 permissions와 roles를 둘다 담았다 

 

 

SecurityConfiguration.java

아래링크에 따르면

@Bean 외부 라이브러리 등 개발자가  직접 제어불가능한 것들을 Bean으로 만들 때 사용

@Component 는 개발자가 직접 만든 클래스를 Bean으로 등록하기 위함

 

DB 관련 내용으로

데이터베이스 인증용 Provider 라고함

 

1. csrf 기능을 무효화하고 세션을 Stateless 상태로바꿈
2. authentication 인증,  authorization 권한을 무엇으로 설정할지 세팅
3. URI 입장권한

 

@Bean 참고 링크

https://galid1.tistory.com/494

 

Spring - @Bean 어노테이션과 @Component 어노테이션(DI) - 2

@Component, @Bean 프로그램이 거대해 짐에 따라 XML 을 이용하여 IOC Container 를 설정하는 것이 점점 어려워졌고 때문에 Annotation(@) 이란 것이 등장했다(이하 어노테이션). 어노테이션은 코드에 메타데��

galid1.tistory.com

 

DaoAuthenticationProvider 참고 링크

https://javaiyagi.tistory.com/431

 

스프링 시큐리티를 이용한 보안 - 1부 인증

현재 맡고 있는 프로젝트의 Rest Api 가 보안이 전혀 돼 있지 않아서, 보안을 강화하는 작업을 시작했다. 보안이 전혀 돼 있지 않기 때문에, 사용자 아이디/비밀번호 정보가 없어도, Rest Api 를 호출�

javaiyagi.tistory.com

 

 

JwtProperties.java

SECRET은 비밀코드인듯

 

 

JwtAuthenticationFilter.java

JwtAuthenticationFilter :  Form Login시 걸리는 Filter이다. UsernamePasswordAuthenticationFilter를 상속한 JwtAuthenticationFilter을 등록하였다. 이 필터는 HttpServletRequest에서 사용자가 Form으로 입력한 로그인 정보를 인터셉트해서 AuthenticationManager에게 Authentication 객체를 넘겨준다.

UsernamePasswordAuthenticationFilter : HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트

AuthenticationManager(ProviderManager) : 아이디 패스워드를 UsernamePasswordAuthenticationToken(인증용 객체)로만들어서 전달하면 UserDetailsService가 UserDetails를 만들어 줄것이다

 

attemptAuthentication : Authentication을 리턴 (securitycontextholder, securitycontext 에 담는 것?)

 

successfulAuthentication : 헤더에 JWT 헤더스트링과 토큰을 추가하는 듯함

 

 

참고 링크

https://javaiyagi.tistory.com/431

 

스프링 시큐리티를 이용한 보안 - 1부 인증

현재 맡고 있는 프로젝트의 Rest Api 가 보안이 전혀 돼 있지 않아서, 보안을 강화하는 작업을 시작했다. 보안이 전혀 돼 있지 않기 때문에, 사용자 아이디/비밀번호 정보가 없어도, Rest Api 를 호출�

javaiyagi.tistory.com

 

참고 링크

https://coding-start.tistory.com/153

 

Spring boot - Spring Security(스프링 시큐리티) 란? 완전 해결!

오늘 포스팅할 내용은 Spring Security이다. 사실 필자는 머리가 나빠서 그런지 모르겠지만, 아무리 구글링을 통해 스프링 시큐리티를 검색해도 이렇다할 명쾌한 해답을 얻지 못했다. 대부분 이론적

coding-start.tistory.com

 

 

JwtAuthorizationFilter.java

JwtAuthorizationFilter : Form Login에서 인증된 이후의 요청에 대해 Header 인증을 담당할 Filter이다.

BasicAuthenticationFilter 는 헤더만 분석하는 필터이다

BasicAuthenticationFilter를 상속한 JwtAuthorizationFilter를 등록하였다.  JWT 기반 인증에서 실제 JWT 토큰의 인증이 이루어질 필터 부분이다.

Authentication은 스프링 내부에서만 사용하는 Authentication이다. 

 

JwtToken안에 있는 payload 즉, Claims를 꺼내고 권한정보까지 만들어 인증처리된 Authentication을 생성한다.

생성한 Authentication을 SecurityContextHolder에 저장한다.

스프링의 나머지 FilterChain들을 수행할 수 있도록 doFilter(request,response)를 호출한다.

 

getUsernamePasswordAuthentication : JWT에서 Authentication을 추출하는 듯함

 

참고 링크

https://mia-dahae.tistory.com/122

 

[Authorization]JWT 토큰 사용하여 사용자정보 parsing하기

JWT토큰을 사용하여 사용자를 식별하기 위해 Header에 해당 정보를 넣을 것이다. Header에 다음과 같은 형식으로 전달하고 주고 받을 예정이다. { Authorization: Bearer token } 또한 모든 요청에 대해서 acces

mia-dahae.tistory.com

 

 

LoginViewModel

로그인 전용 모델인가?

 

 

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

세션 방식??

1. AuthenticationFilterAnotherParam Bean 등록
2. UsernamePasswordAuthenticationFilter 등록 
3. attemptAuthentication 메서드에서 넘어온 데이터를 session에 저장
4. 인증 성공 시 successfulAuthentication에서 setAuthenticationSuccessHandler를 등록
5. Controller에서(SuccessHandler에서 지정한 url) session에 저장한 데이터 사용
6. 인증 실패 시 unsuccessfulAuthentication에서 3번에서 저장했던 데이터를 Session에서 제거

참고 링크

https://okky.kr/article/358004

 

OKKY | spring security에서 로그인 parameter를 추가해서 받을 수 잇나요??

username하고 password가 기본값인데 필요한 추가해서 데이터를 받아서 비교할 순 없나요?? 혹시  가능한지 알고싶고, 가능하면 무엇을 custom해야하는지 알려주시면 감사드립니다.ㅠㅠ

okky.kr

 

 

아래 사이트를 참고하여 정리합니다

https://velopert.com/2350

 

[JWT] 토큰(Token) 기반 인증에 대한 소개 | VELOPERT.LOG

소개 토큰(Token) 기반 인증은 모던 웹서비스에서 정말 많이 사용되고 있습니다. 여러분이 API 를 사용하는 웹서비스를 개발한다면, 토큰을 사용하여 유저들의 인증작업을 처리하는것이 가장 좋은

velopert.com

 

토큰기반 인증

API를 사용하는 웹서비스를 개발한다면, 토큰을 사용하여 유저들의 인증작업을 처리하는 것이 좋은방법

 

토큰기반 인증 시스템을 선택하는 이유

1. Stateless 서버

Stateful 서버
- 클라이언트에게 요청을 받을 때마다 클라이언트의 상태를 계속유지
- 해당 정보를 서비스 제공에 이용
- ex) 세션을 유지하는 웹서버 - 서버컴퓨터의 메모리 또는 데이터베이스

Stateless 서버
- 상태를 유지 하지 않는다
- 클라이언트측에서 들어오는 요청만으로 작업을 처리
- 클라이언트와 서버의 연결고리가 없기 때문에 서버의 확장성이 높아진다

 

2. 모바일 어플리케이션에 적합
- 안드로이드/ios에서는 쿠키는 이상적이지 않다 (쿠키 컨테이너를 사용해야하는 등)
- 토큰기반은 이 번거로움을 해결할 수 있다

 

3.보안
- 해킹의 위험을 무조건 벗어나는 것은 아니지만 보안을 높일 수 있다

 

토큰기반 인증 시스템을 사용하는 서비스

트위터/ 페이스북/ 깃헙 등

 

토큰을 사용하게 되는 이유

1. 서버기반 인증의 문제점

세션 - 로그인 중인 유저수가 늘어나면 서버의 램이나 데이터베이스의 성능에 무리를 줄 수 있다

확장성 - 트래픽을 감당하기 위해 여러개의 프로세서를 돌리거나, 여러대의 서버 컴퓨터를 추가해야한다. 과정이 복잡해진다.

CORS - 쿠키는 단일 도메인 및 서브 도메인에서만 작동하도록 설계 되어있다. 따라서 쿠키를 여러도메인에서 관리하는 것은 번거롭다

 

토큰 기반 시스템의 작동 원리

Stateless 시스템에서 유저의 인증정보를 서버나 세션에 담아 두지 않는다

토큰기반 시스템의 구현방식

1. 유저가 아이디와 비밀번호로 로그인
2. 서버측에서 해당 계정정보를 검증
3. 계정정보가 정확하면 서버->유저 signed 토큰을 발급
4. 클라이언트 측에서 전달 받은 토큰을 저장
5. 요청시마다 토큰을 함께 서버에 전달 (http 요청의 헤더에 토큰값을 포함 시켜 전달)

 

토큰의 장점

무상태(Stateless) + 확장성(Scalability)

서버 확장에 적합(분산 서버라면 어떠한 서버로 요청이 들어가던 상관이 없다)

1. 보안
- 쿠키를 사용함으로 인해 발생하는 취약점이 사라짐(토큰 취약점은 존재)

2. Extensibility 확장성
- Scalability와는 다른 개념
- 로그인 정보가 사용되는 분야를 확장하는 것
- 토큰을 사용하여 다른 서비스에서도 권한을 공유 할 수 있다
(구글 네이버 등의 계정으로 내 사이트에 로그인을 할 수 있는 것처럼)
- 토큰에 선택적인 권한만 부여하여 발급 할 수 있다 (프로필 정보 가져오는 권한 O / 페북에 글쓰는 권한 X)

3. 여러 플랫폼 및 도메인
- 한가지 서비스가 아닌 여러 서비스 및 도메인에서 요청이 정상적으로 처리 된다
- 어플리케이션 응답 부분에 Access-Control-Allow-Origin: * 를 포함시켜서 처리
- 이미지, css, js, html 파일 등은 CDN에서 제공, 서버측에서는 API만 다루도록 설계 가능

아래는 CDN 정보

https://cdn.hosting.kr/cdn%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94/

 

https://cdn.hosting.kr/cdn%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94/

 

cdn.hosting.kr

 

4. 웹 표준 기반

토큰 기반 인증 시스템의 구현체인 JWT는 웹표준에 등록되어있어서 여러 환경에서 지원이 되고 수많은 회사의 인프라스트럭쳐에서 사용 되고 있음

 

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

 

JWT

 

기본정보

두 개체에서 JSON 객체를 사용하여 가볍고 자가수용적인 방식으로 정보를 안전성 있게 전달

1. 수많은 프로그래밍 언어에서 지원됨

2. 자가수용적
- 필요한 모든정보를 자체적으로 지니고 있음
- 토큰의 기본정보 / 전달할 정보 / 토큰 검증 signature

3. 쉽게 전달 가능
- HTTP의 헤더나 URL의 파라미터로 전달 할 수 있음

 

JWT의 사용처

1. 회원인증
- 유저가 로그인 되어 있는지 신경 쓸 필요없이 유저가 요청할 때 토큰만 확인하면 됨
- 세션관리가 필요 없어서 서버 자원을 아낄 수 있음

2. 정보교류
- 두 개체 사이에 안정성 있게 정보를 교환하기에 좋은 방법
- 정보가 sign 되어 있기 때문에 정보를 보낸 이가 바뀌거나 정보가 조작되었는지 검증 가능

 

JWT의 생김새

JWT는 . 으로 구분하여 3가지 문자열로 되어 있음

aaaa.bbbb.cccc

(aaaa는 헤더, bbbb는 내용, cccc는 서명)

JWT 토큰을 만들때는 JWT를 담당하는 라이브러리가 자동으로 인코딩 및 해싱 작업을 해줌

 

헤더

헤더는 두가지 정보를 지니고 있다

typ : 토큰의 타입을 지정한다 (즉 JWT임을 알림)

alg : 해싱 알고리즘을 지정, 보통 HMAC SHA256 또는 RSA가 사용됨, 서명 부분에서 토큰을 검증할 때 사용

{
  "typ": "JWT",
  "alg": "HS256"
}

위 헤더내용을 공백과 엔터를 없애고 base64로 인코딩

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

 

 

정보

payload 라고 하며 토큰에 담을 정보가 들어있음.
정보들 각각을 claim이라고 부름 (name / value 의 한쌍으로 이루어져 있음)

클레임의 종류
1. 등록된(registered) 클레임
- 등록된 클레임의 사용은 선택적이다
- 서비스에서 필요한 정보가 아닌, 토큰에 대한 정보들을 담기 위하여 이름이 정해진 클레임
- iss : 토큰 발급자
- sub : 토큰 제목
- aud : 토큰 대상자
- exp : 토큰의 만료시간 (무조건 현재시간의 이후로 설정해야함)
- nbf : Not Before, 토큰의 활성 날짜와 비슷한 개념, 이 날짜가 되기전엔 토큰이 처리되지 않음
- iat : 토큰이 발급된 시간, 토큰의 age를 판단
- jti : JWT의 고유식별자, 중복적인 처리를 방지하기 위해 사용, 일회용 토큰에 유용

2. 공개(public) 클레임
- 충돌이 방지된 이름을 가지고 있어야함
- 충돌을 방지하기 위해서는 클레임 이름을 URI 형식으로 지어야함

3. 비공개(private) 클레임
- 서버와 클라이언트의 협의하에 사용되는 클레임
- 공개 클레임과는 달리 이름이 중복되어 충돌 될 수 있으니 사용할 때 유의해야함

{
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
}

위 헤더내용을 공백과 엔터를 없애고 base64로 인코딩

eyJpc3MiOiJ2ZWxvcGVydC5jb20iLCJleHAiOiIxNDg1MjcwMDAwMDAwIiwiaHR0cHM6Ly92ZWxvcGVydC5jb20vand0X2NsYWltcy9pc19hZG1pbiI6dHJ1ZSwidXNlcklkIjoiMTEwMjgzNzM3MjcxMDIiLCJ1c2VybmFtZSI6InZlbG9wZXJ0In0

 

 

payload 
- 등록된클레임 - 요청자, 만료시간은 꼭넣자
- 공개된클레임 - URI를 사용
- 비공개클레임 - 비밀번호 이메일 등을 제외한 유저네임, 프라이머리키, 권한 등

토큰에는 패스워드등 중요정보는 넣으면 안된다

 

 

 

라고 한다

 

서명

헤더의 인코딩 값 + 정보의 인코딩 값 + 비밀키로 해쉬

서명부분은 해싱하기 때문에 디코딩이 안됨

 

JWT가 잘 완성 되었는지 검증

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

 

OAuth + 일반로그인

https://ondolroom.tistory.com/646

 

spring boot // 머스태치 mustache html / 주소 일부허용, 컨트롤러에서 허용 / 이클립스 내에서 의존성 ��

-------------- jpa 메서드 참고 https://github.com/codingspecialist/Springboot-Security-OAuth2.0-V2 codingspecialist/Springboot-Security-OAuth2.0-V2 Contribute to codingspecialist/Springboot-Security..

ondolroom.tistory.com

 

 

페이스북 OAuth 로그인

https://ondolroom.tistory.com/651

 

spring boot // 페이스북 로그인

------------------- 테스트결과 잘들어감

ondolroom.tistory.com

 

 

네이버 OAuth 로그인 참고 사이트

https://for1123.tistory.com/24

 

19. 네아로(네이버 아이디로 로그인) API 사용하기

네이버 개발자 센터로 들어가 Application 탭에서 애플리케이션 등록을 누르면 다음과 같은 화면이 나타납니다. 위와 같이 설정하고 등록하면 Client ID와 Secret을 획득할 수 있습니다. 네이버에서는 �

for1123.tistory.com

 

 

참고

구글, 페이스북은 키 - 밸류 형태

네이버는 제이슨 형식이다

 

 

application.yml

기본프로바이더에 네이버가없기 때문에 프로바이더를 추가해준다

 

 

NaverUserInfo.java

기존 OAuth2UserInfo 커스텀과 다르게 attributes안에서 response를 가져와야한다

가져오는 함수를 하나 만들어서 리턴한다

 

 

PrincipalOauth2UserService.java

네이버를 추가해준다

---

 

 

테스트

---

 

 

DB

정상적으로 입력됨

 

----------

 

코드

 

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개 중 하나 선택 , 개발이 끝나면 none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    show-sql: true
      
      
  security:
    oauth2:
      client:
        registration:
          google: # /oauth2/authorization/google가 이 주소를 동작하게 한다
            client-id: 키
            client-secret: 키
            scope:
            - email
            - profile #스코프는 회사마다 다르기 때문에 적음
          
          facebook:
            client-id: 키
            client-secret: 키
            scope:
            - email
            - public_profile
          
          naver:
            client-id: 키
            client-secret: 키
            redirect-uri: http://localhost:8080/login/oauth2/code/naver
            authorization-grant-type: authorization_code
            scope:
            - email
            - profile_image
            
        provider:
          naver: 
            authorization-uri: https://nid.naver.com/oauth2.0/authorize
            token-uri: https://nid.naver.com/oauth2.0/token
            user-info-uri: https://openapi.naver.com/v1/nid/me
            user-name-attribute: response

 

 

SecurityConfig.java

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.authentication.UserServiceBeanDefinitionParser;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.OAuth2User;

import com.jaybon.securityEx01.config.oauth.PrincipalOauth2UserService;

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

 

 

NaverUserInfo.java

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

import java.util.Map;


public class NaverUserInfo implements OAuth2UserInfo{
	
    private Map<String, Object> attributes;

	public NaverUserInfo(Map<String, Object> attrubutes) {
		this.attributes = attrubutes;
	}
	
    private Map<String, Object> ofNaver(Map<String, Object> attributes) {
        Map<String, Object> response = (Map<String, Object>) attributes.get("response");

        return response;
    }

	@Override
	public String getProviderId() {
		return (String) ofNaver(attributes).get("id");
	}

	@Override
	public String getProvider() {
		return "naver";
	}

	@Override
	public String getEmail() {
		return (String) ofNaver(attributes).get("email");
	}

	@Override
	public String getName() {
		return (String) ofNaver(attributes).get("name");
	}
}

 

 

PrincipalOauth2UserService.java

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

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import com.jaybon.securityEx01.config.auth.PrincipalDetails;
import com.jaybon.securityEx01.config.oauth.provider.FacebookUserInfo;
import com.jaybon.securityEx01.config.oauth.provider.GoogleUserInfo;
import com.jaybon.securityEx01.config.oauth.provider.NaverUserInfo;
import com.jaybon.securityEx01.config.oauth.provider.OAuth2UserInfo;
import com.jaybon.securityEx01.model.User;
import com.jaybon.securityEx01.repository.UserRepository;

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
	
	@Autowired
	private UserRepository userRepository;
	
	@Override
	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
	
		
		OAuth2User oAuth2User = super.loadUser(userRequest); // 토큰값으로 자원을 받아오는 함수
		
		// 1번: oAuth2User의 정보를 principalDetails에 넣어주면 된다.
		// 2번: PrincipalDetails를 리턴한다.
		System.out.println("userRequest"+userRequest.getAccessToken().getTokenValue()); // 코드 토큰 유저정보
		System.out.println("userRequest"+userRequest.getClientRegistration()); // 코드 토큰 유저정보
		System.out.println(oAuth2User); // 토큰을 통해 응답받은 회원정보
		
		try {
			
		} catch (Exception e) {
			// TODO: handle exception
		}
				
		return processOAuth2User(userRequest, oAuth2User); // 이때 세션에 등록된다 OAuth2User
	}
	
	private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) {
		
		// 일반적으로는 로그인할 때 유저정보 User
		// 1번 : OAuth2로 로그인할 때 유저정보 oAuth2User.getAttributes() <- 구성해야됨
		// 2번 : DB에 이 사람있나?
		// 있으면? -> 업데이트(구글에서 바뀐정보가 있을 수 있으니 확인 없이 무조건 업데이트)
		// 없으면? -> 인서트(회원가입)
		// 리턴을 PrincipalDetails를 리턴
		
		OAuth2UserInfo oAuth2UserInfo = null;
		if(userRequest.getClientRegistration().getRegistrationId().equals("google")) {
			oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
		} else if(userRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
			oAuth2UserInfo = new FacebookUserInfo(oAuth2User.getAttributes());
		} else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
			oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
		} else {
			System.out.println("구글, 페이스북, 네이버만 가능");
		}
		

		Optional<User> userOptional 
			= userRepository.findByProviderAndProviderId(oAuth2UserInfo.getProvider(), oAuth2UserInfo.getProviderId());
		
		User user;
		if(userOptional.isPresent()) {
			user = userOptional.get();
			// 아이디가 있어도 update 해줘야 함
		} else {
			user = User.builder()
					.username(oAuth2UserInfo.getProvider()+"_"+oAuth2UserInfo.getProviderId())
					.email(oAuth2UserInfo.getEmail())
					.role("ROLE_USER")
					.provider(oAuth2UserInfo.getProvider())
					.providerId(oAuth2UserInfo.getProviderId())
					.build();
			userRepository.save(user);
		}
			
		return new PrincipalDetails(user, oAuth2User.getAttributes());
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

 

 

 

 

 

테스트결과 잘들어감

 

 

 

 

 

 

 

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

참고프로젝트 jpa 메서드도 참고

https://github.com/codingspecialist/Springboot-Security-OAuth2.0-V2

 

codingspecialist/Springboot-Security-OAuth2.0-V2

Contribute to codingspecialist/Springboot-Security-OAuth2.0-V2 development by creating an account on GitHub.

github.com

 

oauth 세팅 유튜브

youtu.be/WACDTFroYNI

 

현재 프로젝트

https://github.com/jaybon1/springwork/tree/master/securityOAuthEx01

 

jaybon1/springwork

Contribute to jaybon1/springwork development by creating an account on GitHub.

github.com

---------

 

이클립스 내에서 의존성 추가하기

 

--

 

 

OAuth2 Client 선택 추가

---

 

기존에 있는 서비스를 이용하면 DB를 이용해야 하기 때문에 카카오나 네이버 인증을 이용하기 어렵다

스프링에는 OAuth 서비스가 따로 있다

 

스프링 부트에서 구글 페이스북을 표준으로 하는 이유는 리턴하는 정보가 규격화 되어 있기 때문

이외의 회사들은 정보규칙이 다르고 코드 토큰 등의 구성 방식이 다를 수 있다

 

OAuth 구동 순서

더보기
             (요청)           
사용자 <---------> 서버
             (로그인페이지)

서버에서 인증하는 것이 아니라 구글 페이스북에 먼저 인증을 요청

사용자 ---------->   구글 페이스북 ( 로그인 )

구글 페북에서 인증되면 서버에 코드를 준다

구글 페이스북 -------> 서버 (코드를 준다)

서버는 코드를 이용해서 구글 페이스북 액세스 토큰을 요청한다 
토큰을 받으면 (권한을 받은 것)

서버 -------> 구글 페이스북   (토큰요청)

구글 페이스북 -------> 서버 (토큰을 준다)

액세스 토큰으로 스코프를 요청 할 수 있다

서버 -------> 구글 페이스북   (스코프요청)

구글 페이스북 -------> 서버 (사용자 정보 스코프를 준다)

 

https://github.com/codingspecialist/Springboot-Oauth2.0-Facebook-Google-Login-Session

 

codingspecialist/Springboot-Oauth2.0-Facebook-Google-Login-Session

Contribute to codingspecialist/Springboot-Oauth2.0-Facebook-Google-Login-Session development by creating an account on GitHub.

github.com

 

약속된 규칙 - 라이브러리를 사용하면 주소를 아래와 같이 맟춰 줘야한다

/oauth2/authorization/google

/oauth2/authorization/ 주소를 OAuth-Client가 낚아채서 분석한다

분석하여 구글쪽으로 던져서 구글로그인 화면을 사용자에게 보내준다

 

yml 파일에 중요한 정보가 있을 경우 암호화파일을 만들어서 인증서로 이용
또는 파일을 다른 디렉토리에 만들어서 깃헙에 업로드 안되도록 하여도 된다
또는 깃에서 해당파일을 이그노어 처리한다

oauth + 세션 연동하는 것이 중점

yml 파일은 하위라면 2칸 띄우기, 값넣을 때는 1칸 띄우기

  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 클라이언트키
            client-secret: 비밀키
            scope:
            - email
            - profile #스코프는 회사마다 다르기 때문에 적음

 

SecurityConfig.java

둘다 쓸거면 

.oauth2Login() 
.loginPage("/login")

를 적지말고

OAuth로그인만 쓸거면

.formLogin() 
.loginPage("/login")
.loginProcessingUrl("/loginProc")
.defaultSuccessUrl("/") 

를 안적으면 됨

 

.formLogin() 은 기본적으로 UserDetails 서비스를 탄다

반대로

.oauth2Login()은 직접 커스텀해줘야한다

 

패키지 추가

 

DefaultOAuth2UserService를 상속

 

무조건 오버라이드 하나해야함 loadUser

 

OAuth2User 오오쓰로그인 
UserDetails 일반로그인

두개의 인증세션이 생기는데 둘 중 하나만 쓸 것

 

PrincipalDetails.java

타입을 맟추기 위해 OAuth2User를 임플리먼트

 

 

 

 

oAuth2User를 리턴하는 게아니라 principalDetails를 리턴하면 세션을 하나로 관리 가능하다

 

테스트를 위해 new 해본다

 

 

내 구글 서버의 프라이머리키

sub= 프로바이더아이디
name= 풀네임
given_name= 이름, family_name= 성씨,
picture= 사진
email= 이메일
email_verified= 확인
locale= 지역

 

[ROLE_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email, SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]



등등이 보인다

 

일반로그인은 
    provider:
    prividerId:
가 있는 사람은 못하게 막는다

User{
    id: 자동증가값
    username: 프로바이더_프로바이더 아이디
    password: 임의의 문자열을 해시값으로 만들어 입력
    emaill: 스코프의 이메일
    provider: 프로바이더
    prividerId: 프로바이더 아이디
}

 

 

 

userRequestClientRegistration{
registrationId='google', 
clientId='클라이언트아이디', 
clientSecret='클라이언트 시크릿',
clientAuthenticationMethod=org.springframework.security.oauth2.core.ClientAuthenticationMethod@592d42e,
authorizationGrantType=org.springframework.security.oauth2.core.AuthorizationGrantType@5da5e9f3, 
redirectUriTemplate='{baseUrl}/{action}/oauth2/code/{registrationId}', 
scopes=[email, profile], providerDetails=org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails@11e8362c, 
clientName='Google'}

{action}은 현재 login이다

 

이제 할 일은 OAuth2User와 PrincipalDetails를 합치는것 

 

 

 

리턴을 위에서 만든 함수로 변경

 

oAuth2User 는 Map이라서 관리하기 힘들다 그래서 클래스를 따로 만들어줌

 

구글 프로바이더 아이디 = sub

페이스북 프로바이더 아이디 = id

 

제공자(구글 페북)가 응답해주는 속성값이 달라서 공통으로 만들어준다

Oauth하나만 할거면 만들 필요 없다

PrincipalOauth2UserService.java에서만 사용할 클래스

 

 

 

SecurityConfig.java 수정

 

이 서비스에 접근해서 이용할 경우가 있기 때문에 싱글톤으로 잡아준다

 

 

 

 

 

userRequest - 코드 토큰 유저정보 등 모든 정보

oAuth2User - 토큰을 통해 응답받은 회원정보

 

 

UserRepository.java

쿼리는 연습삼아 만들어봄

 

@Service를 등록해야 SecurityConfig.java에 autowired가 되기 때문에 등록

userRepository를 @Autowired 해준다

 

 

 

 

 

 

 

빨간줄이 뜬다

 

Jpa 파일을 열어보면 옵셔널타입이다

제네릭 안의 오브젝트가 널이거나 널이 아니거나 담을 수 있다

즉 Optional 이 널을 감싸고 있기 때문에 널포인트 익셉션를 피할 수 있을 것

isPresent()는 값이 있는지 확인하여 불린
get()은 값을 반환
orElse() 값이 없으면 기본값 (빈객체) 반환 / 있으면 그대로 반환
orElseThrow() 값이 없으면 익셉션 발생 / 있으면 그대로 반환

 

첫번째 방법

.get()은 isPresent()이후에 꼭 쓰자

 

1번 검색

 

없는 번호 검색

 

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

JPA에서는 옵셔널을 지향하자

----------

 

두번째 방법

new User()를 하든 원하는 값을 채우든 하면 된다

 

또는 화살표함수 (함수가 하나일 경우!)

 

 

 

 

X자리에 익셉션을 넣는다

 

 

또는 화살표 함수 (함수가 하나라서) + 어떤 익셉션이올지 모를경우

 

sb에 스크립트를 넣어서 이동시켜도됨

value에 값을 여러개 넣을 경우 중괄호로 묶자

 

---

 

비밀번호를 넣지 않음 (Oauth로그인)

 

 

일반로그인은 비밀번호가 암호화 되기 때문에 값을 넣지 않으면 로그인안되고

Oauth로그인은 비밀번호가 없더라도 알아서 로그인 된다

 

일반로그인시

 

Oauth 로그인시

 

 

PrincipalDetails.java

생성자 만들어줌

 

 

실행해보면

 

PrincipalDetails.java

이렇게 합쳐주지 않으면 세션이 2개가 저장된다

@AuthenticationPrincipal PrincipalDetails

 

 

 

----

스프링 시큐리티 보안 공부

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