2025. 3. 6. 11:06ㆍSpringboot/security
전체코드 : https://github.com/gks930620/spring_securty_all
프로젝트 세팅
https://start.spring.io/ 에서 프로젝트를 생성합니다.
필요한 library는
Spring Data JDBC,H2 Database, Spring Data JPA, Spring Web, Thymeleaf,
Spring Boot Devtools , Lombok, Spring security 입니다.
jwt, dotenv(.env 환경변수 설정) 라이브러리는 buuild.gradle에 직접 추가합니다.
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'io.github.cdimascio:dotenv-java:2.2.0'
설정파일
application.yml
spring:
datasource:
url: jdbc:h2:mem:security
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
format_sql: true
default_batch_fetch_size: 100
open-in-view: false
devtools:
livereload:
enabled: true
freemarker:
cache: false
restart:
enabled: true
thymeleaf:
cache: false
jwt:
secret : ${JWT_SECRET_KEY}
#키는 길이만 충분하고 노출되지만 않으면 됨. gpt한테 만들어하던가 내가 막 타자 아무렇게 해도됨
expiration_access: 60000 #테스트용 1분
logging:
level:
org.hibernate.SQL: debug
org.hibernate.type: trace
org.springframework.security : DEBUG
SecurityConfig
설명이 필요한 부분은 JWT를 구현하면서 설명할 것이지만,
마지막에 exceptionHandling.authenticationEntryPoint는 잠깐 살펴보자.
여기는 다음과 같은 상황에 오게된다.
- 인증없이(access token없이) 인증 필요한 url에 접근했을 때
- 토큰만료
- 로그인실패
이후 구현과정에서 이 3가지 상황에 대한 코드부분에서 request.setAttribute를 하게 될 것이다.
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtUtil jwtUtil;
private final CustomUserDetailsService customUserDetailsService; //내가 빈으로 등록한것들
private final AuthenticationConfiguration authenticationConfiguration; //authenticationManger를 갖고있는 빈.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http //내부H2DB 확인용. 진짜 1도 안중요함.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/h2-console/**").permitAll() // H2 콘솔 접근 허용
)
.csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**")) // H2 콘솔 CSRF 비활성화
.headers(
headers -> headers.frameOptions(frame -> frame.disable())); // H2 콘솔을 iframe에서 허용
http //기본 session방식관련 다 X
.csrf(csrf -> csrf.disable())
.sessionManagement(
session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(form -> form.disable())
.httpBasic(basic -> basic.disable());
http //경로와 인증/인가 설정.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/api/join").permitAll() //login필터는 기본적으로 /login 일 때 동작
.requestMatchers("/api/my/info").authenticated()
);
http //필터
.userDetailsService(customUserDetailsService)
.addFilterAt(
new JwtLoginFilter(authenticationConfiguration.getAuthenticationManager(), jwtUtil),
UsernamePasswordAuthenticationFilter.class) //기존 세션방식의 로그인 검증필터 대체.
.addFilterBefore(
new JwtAccessTokenCheckAndSaveUserInfoFilter(jwtUtil, customUserDetailsService),
UsernamePasswordAuthenticationFilter.class);
// .authenticated() url 부분 로그인 안하고 접근하면 기본적으로는 로그인페이지 redirect하기
// 근데 formLogin을 disable했는데 이러면 security는 기본적으로 403보냄.
http
.exceptionHandling(ex -> ex
.authenticationEntryPoint((request, response, authException) -> {
String errorCause =
request.getAttribute("ERROR_CAUSE") != null ? (String) request.getAttribute(
"ERROR_CAUSE") : null;
//인증없이(access token없이) 인증필요한 곳에 로그인했을 떄.
if (errorCause == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\": \"인증이 필요합니다.\"}");
return;
}
// JwtAccessTokenCheckAndSaveUserInfoFilter 토큰체크하는부분
if (errorCause.equals("토큰만료")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 응답
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("{\"error\": \"Access Token expired\"}");
return;
}
if (errorCause.equals("로그인실패")) { //jwtLoginFilter 로그인시도부분.
response.setStatus(
HttpServletResponse.SC_UNAUTHORIZED); //로그인실패도 401이 보통
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"error\": \"아이디 비번 틀림.\"}");
return;
}
})
);
return http.build();
}
}
JWT와 상관없는 회원가입 및 CustomUserAccount 세팅
회원가입, 로그인 판단(CustomUserDetailsService) ,
로그인 성공했을 때 security가 사용할 사용자 정보 자체는 기존 security 방식과 똑같다.
다른점은 로그인 시도 후 성공이면 access token을 클라이언트에 전달,
실패하면 로그인실패사실을 클라이언트에 전달하기.
또 클라이언트가 access token을 이용해 인증이 필요한 곳에 접근하고 이를 처리하는 부분이다.
JoinDTO
@Setter
@Getter
public class JoinDTO {
private String username;
private String password;
}
UserEntity
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String username;
private String password;
private List<String> roles=new ArrayList<>();
}
CustomUserAccount (securiy에서 사용자정보로 사용할 객체)
@Getter
public class CustomUserAccount implements UserDetails {
private UserEntity userEntity;
public CustomUserAccount(UserEntity userEntity) {
this.userEntity = userEntity;
}
@Override
public String getUsername() {
return userEntity.getUsername();
}
@Override
public String getPassword() {
return userEntity.getPassword();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return userEntity.getRoles().stream()
.map(SimpleGrantedAuthority::new)
.toList();
}
}
JoinController
@Controller
@RequestMapping("/api/join")
@RequiredArgsConstructor
public class JoinController {
private final JoinService joinService;
@ResponseBody
@PostMapping
public String joinPost(@RequestBody JoinDTO joinDTO){ //클라이언트가 body로 보낸다고 가정.
joinService.joinProcess(joinDTO);
return "회원가입 완료!";
}
}
UserRepository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
UserEntity findByUsername(String username);
}
JoinService
@Service
public class JoinService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public void joinProcess(JoinDTO joinDTO) {
//db에 이미 동일한 username을 가진 회원이 존재하는지?
UserEntity find = userRepository.findByUsername(joinDTO.getUsername());
if(find!=null) {
System.out.println("이미 있는 ID입니다.");
}
UserEntity user = new UserEntity();
user.setUsername(joinDTO.getUsername());
user.setPassword(passwordEncoder.encode(joinDTO.getPassword())); //DB에 저장될 때는 반드시 encoding되서 저장되어야한다.
user.getRoles().add("USER"); //hasAuthority("USER") 에 맞게 USER로 세팅
userRepository.save(user);
}
}
CustomUserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userData = userRepository.findByUsername(username);
if (userData != null) {
return new CustomUserAccount(userData);
}
throw new UsernameNotFoundException(username+"에 대한 회원정보가 없습니다.");
}
}
JoinController
(JWT를 사용하는 API서버에서는 보통 파라미터보다는 body에 담겨온다 -> @RequestBody)
@Controller
@RequestMapping("/api/join")
@RequiredArgsConstructor
public class JoinController {
private final JoinService joinService;
@ResponseBody
@PostMapping
public String joinPost(@RequestBody JoinDTO joinDTO){ //클라이언트가 body로 보낸다고 가정.
joinService.joinProcess(joinDTO);
return "회원가입 완료!";
}
}
MainController
@Controller
@RequestMapping("/api")
public class MainController {
//로그인 후
@RequestMapping("/my/info")
@ResponseBody
public String myInfo(@AuthenticationPrincipal CustomUserAccount customUserAccount
){
StringBuilder sb=new StringBuilder();
sb.append( "권한 : "+ customUserAccount.getAuthorities().iterator().next().getAuthority() +"<br>" ); //첫번째권한.
sb.append( "password : "+ customUserAccount.getPassword() +"<br> ");
sb.append( "username : "+ customUserAccount.getUsername() + "<br>" );
return sb.toString();
//api서버에서는 보통 DTO로 보내지만..
}
}
JwtUtil
로그인 성공, 인증필요한 곳 접근등에 사용할 token을 JWT로 만들건데
직접 만들면 힘드니까 JwtUtil클래스를 만든다.
이 jwtUtil에서는 access token 생성, 검증, 추출(username 등) 의 기능을 제공한다.
JwtUtil
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration_access}")
private long expirationAccess;
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secretKey.getBytes()); //HMAC 알고리즘일 때는 SecretKey로 return하기.
}
// Access Token 생성
public String createAccessToken(String username) {
return Jwts.builder()
.subject(username) // ✅ setSubject() -> subject()
.claim("token_type", "access")
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expirationAccess))
.signWith(getSigningKey()) // ✅ SignatureAlgorithm.HS256 대신 Jwts.SIG.HS256 사용
.compact();
}
//토큰에서 username 추출. 뭐 subject에 uuid를 넣기도하지만.. 여기서는 subject에 username세팅했었음.
public String extractUsername(String token) {
return Jwts.parser()
.verifyWith(getSigningKey()) // 0.12.3버전에서는 verifyWith에 Key말고 SecretKey가 와야한다.
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}
//토큰에서 인증여부 확인. 코드상 문제가없다면 보통 만료됐을 때 false
public boolean validateToken(String token) {
try {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey()) // ✅ 서명 검증
.build()
.parseSignedClaims(token) // ✅ JWT 파싱
.getPayload(); // ✅ claims(토큰 정보) 추출
// 토큰 만료 확인
Date expiration = claims.getExpiration();
return expiration.after(new Date()); // 현재 시간보다 만료 시간이 뒤에 있어야 유효
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
//jjwt 버전에 따라 구현방식이 다르다. 현재는 0.12.3 버전.
Access token으로 로그인 구현하기
로그인시도
이런식으로 동작하면 되는데 이 때 로그인url은 security기본설정에 의해 /login 이다
/login으로 클라이언트가 요청을 했다면 security는 기본적으로 UsernamePasswordAuthenticationFilter가
로그인 판단 + 로그인 성공/실패 처리를 하게된다.
로그인판단은 우리가 만든 CustomUserDetailsService에서 하는거고,
이건 JWT에서도 똑같이 사용할것이라 상관없다.
UsernamePasswordAuthenticationFilter의 로그인성공은 session에 사용자정보를 담는다.
UsernamePasswordAuthenticationFilter의 로그인실패는 다시 로그인하도록 redirect 된다.
이 기본 동작대신에 우리는 성공했을 때 access token만들어서 클라이언트한테 전달.
실패했을 때 실패 사실전달을 하도록 하는 필터를 만들어 UsernamePasswordAuthenticationFilter를 대체한다.
그래서 SecurityConfig를 보면 UsernamePasswodAuthentcationFilter 를 대체하는코드가 있다.
http //필터
.userDetailsService(customUserDetailsService)
.addFilterAt(
new JwtLoginFilter(authenticationConfiguration.getAuthenticationManager(), jwtUtil),
UsernamePasswordAuthenticationFilter.class) //기존 세션방식의 로그인 검증필터 대체.
// 이부분!!!
.addFilterBefore(
new JwtAccessTokenCheckAndSaveUserInfoFilter(jwtUtil, customUserDetailsService),
UsernamePasswordAuthenticationFilter.class);
JwtLoginFilter
//username, password를 이용해 로그인판단을 하는 필터
// /login URL일 때 동작
@RequiredArgsConstructor
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager; //new 로 생성하면 부모의 authenticationManager필드는 null이기 때문에 생성자로 주입.
private final JwtUtil jwtUtil;
// 로그인 시도
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
// 요청에서 username, password 추출
// jwt는 API서버 분리된방식. username,password는 body에 포함되서 옴.
// 파라미터에 포함되서 오지않음 보통. 이것때문에 재정의. UsernamePasswordAuthetnctionFilter는 parameter 를 처리함.
Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
String username = credentials.get("username");
String password = credentials.get("password");
//이 부분은 UsernamePasswordAuthetnctionFilter 코드 그대로.
// AuthenticationManger를 통해 확인하는건
// 결국 username,password를 가지고 CustomUserDetailsService의 return값(CustomUserAccount)이랑 비교.
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return authenticationManager.authenticate(authRequest); //여기서 AuthenticationException 발생.
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Failed to parse authentication request", e); //readValue하는과정에서 발생.
}
}
// 로그인 성공 → JWT 토큰 발급, 성공했을 때는 아무 문제없이 필터체인을 통과하게 된다.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
CustomUserAccount customUserAccount = (CustomUserAccount) authResult.getPrincipal();
String accessToken = jwtUtil.createAccessToken(customUserAccount.getUsername());
// 토큰을 응답에 포함
response.setContentType("application/json");
response.getWriter().write("{\"access_token\": \"" + accessToken + "\"}");
}
//실패하면 config의 authenticationEntryPoint로 @Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
request.setAttribute("ERROR_CAUSE" , "로그인실패"); //실패 후 config의 entryPoint로
super.unsuccessfulAuthentication(request,response,failed);
}
}
로그인 성공 후 토큰을 가지고 /api/my/info 접근
클라이언트는 받은 토큰을 가지고 /api/my/info에 접근합니다.
기존 security방식에서는 세션에 사용자정보(를 포함하는 securityContext)가 저장되어있지만,
API서버에서는 access token을 이용해 매 요청마다
securityContext에 CustomUserAccount를 저장해야합니다.
참고로 아까 만든 JwtLoginFilter는 /login일 때만 통과하는 필터입니다. 여기서는 사용X
매 요청마다 이런과정을 거쳐야하기 때문에 OncePerRequestFilter를 상속받은 필터를 만들어
SecurityConfig에서 필터체인에 등록해야합니다.
http //필터
.userDetailsService(customUserDetailsService)
.addFilterAt(
new JwtLoginFilter(authenticationConfiguration.getAuthenticationManager(), jwtUtil),
UsernamePasswordAuthenticationFilter.class) //기존 세션방식의 로그인 검증필터 대체.
//이부분!!
.addFilterBefore(
new JwtAccessTokenCheckAndSaveUserInfoFilter(jwtUtil, customUserDetailsService),
UsernamePasswordAuthenticationFilter.class);
JwtAccessTokenCheckAndSaverUserInfoFilter
//보통 이름은 JwtAuthorizationFilter를 한다.
//이름을 잘 못 지어도 jwt관련 필터들이 정확히 무슨처리하는지 알면 덜 헷갈린다.
// access token을 검증하고 CustomUserAccount(UserDetails)를 SecurityContext에 저장하는 필터
@RequiredArgsConstructor
public class JwtAccessTokenCheckAndSaveUserInfoFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil; //단순히 jwt기능제공
private final UserDetailsService userDetailsService; //내가만들고 빈 등록한 CustomUserDetailsService
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token == null) {
//토큰 없을 떄는 그냥 다음필터로
//인증이 필요없는곳 => 무사통과
//인증이 필요한곳 => securityConfig의 authentication에 걸림 => config의 authenticationEntryPoint 에서 처리
chain.doFilter(request, response);
return;
}
if (!jwtUtil.validateToken(token)) { //토큰이 문제 있다면.. 보통 만료돼었을 때
request.setAttribute("ERROR_CAUSE", "토큰만료");
chain.doFilter(request,response); //로그인을 안한거기 때문에 역시 seucirtyConfig의 authetncation에 걸림.
return;
}
String username = jwtUtil.extractUsername(token);
//토큰도 있고, 토큰도 문제없음. securityContext에 담아 로그인한걸로 판단!!.
UserDetails userDetails = userDetailsService.loadUserByUsername(
username); //내가 만든 CustomUserAccount
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { //띄어쓰기 주의
return bearerToken.substring(7);
}
return null;
}
}
실행결과
로그인전 /api/my/info.
(JwtAccessTokenCheckAndSaveUserInfoFilter의 이 부분 실행
=> 이후 config의 authenticationEntryPoint)
if (token == null) {
//토큰 없을 떄는 그냥 다음필터로
//인증이 필요없는곳 => 무사통과
//인증이 필요한곳 => securityConfig의 authentication에 걸림 => config의 authenticationEntryPoint 에서 처리
chain.doFilter(request, response);
return;
}
회원가입전 로그인 시도 /login
/login 이기때문에 JwtLoginFilter 동작.
DB에 아직 회원정보 없기때문에
attemptAuthentication()에서 AuthenticationException발생 => unsuccessfulAuthencation() 실행
=> 이후 config의 authenticationEntryPoint
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
String username = credentials.get("username");
String password = credentials.get("password");
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return authenticationManager.authenticate(authRequest); //여기서 AuthenticationException 발생.
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Failed to parse authentication request", e); //readValue하는과정에서 발생.
}
//AuthenticationException이 발생해서 여기 코드 실행
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
request.setAttribute("ERROR_CAUSE" , "로그인실패"); //실패 후 config의 entryPoint로
super.unsuccessfulAuthentication(request,response,failed);
}
}
회원가입
회원가입 후 로그인시도 => 로그인성공
JwtLoginFilter의 attemptAuthentcation() => 문제없음 => successfulAuthentication()에서 토큰발급
// 로그인 시도
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
Map<String, String> credentials = new ObjectMapper().readValue(request.getInputStream(), HashMap.class);
String username = credentials.get("username");
String password = credentials.get("password");
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
this.setDetails(request, authRequest);
return authenticationManager.authenticate(authRequest);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Failed to parse authentication request", e); //readValue하는과정에서 발생.
}
}
// 로그인 성공 → JWT 토큰 발급, 성공했을 때는 아무 문제없이 필터체인을 통과하게 된다.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
CustomUserAccount customUserAccount = (CustomUserAccount) authResult.getPrincipal();
String accessToken = jwtUtil.createAccessToken(customUserAccount.getUsername());
// 토큰을 응답에 포함
response.setContentType("application/json");
response.getWriter().write("{\"access_token\": \"" + accessToken + "\"}");
}
올바른 access token을 가지고 /api/my/info 요청
JwtAccessTokenCheckAndSaveUserInfoFilter에서 securityContext에 사용자 정보 저장
=> security가 인증된걸로 판단 => 컨트롤러까지
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token == null) {
chain.doFilter(request, response);
return;
}
if (!jwtUtil.validateToken(token)) { //토큰이 문제 있다면.. 보통 만료돼었을 때
request.setAttribute("ERROR_CAUSE", "토큰만료");
chain.doFilter(request,response); //로그인을 안한거기 때문에 역시 seucirtyConfig의 authetncation에 걸림.
return;
}
String username = jwtUtil.extractUsername(token);
//토큰도 있고, 토큰도 문제없음. securityContext에 담아 로그인한걸로 판단!!.
UserDetails userDetails = userDetailsService.loadUserByUsername(
username); //내가 만든 CustomUserAccount
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}
만료된 access token가지고 /api/my/info 요청
JwtAccessTokenCheckAndSaveUserInfoFilter에서 if문에 걸림. securityContext에 저장안함
=> security가 인증안된걸로 판단=> 이후 config의 authenticationEntryPoint로
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token == null) {
//토큰 없을 떄는 그냥 다음필터로
//인증이 필요없는곳 => 무사통과
//인증이 필요한곳 => securityConfig의 authentication에 걸림 => config의 authenticationEntryPoint 에서 처리
chain.doFilter(request, response);
return;
}
if (!jwtUtil.validateToken(token)) { //토큰이 문제 있다면.. 보통 만료돼었을 때
request.setAttribute("ERROR_CAUSE", "토큰만료");
chain.doFilter(request,response); //로그인을 안한거기 때문에 역시 seucirtyConfig의 authetncation에 걸림.
return;
}
String username = jwtUtil.extractUsername(token);
//토큰도 있고, 토큰도 문제없음. securityContext에 담아 로그인한걸로 판단!!.
UserDetails userDetails = userDetailsService.loadUserByUsername(
username); //내가 만든 CustomUserAccount
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
}