REST API 서비스와 Stateless 아키텍처JWT (Json Web Token)REST API with JWT 제대로 해보기JWT 토큰 생성과 검증jwt를 이용한 필터구성 (UsernamePasswordFilter, BasicAuthenticationFilter 구현하여)
REST API 서비스와 Stateless 아키텍처
앞서 7장까지는 세션을 활용하는 브라우저 기반 웹 서비스에 대해 Spring Security를 어떻게 활용하는지 확인해보았다. 지금부터는 SPA(Single Page Application), 안드로이드/아이폰 모바일 앱 등을 위해 API를 제공하는 API 서버에서 Spring Security를 어떻게 활용할 수 있는지 알아본다.
REST API
- API 서비스 개발을 위한 가장 일반적인 접근 방법이며 (사실상) 표준
- 엄격하게 REST API 디자인 원칙을 따르는 것는 쉽지 않음
- 어쨋든 통상적으로 REST API 라고 부르고 있음 — 본 과정에서는 사회통념을 따르도록 함
- Java, Python, Go 등 다양한 언어로 풍부한 레퍼런스가 존재 — 구현이 쉬움
- HTTP 프로토콜 기반만 따르면 어떤 기술이든 응용 가능(XML, JSON)
- 앞에서 알아본 3-Tier Architecture 구조와 REST API 서비스를 궁합이 좋음
- 3-Tier Architecture의 장점 대부분은 REST API 서비스를 개발/운영할때 여전히 유효함
- REST API 서비스에서 Session을 꼭 써야 하는가에 대해서는 서비스 성격에 따라 고민이 필요함

Stateful vs Stateless
- Stateful 아키텍처의 장단점
- 일단 Session을 사용하고 있으면, Stateful 하다고 할 수 있음
- 앞서 살펴본 것처럼 수평확장(Scale-Out) 과정이 쉽지 않음 — Session Cluster 반드시 필요
- Session Cluster의 장애 또는 성능 병목이 서비스 전체에 큰 영향을 줄수 있음
- 단일 사용자의 다중 로그인 컨트롤, 사용자 유효성 체크, 강제 로그아웃 기능 구현이 쉬움
- Stateless 아키텍처의 장단점
- Session을 전혀 사용하지 않아야함 — 사실 HTTP 프로토콜 자체가 Stateless
- 수평확장이 매우 쉬움 — Session Cluster 필요 없음
- 단일 사용자의 다중 로그인 컨트롤, 사용자 유효성 체크, 강제 로그아웃 기능 구현이 어려움
- 무엇보다 완전한 Stateless 아키텍처 기반으로 유의미한 서비스 개발이 어려움
- 완전한 Stateless 서비스는 정적 리소스(html, css, javascript, 이미지 등)를 서비스 하는데 적합함 — 예: AWS S3
- 서버는 어떤식으로든 사용자를 식별할 수 있어야 함 (단, Session 사용 금지)
JWT (Json Web Token)
JWT는 Stateless 상태를 유지하며, 서버에서 사용자를 식별할 수 있는 수단을 제공한다.
JWT 개요
- JSON 포맷을 사용하여 데이터를 만들기 위한 웹 표준(open standard) — RFC 7519
JWT는 유저에 관한 자체적으로 필요한 모든 정보를 지니고 있다. [ Self-contained
] → db 쿼리를 한번 초과로 날릴 필요가 없음
- 토큰에 대한 메타정보 (토큰타입, 사용된 해시 알고리즘)
- 사용자 정의 데이터
- 토큰 유휴성 검증을 위한 데이터
인터넷상에서 쉽게 전달할 수 있음. [ Compact
]
- URL-Safe 텍스트로 구성되기 때문에 HTTP 프로토콜의 어느 위치에든 들어갈 수 있음 (보통 HTTP 헤더에 들어감)
- URL, POST parameter, HTTP header
위변조 검증 가능
- 토큰이 위변조되지 않았음을 증명하는 서명(Signature) 포함
- JWT는 secret키(HMAC 알고리즘)나, public/private key pair (RSA 알고리즘)을 사용하여 서명될 수 있음
Signed tokens
can verify theintegrity
of the claims contained within it, whileencrypted tokens
hide
those claims from other parties
JWT를 사용하는 이유
쿠키와 세션 →
- 쿠키는 http의 header에 포함되어 보내지는데, 이 http의 내용들은 패킷을 중간에 가로채서 가져가게 되면 내용이 그대로 다 보임(암호화 이런것들 전혀 안하니까)
- 개인 민감정보를 그대로 노출했을 때(쿠키)의 단점을 막아내기 위해서 나온 것이 바로 Session임(인증 정보자체를 주고 받지 말고 서버에 저장하자). 그러나 Session을 이용할 때, 수평확장을 하게 되면 Session Cluster라는 것을 사용해야 하고 이는 성능 병목이 될 수 있음(stateful의 단점) → stateless이면서 세션 저장소 조회 같은 DB작업도 없는 무언가 있을까?? → JWT
- Single Sign On 에서 JWT를 많이 사용한다고 함 ( 적은 오버헤드와 다른 도메인의 시스템 사이에 쉽게 사용이 가능하기 때문에)
- Information Exchange : JWT는 안전하게 정보를 주고 받는데 있어 좋은 방법임. 왜냐하면, 서명이 되어 있기 때문에(예를 들어, public/private key pair를 사용한다면) 전송자가 누구인지 알 수 있고 내용이 변조되지 않았음을 알 수 있음
- 내용이 변조되면 Signature 부분이 맞지 않을 것이기에. Signautre부분
JWT 작동원리 (Token-based Authentication 의 작동원리임)
JWT 구조
- Header, Payload, Signature 세 부분으로 구성됨
- Header, Payload, Signature 세 부분을
Base64 Url-Safe
방식으로 인코딩하고 dot(.)을 구분자로 결합함
- XML 기반의 표준인 SAML 에 비교해서 compact 함
- 알고리즘은 HMAC, RSA 방식을 지원
- 위 그림에서 HS512는 HMAC using SHA-512를 의미
- HMAC 알고리즘에서 비밀키는 최소한 알고리즘의 서명 길이 만큼의 비트를 가지고 있어야함 (HS512 — 64byte)
- Claims는 entity(대개 user)에 대한 statements, 그리고 추가적인 메타데이터를 포함하고 잇음
- Claim 자체는 쉽게 말해 Key-Value 데이터 쌍
- JWT 자체는 암호화되는 것이 아니기 때문에 민감정보를 포함해서는 안됨
- Claim은 3가지 타입(Reserved Claims, Public Claims, Custom Claims) 으로 구분됨
- iss — 토큰을 발급한 발급자 (Issuer)
- exp — 만료시간이 지난 토큰은 사용불가
- nbf — Not Before의 의미로 해당 시간 이전에는 토큰 사용불가
- iat — 토큰이 발급된 시각
- jti — JWT ID로 토큰에 대한 식별자
- Public Claims : 사용자 마음대로 쓸 수 있으나 충돌 방지를 위해 미리 정의된(IANA JSON Web Token Registry) 이름으로 사용을 권고함
- Custom Claims : 사용자 정의 Claims (Reserved, Public 에 정의된 이름과 중복되지 않도록함)
- 서명값을 만들기 위해서는
encoded header
,encoded payload
,secret
,algorithm specified in the header
가 필요함 - 토큰 생성 주체만 알고 있는 비밀키를 이용해 헤더에 정의된 알고리즘으로 서명된 값
- 서명은 JWT의 전송자가 누구인지 검증하기 위해, 토큰이 위변조 되지 않았음을 확실히 하기 위해 사용됨

Header — JWT를 검증하는데 필요한 두가지 정보를 담고 있음 (토큰 타입
, 사용된 알고리즘
)
Payload : JWT를 통해 전달하고자 하는 데이터
Reserved Claims : 미리 등록된 Claims. 필수적으로 사용할 필요는 없지만 사용을 권고함(이름은 compact하게 유지하기 위해 다 3글자)
Signature
String concatenated = encodedHeader + '.' + encodedClaims String token = base64URLEncode(HMACSHA512(concatenated, key))
JWT 장단점 — Stateful vs. Stateless
- 장점
- 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 따로 스토리지가 필요 없음
- 수평확장이 매우 쉬움 — Session Cluster 필요 없음
- 따라서, Active User가 많은 서비스에서 JWT사용이 유리함
- Session을 사용할 경우 Active User 수 만큼 Session을 저장해야 하기 때문에 스토리지 관리가 어려워짐
- 만료시간을 내부적으로 설정할 수 있는 기능이 있음
- 자체적으로 필요한 모든 것을 가지고 있음(Self-contained)
- 이미 토큰을 생성할 때 해당 유저가 존재한다는 사실을 기반으로 토큰을 찍어내고, 해당 토큰의 서명을 통해 위변조 여부도 확인하기 때문에 매 리퀘스트 마다 DB를 확인할 필요가 없는 것! → 스케일 아웃 시, 인증 서버(유저 정보 확인을 진행하는)의 부하가 낮아지게 됨
- 인증 서버가 단일 장애점(Single Point of Failure)이 아니게 되는 것
- 단점
- 토큰 크기를 가능한 작게 유지해야 함
- 토큰 자체가 항상 HTTP 요청에 포함되어야 하기 때문에 토큰이 커질수록 불리함
- 유효기간이 남아 있는 정상적인 토큰에 대해 강제적으로 만료 처리가 어려움(외부 유출 시) — 보안성 측면에서는 다소 불리한 부분이 있음. 그래서 만료기간을 짧게 가져감
- Session을 사용할 경우 동시 Session 제어, Session 만료 처리 등 보안상 이점이 있음
REST API with JWT 제대로 해보기
Jwt에 필요한 config들을 받아올 수 있는 @ConfigurationProperties 클래스 만들기
- clientSecret
- header
- issuer
- expirySeconds 등
@ConfigurationProperties(prefix="jwt") @Configuration @Setter @Getter public class JwtConfigure { private String secretKey; private Long expirySeconds; private String issuer; } // ---------------- jwt: issuer: todo-spring secret-key: secretsecret expiry-seconds: 3600
Jwt를 verify하고 sign해줄 수 있는 클래스 만들기(Claims 포함)
- method signature
Claims verify(String token)
- token 받아서 verify 하고 Payload 반환String sign(Claims claims)
- Payload 받아서 Jwt 토큰 만들기
- Claims을 쓰지 않고 그냥 값으로 받아서 token을 만들어주면, payload에 뭐 추가할 때마다 parameter 갯수가 늘어나겠지.
- public static class Claims ( JWT 토큰의 Payload에 해당하는 부분)
private Claims(DecodedJWT decodedJWT)
↔Claims verify(String token)
public static Claims from(String email, String… roles)
↔
String sign(Claims claims)
Jwt전용 Authentication 인터페이스 구현체 추가(JwtAuthenticationToken)
- authorities
- credentials
- details
- principal
- authenticated
- 인증되기 전의 Token 생성자
- 로그인 시, JwtAutheticationFilter에서 사용
- 인증되고 난 후의 Token 생성자
- 재로그인 시, JwtAuthenticationFilter에서 사용
- JwtAuthenticationProvider(로그인 시)에서 사용
Token에 들어갈 principal 구현
- 이거 구현할 시, Claims에 포함되어 있는 내용들만 여기에 들어갈 수 있음. 왜냐면 얘를 만드는 곳은 Filter기 때문에 Token의 내용들로만 값을 채워넣을 수 있음
@Getter @RequiredArgsConstructor public class JwtPrincipal { private final String token; private final Long userId; }
JwtAuthenticationFilter
- 두가지 기능을 수행해야함(이건 나의 뇌피셜)
- login endpoint로 들어올 시, Token 생성
- JwtAuthenticationFilter : attemptAuthentication()
- → AuthenticationManager : authenticate()
- → JwtAuthenticationProvider : authenticate()
- → userService.login()
- & token 생성, 반환
- Authorization Header에 Token담겨 있을 시, 인증하여 SecurityContextHolder에 담기
- getToken — 헤더에 값있는지 확인
- Token validate 후 JwtAuthenticationToken 생성하여 SecurityContextHolder에 담기
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String token = getToken((HttpServletRequest) request); if(token == null || SecurityContextHolder.getContext().getAuthentication() != null){ chain.doFilter(request, response); return; } HttpServletRequest req = (HttpServletRequest) request; try{ Jwt.Claims claims = jwt.verify(token); String username = claims.getUsername(); List<GrantedAuthority> authorities = getAuthorities(claims); if (StringUtils.hasText(username) && authorities != null && !authorities.isEmpty()){ JwtAuthenticationToken authentication = new JwtAuthenticationToken( new JwtPrincipal(username, token), null, authorities); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req)); SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); } catch (JWTVerificationException e){ log.warn("Jwt processing failed : {}", e.getMessage()); } }
JwtAuthenticationToken 타입을 처리할 수 있는 AuthenticationProvider 인터페이스 구현체를 추가 — JwtAuthenticationProvider
- UserService 클래스를 이용해 로그인을 처리하고, JWT 토큰을 생성함
- UserService 클래스는 더이상 UserDetailsService 인터페이스를 구현하지 않음
- 인증이 완료된 사용자의 JwtAuthenticationToken 을 반환함
- principal 필드 — JwtAuthentication 객체
- details 필드 — com.prgrms.devcourse.user.User 객체 (org.springframework.security.core.userdetails.User와 명백히 다름에 주목)
@Override public boolean supports(Class<?> authentication) { return JwtAuthenticationToken.class.isAssignableFrom(authentication); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { JwtAuthenticationToken jwtAuthentication = (JwtAuthenticationToken) authentication; return processUserAuthentication( String.valueOf(jwtAuthentication.getPrincipal()), jwtAuthentication.getCredentials() ); } private Authentication processUserAuthentication(String principal, String credentials) { try { User user = userService.login(principal, credentials); List<GrantedAuthority> authorities = user.getGroup().getAuthorities(); String token = getToken(user.getLoginId(), authorities); JwtAuthenticationToken authenticated = new JwtAuthenticationToken( new JwtPrincipal(token, user.getLoginId()), null, authorities); authenticated.setDetails(user); return authenticated; } catch (IllegalArgumentException e) { throw new BadCredentialsException(e.getMessage()); } catch (DataAccessException e) { throw new AuthenticationServiceException(e.getMessage(), e); } }
UserDetailsService에 의존하지 않는 UserService를 만들며, Spring Security의 인증 처리 기능 일부를 커스터마이징 해야함
UsernamePasswordAuthenticationToken 타입을 처리할 수 있는 AuthenticationProvider 인터페이스 구현체로 DaoAuthenticationProvider 클래스가 있다. 또한 DaoAuthenticationProvider 구현체는 UserDetailsService 인터페이스에 의존한다. 8장까지는 Spring Security가 제공하는 기본 인프라스트럭쳐를 최대한 이용하려 했고, 이런 이유로 UserService 클래스가 UserDetailsService 인터페이스 구현체 역할을 했다.
JwtAuthenticationProvider Security Configuration에 설정(AuthenticationManager가 JwtAuthentication Provider를 찾을수 있도록 설정해주어야 함)
@Bean public JwtAuthenticationProvider jwtAuthenticationProvider( Jwt jwt, UserService userService ) { return new JwtAuthenticationProvider(jwt, userService); } @Autowired public void configureAuthentication( AuthenticationManagerBuilder builder, JwtAuthenticationProvider authenticationProvider ) { builder.authenticationProvider(authenticationProvider); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
JwtAuthenticationFilter 가 해야할 일을 SecurityContextPersistenceFilter의 SecurityContextRepository 커스텀 구현으로 옮기기(Optional)
- JwtAuthenticationFilter의 핵심 역할은 HTTP 요청헤더에서 JWT 토큰을 확인하고, 검증하여 SecurityContext 를 생성하는 것
- SecurityContextPersistenceFilter 구현을 살펴보면, SecurityContextRepository에서 SecurityContext을 읽어옴
- SecurityContextRepository는 HTTP 요청에서 필요한 데이터를 얻고, 이 데이터를 이용함
- JwtAuthenticationFilter 구현 VS. SecurityContextRepository 커스텀 구현
- JwtAuthenticationFilter
- HTTP 헤더에서 JWT 토큰을 추출하고, 검증하여 SecurityContext를 생성할 수 있음
- Security Filter 체인 상에서 어디에 위치하는지가 중요함
- SecurityContextPersistenceFilter 바로 뒤에 또는 UsernamePasswordAuthenticationFilter 필터 전후로 위치하면 적당함
- SecurityContextRepository 커스텀 구현
- 기본적으로 JwtAuthenticationFilter 구현과 유사함
- 그러나 SecurityContextRepository 인터페이스에 맞추어 부수적인 메소드 구현이 필요함
- saveContext, containsContext 메소드
- SecurityContextPersistenceFilter, SessionManagementFilter 2개의 필터에서 SecurityContextRepository 구현이 어떻게 사용되는지 잘 알고 있어야함
- SecurityContextRepository 인터페이스 커스텀 구현 방식이 추가적으로 고려할 내용이 많고, Spring Security 전반에 걸쳐 끼치는 영향이 더 큼 → Filter구현 방식이 조금 더 낫다
- 특히 SessionManagementFilter를 사용할 경우 SecurityContextRepository 메소드 구현 방법에 따라 적절한 설정이 필요함
sessionCreationPolicy가 STATELESS 이면서 (사실 JWT 토큰이 사용된다는 것 자체가 STATELESS임을 의미함) SecurityContextRepository의 containsContext 메소드가 false를 반환하는 경우, 불필요한 SessionFixationProtectionStrategy가 실행되지 않도록 NullAuthenticatedSessionStrategy 구현체를 설정하는 등 별도 처리가 필요하다.
토큰 관리방법
- redis나 hazelcast와 같은 메모리 베이스드 캐쉬 방식을 사용해서 토큰을 관리함
- db에 저장하면 확인할 때마다 db 요청이 들어가야 하기 때문에 부담이 될 수 있음
JWT 토큰 생성과 검증
implementation("com.auth0:java-jwt:3.16.0") implementation("io.jsonwebtoken:jjwt:0.9.1") public class JWTSimpleTest { private void printToken(String token){ String[] tokens = token.split("\\."); System.out.println("header : " + new String(Base64.getDecoder().decode(tokens[0]))); System.out.println("body : " + new String(Base64.getDecoder().decode(tokens[1]))); } @DisplayName("1. jjwt 를 이용한 토큰 테스트") @Test void test_1(){ String okta_token = Jwts.builder().addClaims( Map.of("name", "jongwon", "price", 3000) ).signWith(SignatureAlgorithm.HS256, "jongwon").compact(); System.out.println(okta_token); printToken(okta_token); } @DisplayName("2. java-jwt 를 이용한 토큰 테스트") @Test void test_2() { String oauth0_token = JWT.create().withClaim("name", "jongwon") .withClaim("price", 3000) .sign(Algorithm.HMAC256("jongwon")); System.out.println(oauth0_token); printToken(oauth0_token); } }
- 이렇게 두개의 jwt 이용할 수 있는 라이브러리가 존재하는데, 이 둘은 시크릿 키를 관리하는 방법에 있어서 차이가 있음. 그래서 그 부분을 동일하게 해주어야 서로 간의 호환이 됨
- json 웹 토큰은 클라이언트가 어디든 간에 알고리즘과 키 값이 서로 일치한다면 누구든 검증가능한 토큰
@DisplayName("2. java-jwt 를 이용한 토큰 테스트") @Test void test_2() { byte[] SEC_KEY = DatatypeConverter.parseBase64Binary("jongwon"); String oauth0_token = JWT.create().withClaim("name", "jongwon") .withClaim("price", 3000) .sign(Algorithm.HMAC256(SEC_KEY)); System.out.println(oauth0_token); printToken(oauth0_token); DecodedJWT verified = JWT.require(Algorithm.HMAC256(SEC_KEY)).build().verify(oauth0_token); System.out.println(verified.getClaims()); Jws<Claims> tokenInfo = Jwts.parser().setSigningKey(SEC_KEY).parseClaimsJws(oauth0_token); System.out.println(tokenInfo); } @Test @DisplayName("3. 만료 시간 테스트") void test_3() throws InterruptedException { val AL = Algorithm.HMAC256("jongwon"); String token = JWT.create().withSubject("a1234") .withNotBefore(new Date(System.currentTimeMillis() + 1000)) .withExpiresAt(new Date(System.currentTimeMillis() + 3000)) .sign(Algorithm.HMAC256("jongwon")); // Thread.sleep(2000); try{ DecodedJWT verify = JWT.require(AL).build().verify(token); System.out.println(verify.getClaims()); }catch (Exception ex){ System.out.println("유효하지 않은 토큰입니다.."); DecodedJWT decode = JWT.decode(token); System.out.println(decode.getClaims()); } }
jwt를 이용한 필터구성 (UsernamePasswordFilter, BasicAuthenticationFilter 구현하여)
package com.sp.fc.web.config; import com.sp.fc.user.domain.SpUser; import com.sp.fc.user.service.SpUserService; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.security.sasl.AuthenticationException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class JWTCheckFilter extends BasicAuthenticationFilter { private SpUserService userService; public JWTCheckFilter(AuthenticationManager authenticationManager, SpUserService userService) { super(authenticationManager); this.userService = userService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String bearer = request.getHeader(HttpHeaders.AUTHORIZATION); if(bearer == null || !bearer.startsWith("Bearer ")){ chain.doFilter(request, response); return; } String token = bearer.substring("Bearer ".length()); VerifyResult result = JWTUtil.verify(token); if(result.isSuccess()){ SpUser user = (SpUser) userService.loadUserByUsername(result.getUsername()); UsernamePasswordAuthenticationToken userToken = new UsernamePasswordAuthenticationToken( user.getUsername(), null, user.getAuthorities() ); SecurityContextHolder.getContext().setAuthentication(userToken); chain.doFilter(request, response); }else{ throw new AuthenticationException("Token is not valid"); } } }
package com.sp.fc.web.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.sp.fc.user.domain.SpUser; import lombok.SneakyThrows; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter { private ObjectMapper objectMapper = new ObjectMapper(); public JWTLoginFilter(AuthenticationManager authenticationManager) { super(authenticationManager); setFilterProcessesUrl("/login"); } @SneakyThrows @Override public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { UserLoginForm userLogin = objectMapper.readValue(request.getInputStream(), UserLoginForm.class); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( userLogin.getUsername(), userLogin.getPassword(), null ); // user details... return getAuthenticationManager().authenticate(token); } @Override protected void successfulAuthentication( HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { SpUser user = (SpUser) authResult.getPrincipal(); response.setHeader("auth_token", JWTUtil.makeAuthToken(user)); response.setHeader("refresh_token", JWTUtil.makeRefreshToken(user)); response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); response.getOutputStream().write(objectMapper.writeValueAsBytes(user)); } }
package com.sp.fc.web.config; import com.sp.fc.user.service.SpUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; 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.config.http.SessionCreationPolicy; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class AdvancedSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SpUserService userService; @Bean PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); } @Override protected void configure(HttpSecurity http) throws Exception { JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager()); JWTCheckFilter checkFilter = new JWTCheckFilter(authenticationManager(), userService); http .csrf().disable() .sessionManagement(session-> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .addFilterAt(loginFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAt(checkFilter, BasicAuthenticationFilter.class) ; } }