반응형
스프링 버전 3.1.2
스프링 시큐리티 버전 6.1.2
RestController 로그인 구현하기
1. 라이브러리 추가
dependencies {
...
// spring security
implementation 'org.springframework.boot:spring-boot-starter-security'
// jwt token
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
}
2. WebSecurityConfig 파일 작성
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurity {
private final Environment env;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final UserService userService;
private final UserRepository userRepository;
private final ObjectPostProcessor<Object> objectPostProcessor;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth.requestMatchers("/login").permitAll());
http.csrf(AbstractHttpConfigurer::disable)
// 로그인 시도 시 AuthenticationFilter 에서 처리하도록 추가
.addFilter(getAuthenticationFilter())
// jwt filter 추가
.addFilter(getJwtAuthorizationFilter())
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(login -> login
.defaultSuccessUrl("/", true)
.permitAll()
)
.logout(withDefaults());
return http.build();
}
private JwtAuthenticationTokenFilter getJwtAuthorizationFilter() throws Exception {
AuthenticationManagerBuilder builder = new AuthenticationManagerBuilder(objectPostProcessor);
return new JwtAuthenticationTokenFilter(authenticationManager(builder), userRepository, env);
}
private AuthenticationFilter getAuthenticationFilter() throws Exception {
AuthenticationManagerBuilder builder = new AuthenticationManagerBuilder(objectPostProcessor);
return new AuthenticationFilter(authenticationManager(builder), userService, env);
}
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
return auth.build();
}
}
3. Spring Security에서 로그인 처리하기 위한 AuthenticationFilter 작성
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
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.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.ArrayList;
import java.util.Date;
import java.util.Objects;
@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final UserService userService;
private Environment env;
public AuthenticationFilter(AuthenticationManager authenticationManager, UserService userService, Environment env) {
super.setAuthenticationManager(authenticationManager);
this.userService = userService;
this.env = env;
}
// 로그인 시도하면 제일 먼저 호출되는 메서드
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>()
)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 로그인 성공 시 여기서 jwt 토큰 만들어 header에 추가하여 응답
@Override
protected void successfulAuthentication (HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
String username = ((User)authResult.getPrincipal()).getUsername();
UserDto userDetails = userService.getUserDetailsByUsername(username);
try {
// jwt 토큰 생성
Key key = Keys.hmacShaKeyFor(Objects.requireNonNull(env.getProperty("token.secret")).getBytes(StandardCharsets.UTF_8));
String token = Jwts.builder()
.setSubject(userDetails.getId())
.claim("auth", userDetails.getRole())
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(Objects.requireNonNull(env.getProperty("token.expiration_time")))))
.signWith(key, SignatureAlgorithm.HS512)
.compact();
// response의 header에 토큰 추가
response.addHeader("token", token);
response.addHeader("userId", userDetails.getId());
} catch (Exception e) {
log.error("token create fail - cause : {} , msg : {}", e.getCause(), e.getMessage());
}
}
}
4. jwt 토큰이 유효한지 검증하는 필터 추가
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.*;
import java.util.stream.Collectors;
import static org.springframework.security.config.Elements.JWT;
@Slf4j
public class JwtAuthenticationTokenFilter extends BasicAuthenticationFilter {
private UserRepository userRepository;
private Environment env;
public JwtAuthenticationTokenFilter(AuthenticationManager authenticationManager, UserRepository userRepository, Environment env) {
super(authenticationManager);
this.userRepository = userRepository;
this.env = env;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String jwtToken = request.getHeader("Authorization");
if(jwtToken == null || !jwtToken.startsWith(Objects.requireNonNull(env.getProperty("token.pre_fix")))){
chain.doFilter(request, response);
return;
}
Authentication authentication = getUsernamePasswordAuthentication(jwtToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Continue filter execution
chain.doFilter(request, response);
}
private Authentication getUsernamePasswordAuthentication(String jwtToken) {
if(jwtToken != null && !jwtToken.isEmpty()){
jwtToken = jwtToken.replace("Bearer", "").trim();
return getAuthentication(jwtToken);
}
return null;
}
public Authentication getAuthentication(String jwtToken) {
Key key = Keys.hmacShaKeyFor(Objects.requireNonNull(env.getProperty("token.secret")).getBytes(StandardCharsets.UTF_8));
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jwtToken)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get("auth").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, jwtToken, authorities);
}
}
반응형
'스프링' 카테고리의 다른 글
[Spring] RestTemplate 사용법 (1) | 2023.10.10 |
---|---|
[Spring] 스프링부트 RabbitMQ 연동하기 (1) | 2023.06.14 |
[Spring] Spring Cloud Gateway 사용법 (0) | 2023.01.01 |
[Spring] Spring Cloud Netflix Eureka (1개의 PC에서 여러 인스턴스 등록하기) (0) | 2022.12.29 |
[Spring] Spring Cloud Netflix Eureka 란? (0) | 2022.12.28 |