ํ์๊ฐ์ /๋ก๊ทธ์ธ ๊ตฌํ
Filter
'Spring security' framwork
1-1)ํ์๊ฐ์ ๊ตฌํ
- build.gradle
JPA, MySQL ์ถ๊ฐ
// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// MySQL
runtimeOnly 'com.mysql:mysql-connector-j'
+ application.properties / ์ฌ์ฉํ DB ์ฐ๊ฒฐ
- ํจ์ค์๋ ์ํธํ ํ์
- Spring security์์ ์ ๊ณตํ๋ ์ํธํ ๋ฉ์๋ ์ฌ์ฉํด์ค๋ค.(์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ํธ๋ ์ํธํ๋ ์ํธ๋ฅผ ๋น๊ตํด ์ผ์น์ฌ๋ถ๋ฅผ ํ์ธํด์ฃผ๋ ๊ธฐ๋ฅ๋ ์์ด ๋งค์ฐ ํธ๋ฆฌํ๋ค.)
import java.util.Optional;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
// ADMIN_TOKEN
private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";
public void signup(SignupRequestDto requestDto) {
String username = requestDto.getUsername();
String password = passwordEncoder.encode(requestDto.getPassword());
// ํ์ ์ค๋ณต ํ์ธ
Optional<User> checkUsername = userRepository.findByUsername(username);
if (checkUsername.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ ์ฌ์ฉ์๊ฐ ์กด์ฌํฉ๋๋ค.");
}
// email ์ค๋ณตํ์ธ
String email = requestDto.getEmail();
Optional<User> checkEmail = userRepository.findByEmail(email);
if (checkEmail.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ Email ์
๋๋ค.");
}
// ์ฌ์ฉ์ ROLE ํ์ธ
UserRoleEnum role = UserRoleEnum.USER;
if (requestDto.isAdmin()) {
if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
throw new IllegalArgumentException("๊ด๋ฆฌ์ ์ํธ๊ฐ ํ๋ ค ๋ฑ๋ก์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
}
role = UserRoleEnum.ADMIN;
}
// ์ฌ์ฉ์ ๋ฑ๋ก
User user = new User(username, password, email, role);
userRepository.save(user);
}
}
1-2)๋ก๊ทธ์ธ ๊ตฌํ
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.jwtUtil = jwtUtil;
}
// ADMIN_TOKEN
private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";
public void signup(SignupRequestDto requestDto) {
String username = requestDto.getUsername();
String password = passwordEncoder.encode(requestDto.getPassword());
// ํ์ ์ค๋ณต ํ์ธ
Optional<User> checkUsername = userRepository.findByUsername(username);
if (checkUsername.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ ์ฌ์ฉ์๊ฐ ์กด์ฌํฉ๋๋ค.");
}
// email ์ค๋ณตํ์ธ
String email = requestDto.getEmail();
Optional<User> checkEmail = userRepository.findByEmail(email);
if (checkEmail.isPresent()) {
throw new IllegalArgumentException("์ค๋ณต๋ Email ์
๋๋ค.");
}
// ์ฌ์ฉ์ ROLE ํ์ธ
UserRoleEnum role = UserRoleEnum.USER;
if (requestDto.isAdmin()) {
if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
throw new IllegalArgumentException("๊ด๋ฆฌ์ ์ํธ๊ฐ ํ๋ ค ๋ฑ๋ก์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
}
role = UserRoleEnum.ADMIN;
}
// ์ฌ์ฉ์ ๋ฑ๋ก
User user = new User(username, password, email, role);
userRepository.save(user);
}
public void login(LoginRequestDto requestDto, HttpServletResponse res) {
String username = requestDto.getUsername();
String password = requestDto.getPassword();
// ์ฌ์ฉ์ ํ์ธ
User user = userRepository.findByUsername(username).orElseThrow(
() -> new IllegalArgumentException("๋ฑ๋ก๋ ์ฌ์ฉ์๊ฐ ์์ต๋๋ค.")
);
// ๋น๋ฐ๋ฒํธ ํ์ธ
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new IllegalArgumentException("๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
}
// JWT ์์ฑ ๋ฐ ์ฟ ํค์ ์ ์ฅ ํ Response ๊ฐ์ฒด์ ์ถ๊ฐ
String token = jwtUtil.createToken(user.getUsername(), user.getRole());
jwtUtil.addJwtToCookie(token, res);
}
}
2) ํํฐ
- web application ์์ ๊ด๋ฆฌ๋๋ ์์ญ์ผ๋ก ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์ค๋ ์์ฒญ๊ณผ ์๋ต์ ๋ํ ์ต์ด/์ต์ข ๋จ๊ณ์ ์๋ค.
- ๊ทธ๋ฆผ์์๋ ๋ณผ ์ ์๋ฏ DispatcherServlet ๋ณด๋ค ์์ ์์นํ๋ค.
- ๊ตณ์ด ํํฐ๋ฅผ ์ฐ๋ ์ด์ ๋? ์ธ์ฆ/์ธ๊ฐ์ ๊ด๋ จํ ๋ด์ฉ์ ๋น์ง๋์ค ๋ก์ง(Service)์์ ๋ถ๋ฆฌํ ์ ์์!
- ํํฐ๋ ๋ฑ ํ๊ฐ๋ง ์กด์ฌํ๋๊ฒ ์๋๋ผ ์ฌ๋ฌ๊ฐ ์กด์ฌ ๊ฐ๋ฅ. ์ฌ๋ฌ๊ฐ๋ฅผ chain ํํ๋ก ๋ฌถ์ด ์ฒ๋ฆฌ ๊ฐ๋ฅํจ.
<ํํฐ ์ฌ์ฉ ์>
# LoggingFilter: URL ๋ก๊น filter (1)
# AuthFilter: ์ธ์ฆ/์ธ๊ฐ ์ฒ๋ฆฌ filter (2)
- ์ฒด์ธ์ผ๋ก ๋ฌถ์ฌ์๋ ํํ๋ก ๊ตฌํํ๋ฉฐ @Order(1) ํด๋น ์ ๋ํ ์ด์ ๊ณผ ๋ฒํธ๋ฅผ ๋ฃ์ด ํํฐ์ ์์๋ฅผ ๋ถ์ฌํด์ค ์ ์๋ค.
- ๋ ๊ฐ์ง ๋ชจ๋ Filter interface๋ฅผ implements ํ๋ค. ์๋์ผ๋ก bean ๋ฑ๋กํด์ค๋ค.(@Component ์ฌ์ฉ)
1. LoggingFilter
@Slf4j(topic = "LoggingFilter")
@Component
@Order(1)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// ์ ์ฒ๋ฆฌ
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
log.info(url);
chain.doFilter(request, response); // ๋ค์ Filter ๋ก ์ด๋
// ํ์ฒ๋ฆฌ
log.info("๋น์ฆ๋์ค ๋ก์ง ์๋ฃ");
}
}
2. AuthFilter
@Slf4j(topic = "AuthFilter")
@Component
@Order(2)
public class AuthFilter implements Filter {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
// ํ์๊ฐ์
, ๋ก๊ทธ์ธ ๊ด๋ จ API ๋ ์ธ์ฆ ํ์์์ด ์์ฒญ ์งํ
chain.doFilter(request, response); // ๋ค์ Filter ๋ก ์ด๋
} else {
// ๋๋จธ์ง API ์์ฒญ์ ์ธ์ฆ ์ฒ๋ฆฌ ์งํ
// ํ ํฐ ํ์ธ
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
if (StringUtils.hasText(tokenValue)) { // ํ ํฐ์ด ์กด์ฌํ๋ฉด ๊ฒ์ฆ ์์
// JWT ํ ํฐ substring
String token = jwtUtil.substringToken(tokenValue);
// ํ ํฐ ๊ฒ์ฆ
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// ํ ํฐ์์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // ๋ค์ Filter ๋ก ์ด๋
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}
3) Spring security
* ์ธ์ฆ/์ธ๊ฐ ํธ๋ฆฌํ๊ฒ ํด์ค๋ค.
* session ๋ฐฉ์์ผ๋ก ๋์!
* ๋ํดํธ ๋ก๊ทธ์ธ ์ ๊ณต
* filter chain ๋ฐฉ์์ผ๋ก ๋์
<์ฌ์ฉ๋ฐฉ๋ฒ>
- spring-security ํ๋ ์์ํฌ ์ถ๊ฐ
- spring-security ์ค์
package com.sparta.springauth.config;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity // Spring Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf((csrf) -> csrf.disable());
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // resources ์ ๊ทผ ํ์ฉ
.anyRequest().authenticated() // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ ์ธ์ฆ์ฒ๋ฆฌ
);
// ๋ก๊ทธ์ธ ์ฌ์ฉ
http.formLogin(Customizer.withDefaults());
return http.build();
}
}
- ์์ filter์ ๋น๊ตํด๋ณด๋ฉด ์ธ์ฆ, ์ธ๊ฐ๋ฅผ ์์ฃผ ๊ฐํธํ๊ฒ ์ฒ๋ฆฌํ๊ณ ์๋ค.
^ Spring security - filter chain
- Spring์์ ๋ชจ๋ ํธ์ถ์ DispatcherrServlet์ ํต๊ณผ ํ -> Controller๋ก ๋ถ๋ฐฐ
- ์ด๋ ๊ฐ ์์ฒญ์ ๋ํด ๊ณตํต์ ์ผ๋ก ์ฒ๋ฆฌํด์ผํ ๋ DispatcherrServlet ์ด์ ๋จ๊ณ๊ฐ ํ์ -> ๊ทธ๊ฒ์ด Filter
- Spring security๋ ์ธ์ฆ ๋ฐ ์ธ๊ฐ ์ฒ๋ฆฌ๋ฅผ ์ํด filter ์ฌ์ฉ!
- Spring security๋ FilterChainProxy๋ฅผ ํตํด ์์ธ๋ก์ง์ ๊ตฌํํ๊ณ ์๋ค!
1) ๋ก๊ทธ์ธ(์ธ์ )
2) ๋ก๊ทธ์ธ(JWT) - ๋น์ง๋์ค ๋ก์ง๊ณผ ์ธ์ฆ/์ธ๊ฐ๋ฅผ ๋ถ๋ฆฌํด์ฃผ๊ธฐ
2-1) JwtAuthenticaionFilter: ๋ก๊ทธ์ธ ์งํ ๋ฐ JWT ์์ฑ
: Dispatcher Servlet ์ง๋ Controller ์ดํ์์ business logic ์๋ ๊ณณ์์ ์ธ์ฆ/์ธ๊ฐ ํ๋๊ฒ ์๋ filter ๋จ๊ณ์์ ์ธ์ฆ/์ธ๊ฐ ์ฒ๋ฆฌ
@Slf4j(topic = "๋ก๊ทธ์ธ ๋ฐ JWT ์์ฑ")
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
setFilterProcessesUrl("/api/user/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("๋ก๊ทธ์ธ ์๋");
try {
LoginRequestDto requestDto = new ObjectMapper().readValue(request.getInputStream(), LoginRequestDto.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
requestDto.getUsername(),
requestDto.getPassword(),
null
)
);
} catch (IOException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("๋ก๊ทธ์ธ ์ฑ๊ณต ๋ฐ JWT ์์ฑ");
String username = ((UserDetailsImpl) authResult.getPrincipal()).getUsername();
UserRoleEnum role = ((UserDetailsImpl) authResult.getPrincipal()).getUser().getRole();
String token = jwtUtil.createToken(username, role);
jwtUtil.addJwtToCookie(token, response);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
log.info("๋ก๊ทธ์ธ ์คํจ");
response.setStatus(401);
}
}
2-2) JwtAuthorizationFilter: API์ ์ ๋ฌ๋๋ JWT ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐ ์ธ๊ฐ ์ฒ๋ฆฌ -> ๋ค์ด์จ JWT์ ๊ฒ์ฆ
@Slf4j(topic = "JWT ๊ฒ์ฆ ๋ฐ ์ธ๊ฐ")
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsServiceImpl userDetailsService;
public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
String tokenValue = jwtUtil.getTokenFromRequest(req);
if (StringUtils.hasText(tokenValue)) {
// JWT ํ ํฐ substring
tokenValue = jwtUtil.substringToken(tokenValue);
log.info(tokenValue);
if (!jwtUtil.validateToken(tokenValue)) {
log.error("Token Error");
return;
}
Claims info = jwtUtil.getUserInfoFromToken(tokenValue);
try {
setAuthentication(info.getSubject());
} catch (Exception e) {
log.error(e.getMessage());
return;
}
}
filterChain.doFilter(req, res);
}
// ์ธ์ฆ ์ฒ๋ฆฌ
public void setAuthentication(String username) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = createAuthentication(username);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
}
// ์ธ์ฆ ๊ฐ์ฒด ์์ฑ
private Authentication createAuthentication(String username) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
-> ์ด ์ค์ต์ token์ ์ง์ ๊ฒ์ฆํ๊ณ ์ง์ ์ธ์ฆ ๊ฐ์ฒด ๋ง๋ค์ด์ SecurityController ์ ๋ฃ์ด์ค์ผํจ...
=> ์ฝ๋ ๋ถ์์ ์ด์ฌํ ํ์ ์์ฐจ ์์ฐจ ...
'Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
TIL Spring #3-3 (0) | 2023.12.18 |
---|---|
TIL Spring #3-1 (0) | 2023.12.14 |
TIL Spring #2-5 (0) | 2023.12.13 |
TIL Spring #2-4 (0) | 2023.12.12 |
TIL Spring #2-3 (0) | 2023.12.12 |