-
스프링부트 시큐리티 04 - JWT 토큰 세팅까지의 개념스프링/시큐리티 2021. 9. 9. 15:30
유투버 '데어프로그래밍'님 강의 참조
01 - 세션이란?
- 클라이언트가 어떠한 요청을 최초로 하게되면 서버에서는 응답시에 HTTP 헤더에 쿠키를 담고 그 쿠기가 세션 ID값을 든 채로 클라이언트에게 응답이 가게 된다. 클라이언트는 브라우저상에 이 응답+세션값을 자동으로 들고 머무르게 되는 것이다.
- 클라이언트가 최초 이후에 두번째 요청을 하게되면 이 세션값을 포함해서 요청을 하게 된다. 서버는 이 세션값을 보고 유효성을 검사하게 되는 것.
- 세션은 계속 유지되지 않는다 1) 서버가 세션을 끊을 수도 있고 2) 사용자가 브라우저를 닫게되면 없어 진다.
(브라우저가 닫히면 브라우저상의 세션은 날라가고 서버에서는 보통 30분정도 그 값이 유지 된다)
- 세션은 보통 인증(로그인)을 위해 많이 사용 된다. 하지만 단점이 있다. 로드밸런싱이 걸리면 최초 서버와의 세션값은 다른 서버에서 찾을 수 없게 된다. (이런 세션의 단점은 여러가지 해결방법이 있다 - sticky 서버라던지.. 하지만 모든 방법은 많은 비효율성을 가져온다)
※ JWT를 이해하기전 알아야할 몇가지 개념들
1) 통신 : OSI 7계층
- 응용계층 → 프리젠테이션 → 세션 → 트랜스포트 → 네트워크 →데이터링크 →물리 순으로 통신이 시작되고 통신을 받을때는 반대로 다시 올라 가게 되는 형태이다.
https://blog.naver.com/PostView.nhn?blogId=pst8627&logNo=221670903384
IT관련 용어 - [OSI 7계층] 이란? (OSI 7 Layer)
안녕하세요 탄탄이 입니다. 오늘은 컴퓨터 네트워크 통신을 이야기 할 때 항상 등장하는 OSI 7계층에 대...
blog.naver.com
- 여기서 트랜스포트 계층에서 TCP/UDP형태로 나뉘게 된다
TCP - 신뢰성 기반 통신 / UDP - 속도 기반 통신
웹개발은 TCP통신으로 주로 이루어 진다
2) 보안
- 데이터 통신에 있어서 보안은 아주 중요한 문제인데 이 보안을 설명하는 단어가 'CIA' 이다.
C - Confidentiality (기밀의) - 승인받은 사람만 보도록
I - Integrity (무결성) - 승인 받은 사람만 보는 인증 시스템이 무결점인지
A - Availability (가용성) - 2중 3중으로 보안장치가 되어도 언제든 정확한 키로 열수 있는지
이 3가지가 지켜져야 한다.
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=pentamkt&logNo=221204282722
[하루 3분 IT] 정보보안의 기본 3원칙, CIA
한 줄 요악 CIA는 정보보안의 기본 3원칙으로, 각각 Confidentiality(기밀성), Integrity(무결성), A...
blog.naver.com
- 이 CIA도 2가지 문제가 있다. 1) 정확한 키 전달 문제 2) 데이터를 누가 보냈는지의 인증 문제. 이 두가지 문제만 해결하면 보안에 대한 이슈가 해결이 가능
3) RSA
- 암호화 중의 하나로 키를 두개 들고 있다 1)Public(공개키) 2)Private(개인키)
- 데이터를 보낼때 다 공개되는 공개키로 보내고 받는 사람은 자기가 들고있는 개인키로만 공개키로 걸려있는 데이터를 열 수 있는 특징이 있다. 즉 다른사람이 데이터를 가로채려고 했을때 받는 사람의 개인키 없이는 못 여니 정확한 키 전달 문제는 여기서 해결
- 두번째로는 데이터를 보낼때 개인키로 잠구면 공개키로 열린다. 즉 A가 한 데이터를 A의 개인키로 데이터를 암호화하고 B에게 보면 B는 A의 공개키로만 열수 있기때문에 누가 보냈는지의 대한 인증문제가 해결 되는 것
(보통 전자문서의 서명에 쓰인다) - 즉 공개키 → 개인키 (전자서명) / 개인키 → 공개키(암호화) 의 특징을 가지는것이 RSA, 즉 JWT는 RSA기반의 방식
- 또한 RSA 방식에서는 공개키로 데이터를 잠구고 개인키로 한번더 암호화를 시켜 중간에서 데이터가 가로채져도 받는사람이 보낸사람의 공개키로 열었을때 열리면 OK, 안열리면 볼 필요가 없는 데이터라고 쉽게 알 수 있다.
4) RFC7519 문서
- 통신에 있어서 양자간의 약속된 규칙을 정의한 문서 (프로토콜)
- 현재까지 통신에 있어서 정의된 규칙문서 엄청나게 많고 JWT가 RFC7519규칙문서를 기반으로 정의 됨
02 - JWT란?
- 현대 웹서비스에서는 토큰을 사용하여 사용자들의 인증 작업을 처리하는것이 가장 좋은 방법이다. 그 토큰 인증 작업중 하나인 JWT(Json Web Token)는 JSON 포맷형식을 이용하여 사용자에대한 속성을 저장하는 Claim 기반의 서명이 이루어진 웹 토큰이다. 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달 한다.
- JWT는 세션의 고질적 단점을 해결하기위해 고안 된 것.
- JWT의 구조는 점(.) 으로 구분 된 세 부분으로 구성
헤더
페이로드
시그니쳐(서명)
→ xxxxx.yyyyy.zzzz 으로 생김 그리고 세부분 다 Base64Url 로 인코딩 시킴 - 헤더 - 알고리즘/타입을 설정하며 JSON-Base64Url 기반으로 암호화/복구화를 가능하게 함 → 보통 HS256방식으로 암호화가 됨(H=HMAC(시크릿키를 가지고있음) S=SHA(해쉬화를 뜻함))
- 페이로드 - 클래임 기반으로 개인 클레임을 주로 쓰며 사용자동의/정보 공유등에 쓰임
- 시그니쳐 - 헤더/페이로드를 넣고 개인 키를 설정함
- JWT는 보통 웹브라우저의 로컬 스토리지에 저장이 되고 최초 요청시 서버가 JWT를 같이 실어서 응답하게되고 그 이후는 세션 대신에 JWT를 확인해서 서버가 응답을 하는 방식
- HS256방식을 쓰기 싫으면 헤더부분에 RSA방식이라고 표시만 해주면 사용 가능하다
03 - JWT 프로젝트 세팅
- HS암호화 방식을 쉽게 해주는 메이븐 추가
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
- 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 jpa: hibernate: ddl-auto: create #create update none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: true
→ 간단 테스트
@RestController public class RestApiController { @GetMapping("home") public String home() { return "<h1>home</h1>"; } }
굿! 04 - JWT를 위한 모델/세큐리티 설정
@Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String username; private String password; private String roles; //USER,ADMIN 형식으로 role 부여 public List<String> getRoleList(){ if(this.roles.length()>0) { return Arrays.asList(this.roles.split(",")); } return new ArrayList<String>(); } }
- 하나의 유저가 두개의 롤을 가질 수 있다면 List 형식, 아니라면 그냥 게터 세터로 처리
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .httpBasic().disable() .authorizeRequests() .antMatchers("/api/v1/user/**") .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/manager/**") .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ROLE_ADMIN')") .anyRequest().permitAll(); } }
- http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) → 세션 안만듬
- .formLogin().disable()/.httpBasic().disable() → 기본적인 HTTP 방식 안씀
- JWT는 JSON을 기본으로하기때문에 내서버가 응답시에 JSON을 JS에서 처리가능하게 하는 등의 필터가 필요
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/api/**", config); return new CorsFilter(source); } }
- config.setAllowCredentials(true); → 내서버가 응답시 JSON을 JS에서 처리 가능하도록 할지 여부
- config.addAllowedOrigin("*"); → 모든 IP에 응답 허용
- config.addAllowedHeader("*"); → 모든 HEADER에 응답 허용
- config.addAllowedMethod("*"); → 모든 POST/GET/PUT/DELETE/PATCH등의 요청을 허용
- 이 필터를 다시 SecurityConfig에서 쓰도록 코딩
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CorsFilter corsFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilter(corsFilter) .formLogin().disable() .httpBasic().disable() .authorizeRequests() .antMatchers("/api/v1/user/**") .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/manager/**") .access("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ROLE_ADMIN')") .anyRequest().permitAll(); } }
- 현재까지의 작업으로 모든 요청은 반드시 CorsFilter를 타게 되있고 CrossOrigin 요청도 허용이 되도록 세팅을 한 것
- @CrossOrigin 어노테이션 방법도 있지만 이 어노테이션은 인증이 필요하지 않은 요청만 허용하므로 따로 필터를 해줘야 한다
- 요약하자면 이번 강의의 기본은 1) 세션이 없고 2)크로스오리진정책에서 벗어난다(모든 요청 허용) 3)Formlogin을 쓰지 않는다.
※ CORS란?
Cross-Origin Resource Sharing의 약자로 HTTP의 헤더를 사용하여 다른 포트/아이피의 자원 접근 권한을 부여를 하도록 알려주는 체제.
간단히 말하면 도메인 또는 포트가 다른 서버의 자원을 요청할때 이에대한 권한 부여 및 여부 확인 담당. 보통 개발을 하다보면 리액트와 자바를 연동할때 포트번호가 다르기 때문에 CORS 오류를 일으키는 경우가 자주 발생 한다. 이 오류가 발생하는 이유는 HTTP의 기본 정책이 동일 출저 정책을 따르기 때문. 즉 하나의 웹을 사용할때 자신의 출저와 동일한 리소스만 불러올 수 있으며 다른 출저의 리소르를 불러오면 CORS 오류가 뜬다.(다른 출저의 자원을 보려면 그 출처에 CORS헤더를 포함시켜 응답을 반환 해야함)
더 자세한 사항은 아래 브로그로!
https://evan-moon.github.io/2020/05/21/about-cors/
CORS는 왜 이렇게 우리를 힘들게 하는걸까?
이번 포스팅에서는 웹 개발자라면 한번쯤은 얻어맞아 봤을 법한 정책에 대한 이야기를 해보려고 한다. 사실 웹 개발을 하다보면 CORS 정책 위반으로 인해 에러가 발생하는 상황은 굉장히 흔해서
evan-moon.github.io
※JWT Bearer 인증 방식 짚고 넘어 가기
- .httpBasic().disable() 을 이해할 필요가 있다. http bisic방식은 쿠키 세션 방식을 쓰지 않고 헤더에 Authorization 키값에 ID/PW를 담아서 요청하는 방식을 의미한다. 이렇게 하면 쿠키 세션 인증 방식이 필요없다 왜냐하면 인증시마다 ID/PW를 달고 있기 때문이다. 하지만 이방식도 ID/PW가 노출되는 위험이 있기때문에 Authorization 에 토큰을 넣어서 보내는 방식이 JWT Bearer 방식. 이 방식에서도 토큰이 노출이 되면 안되지만 토큰이 노출되어도 직접적인 ID/PW 노출보다는 훨씬 안전하다. 즉 이 방식을 쓰기 위해서 '.httpBasic().disable()' 걸어 주는 것.
05 - JWT 토큰 검증 필터 설정
- 스프링 시큐리티는 기본적으로 서블릿 필터를 가지고 있어 인증이 되지 않으면 보안상 웹 페이지 접근에 제한 처리를 해버린다
- 따라서, 매번 웹 uri로 자원에 접근할때 WebSecurityConfigure에서 configure()에 설정해둔 uri 매칭에 따라permitAll()이나 Role을 가지고 있는지 체크하는 hasRole을 통해 인증과정을 거친다.
- .이때, addFilterBefore추가한 필터도 당연히 Filter로 사용하게 되고 JWT 토큰을 검증하여 로그인 인증을 통과 시킨다.
public class MyFilter1 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Filter 1"); chain.doFilter(request, response); } }
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean<MyFilter1> filter1(){ FilterRegistrationBean<MyFilter1> bean = new FilterRegistrationBean<MyFilter1>(new MyFilter1()); bean.addUrlPatterns("/**"); bean.setOrder(0); return bean; } }
- bean.setOrder(0); → 낮은 번호가 필터중 가장 먼저 실행 됨
06 - 임시 토큰 검증 테스트
@RestController public class RestApiController { ... @PostMapping("token") public String token() { return "<h1>token</h1>"; } }
public class MyFilter1 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; if (req.getMethod().equals("POST")) { System.out.println("POST 요청됨"); String headerAuth = req.getHeader("Authorization"); System.out.println(headerAuth); } System.out.println("Filter 1"); chain.doFilter(req, res); } }
- 현재 인증이 되던 안되던 컨트롤러를 타고 있는데 토큰값을 정확히 주고 그 값이 맞지 않으면 아예 컨트롤러도 타지 못하고 계속 인증을 하도록 만들어 보자
굿! - 즉 JWT 토큰 인증은 반드시 세큐리티 전에 돌아야 한다!
public class MyFilter1 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Filter 1"); chain.doFilter(request, response); } } public class MyFilter2 implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; if (req.getMethod().equals("POST")) { System.out.println("POST 요청됨"); String headerAuth = req.getHeader("Authorization"); System.out.println(headerAuth); if (headerAuth.equals("cos")) { chain.doFilter(req, res); } else { PrintWriter out = res.getWriter(); out.println("인증부터 하세요!"); } } } } public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CorsFilter corsFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(new MyFilter2(), SecurityContextPersistenceFilter.class); ..... } }
- 이제 로그인을 정상적으로 하게되면 토큰을 만들어서 응답해주고 그 이후 요청마다 Authrization의 value값으로 토큰을 가지고 오게 되는 것. 이때 토큰이 넘어오면 이 토큰이 내가 응답으로 준 토큰이 맞는지 검증만 하면 됨
07 - JWT를 위한 로그인 시도
- PrincipalDetails 세팅
@Data public class PrincipalDetails implements UserDetails { private User user; public PrincipalDetails(User user) { this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); user.getRoleList().forEach(r -> { authorities.add(()-> r); }); return authorities; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
- PrincipalDetailsService 세팅
@Service @RequiredArgsConstructor public class PrincipalDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("PrincipalDetailsService의 loadUserByUsername() 호출 됨"); User userEntity = userRepository.findByUsername(username); return new PrincipalDetails(userEntity); } }
- 로그인 요청시 실행 됨.
- UserRepository
public interface UserRepository extends JpaRepository<User, Long>{ public User findByUsername(String username); }
- 여기까지 세팅을 한 후 포스트맨으로 하면....
- 당연히 동작 안된다 왜냐하면 시큐리티의 기본 /login 을 막아놨기 때문이다 (.formLogin().disable())
- 즉 로그인 요청시 실행되는 PrincipalDetailsService 을 직접 실행해주는 필터가 필요하다
- JwtAuthenticationFilter 세팅
@RequiredArgsConstructor public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { System.out.println("JwtAuthenticationFilter: 로그인 요청 중!"); return super.attemptAuthentication(request, response); } }
- 이 필터가 강제로 실행되어야한다. SecurityConfig에 코드 한줄 추가
.formLogin().disable() .httpBasic().disable() .addFilter(new JwtAuthenticationFilter(authenticationManager()))
굿! - 이제 필터가 강제로 실행되면서 시큐리티에서의 로그인도 실행이 된다. 이후 JwtAuthenticationFilter 에서 ID/PW 유효성 검사를 하면되는데 이때 authenticationManager로 로그인 시도를 하면 이 함수가 로그인 진행(UsernamePasswordAuthenticationFilter)를 들고 있기때문에 진행이 되는 것.
- JwtAuthenticationFilter → authenticationManager (로그인 진행) → PrincipalDetailsService 호출 됨 → loadUserByUsername 함수 실행 됨 → PrincipalDetails 세션에 담음 → JWT 토큰 생성 후 응답
- 기억해야할 부분은 PrincipalDetails 을 세션에 담는데 권한을 필수로 관리를 해야하는 웹이기 때문!!
08 - JWT를 위한 강제 로그인
- JwtAuthenticationFilter 이 가지고 있는 함수 attemptAuthentication에서 넘어오는 ID/PW 받아보기
(로그인 요청시에는 대부분 데이터가 JSON형태이다. JSON형태로 파싱을 해야 ID/PW가 읽어진다)
- 디비에 값부터 넣기
- IndexController
@RequiredArgsConstructor @RestController public class RestApiController { private final UserRepository userRepository; .... @PostMapping("join") public String join(@RequestBody User user) { user.setPassword(new BCryptPasswordEncoder.encode(user.getPassword())); user.setRoles("ROLE_USER"); userRepository.save(user); return "회원가입완료"; } }
※ @Requestbody 가 있으므로 JSON으로 날려야 응답이 된다 기억하자!!!
@RequiredArgsConstructor public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter{ private final AuthenticationManager authenticationManager; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { System.out.println("JwtAuthenticationFilter: 로그인 시도중"); try { // BufferedReader br = request.getReader(); // // String input = null; // while((input=br.readLine())!=null) { // System.out.println(input); // } ObjectMapper om = new ObjectMapper(); User user = om.readValue(request.getInputStream(), User.class); System.out.println(user); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); Authentication authentication = authenticationManager.authenticate(authenticationToken); PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); System.out.println(principalDetails.getUser().getUsername()); return authentication; } catch (IOException e) { e.printStackTrace(); } System.out.println("==================================="); return null; } }
굿! - 제이슨으로 던져도 ObjectMapper라는 클래스가 제이슨을 알아서 파싱을 해주고 request에 JSON형태로 담긴 내용을 파싱해서 보여준다.
- 이제 여기에 직접 토큰을 만들어서 로그인 처리를 해보자 (원래라면 formLogin()을 시큐리티에 설정하면 자동으로 해주지만 현재 막아놨기때문에 직접 만들어야한다.)
- 하지만 시큐리티 특성상 패스워드는 무조건 암호화되야하고 비교시에도 암호화를 시켜야 작동이 된다. 암호화 세팅이 필요하다
- SecurityConfig
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CorsFilter corsFilter; @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } ...
- RestApiController에도 BCryptPasswordEncoder() 를 선언해서 바꾸자 (빈으로 등록을 했으니 new하지말고 쓰자)
private final BCryptPasswordEncoder bCryptPasswordEncoder; user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
→ 포스트맨으로 제 테스트를 해보면
굿! - 아이디가 뜬다는 것은 로그인 처리가 잘 됬다는 것. JwtAuthenticationFilter이 잘 불러지고 리턴으로 Authentication 값이 잘 떨어졌고 세션에 리턴값 'Authentication'이 세션에 잘 저장이 된 것.
* 정리를 하자면
- JwtAuthenticationFilter의 attemptAuthentication() 함수에 선언된
Authentication authentication = authenticationManager.authenticate(authenticationToken);
가 토큰을 만들어서 로그인 시도를 함. 정상이면 authentication 이 만들어 짐
즉 DB 저장된 값과 로그인으로 넣어지는 값이 동일하다 → 인증 끝 - 그 아래 코드로 선언된 principalDetails 단순 로그인 확인 용이며 authentication 객체가 세션에 저장이 되어야하는데 그 방법이 return으로 처리하는 것
- 리턴으로 굳이 하는 이유는 세큐리티의 권한 처리를 위함. JWT를 만들면 세션을 만들 이유가 없으나 권한을 부여해서 작업하고싶으면 어쩔 수 없음.
- 이제 남은것은 JWT 토큰을 만들어서 부여하는것인데 authentication 세션 처리가 끝나면 따라오는 함수가 있다.
@Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { System.out.println("successfulAuthentication 실행 됨"); super.successfulAuthentication(request, response, chain, authResult); }
- attemptAuthentication 함수가 끝나면 자동적으로 successfulAuthentication 실행이되고 여기서 JWT토큰을 만들어서 응답시에 사용자에게 JWT토큰을 RESPONSE 해주면 됨
굿 ! 09 - JWT 토큰 만들어서 응답하기
- successfulAuthentication 토큰을 담는다고 했으니 만들어 보자
@Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { System.out.println("successfulAuthentication 실행 됨"); //HS방식 PrincipalDetails principalDetails = (PrincipalDetails)authResult.getPrincipal(); String jwtToken = JWT.create() .withSubject("Token1") .withExpiresAt(new Date(System.currentTimeMillis()+(60000*10))) .withClaim("id", principalDetails.getUser().getId()) .withClaim("username", principalDetails.getUser().getUsername()) .sign(Algorithm.HMAC512("cos")); response.addHeader("Authorization", "Bearer "+jwtToken); }
- withSubject → 토큰 이름
- withExpiresAt → 토큰 만료 시간
- withClaim → 비공개 클레임으로 내가 받고 싶은 값을 보도록 하는 것
- sign → 내 서버만 아는 고유 비번을 넣음
- response.addHeader → 마지막으로 응답 헤더에 들어갈 값 (사용자가 이 값들을 받음)
굿! - 위의 값이 JWT 토큰이고 사용자에게 응답이 되고, 사용자는 이걸가지고 최초이후에 같이 요청하는 개념
- JWT 토큰과 세션 차이는 아래블로그!
쿠키/세션 방식과 JWT
1. 쿠키/세션 방식 1) 인증 방식 순서 1. 사용자가 로그인을 한다. 2. 서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여하여 세션 저장소에 저장한 후, 이와 연결되는
jihyun03.tistory.com
'스프링 > 시큐리티' 카테고리의 다른 글
스프링부트 시큐리티 04 - 최종 JWT 서버 구축 (0) 2021.09.11 스프링부트 시큐리티 03 - OAuth (구글/페이스북/네이버 로그인) (0) 2021.09.08 스프링부트 시큐리티 02 - 시큐리티 기본(회원가입/로그인/권한처리) (0) 2021.09.07 스프링부트 시큐리티 01 - 환경설정 (0) 2021.09.07