Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- MSA
- Reduxpender
- axios
- 리액트
- SpringBoot
- vue
- cheerio
- Filter
- REACT
- Spring REST Docs
- MFA
- gradle
- Spring Security
- UsernamePasswordAuthenticationFilter
- openapi3
- Spring Batch
- OpenStack
- Flyway
- SWAGGER
- stopPropogation
- SpringRESTDocs
- cloud native
- Pender
- tasklet
- AuthenticatoinProvide
- preventdefault
- vuejs
- Crawling
- T-OTP
- JavaScript
Archives
- Today
- Total
Miracle Morning, LHWN
19. Spring Security 를 활용하여 OpenStack 연동해보기 본문
# 먼저 Spring Web 설정을 한다.
// configure/WebMvcConfiguration.java
package com.spring.openstack.configure;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css");
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
}
}
/static/** 패턴으로 요청이 들어오면 자동으로 Resource 하위의 /static/ 으로 매핑시킨다. Build 가 완료되면 Resource 폴더는 Root 하위 폴더에 위치한다.
(※ 여기서 classpath:/ 는 Resource/ 경로와 매핑된다.)
// configure/WebMvcConfiguration.java
package com.spring.openstack.configure;
import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/i18n/**")
.antMatchers("/static/**")
.antMatchers("/css/**")
.antMatchers("/js/**")
.antMatchers("/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin()
.and().formLogin().loginPage("/login")
.and().logout().logoutUrl("/logout")
.and().authorizeRequests().antMatchers("/login", "/").permitAll()
.and().authorizeRequests().anyRequest().authenticated();
http.csrf().disable();
}
}
web.ignoring() 을 통해 인증을 하지 않고도 접근 가능하도록 설정해준다.
/login 경로로 접근하면 로그인 페이지가 나오도록 설정해주고,
/login 경로와 / (Root) 경로는 모두 접근 가능하도록 설정한다. 그 외의 모든 Request (anyRequest()) 는 인증을 해야한다.
다음은 Filter 구현이다.
전체적인 Spring Security 구조에서 Filter, Manager, Provider 는 인증에 대한 것만 구현하고,
인증에 성공/실패했을 경우에 대한 로직은 Handler 에서 처리하는 형태를 권장한다.
// OpenStackFilter.java
package com.spring.openstack.configure.filters;
import org.openstack4j.api.exceptions.ClientResponseException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
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 org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class OpenStackFilter extends UsernamePasswordAuthenticationFilter {
private boolean postOnly = true;
private SessionAuthenticationStrategy sessionAuthenticationStrategy = new NullAuthenticatedSessionStrategy();
private boolean continueChainBeforeSuccessfulAuthentication = false;
public String obtainDomain(HttpServletRequest httpServletRequest) { return (String)httpServletRequest.getParameter("domain"); }
public OpenStackFilter() {
}
public OpenStackFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
@Override
public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) {
this.sessionAuthenticationStrategy = sessionAuthenticationStrategy;
}
@Override
public void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
// POST 로 넘어온 username, password 를 추출하여 인증을 시도하는 부분이다.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
String domain = obtainDomain(request);
domain = (domain != null) ? domain : "";
domain = domain.trim();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
usernamePasswordAuthenticationToken.setDetails(domain);
return this.getAuthenticationManager().authenticate(usernamePasswordAuthenticationToken);
}
private boolean checkExpire(String tokenExpire) throws ParseException {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
Date tokenExpireDateTime = dateFormat.parse(tokenExpire);
return tokenExpireDateTime.after(new Date()); // 현재 시간과 비교하여 tokenExpireDateTime 이 더 뒤에 있으면 true 이다. 즉, 아직 만료되지 않았을 경우 true 를 반환.
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 인증이 필요없으면 doFilter 를 통해 다음 Chain 이 실행되도록 한다.
if(!requiresAuthentication(httpServletRequest, httpServletResponse)) {
chain.doFilter(request, response);
return;
}
// Session 을 가져와서 그 안에 있는 token 정보를 가져온다.
HttpSession httpSession = httpServletRequest.getSession();
String tokenId = (String)httpSession.getAttribute("unscopedTokenId");
String tokenExpire = (String)httpSession.getAttribute("tokenExpire");
// 이미 인증이 된 경우이다.
if(tokenId != null && tokenExpire != null) {
try {
if(checkExpire(tokenExpire)) {
//TODO : 인증객체 구현 필요
chain.doFilter(httpServletRequest, httpServletResponse);
} else {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, new AuthenticationServiceException("Token has been expired."));
}
} catch (ClientResponseException clientResponseException) {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, new AuthenticationServiceException("Token Id is invalidated."));
} catch (ParseException parseException) {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, new AuthenticationServiceException("Token expire date time format invalidated."));
}
} else {
// 아직 인증이 되지 않은 경우이다. (토큰이 없으면 POST 로 넘어온 username, password, domain 으로 인증을 진행한다.)
if(this.postOnly && httpServletRequest.getMethod().equals("POST")) {
String username = this.obtainUsername(httpServletRequest);
String password = this.obtainPassword(httpServletRequest);
String domain = this.obtainDomain(httpServletRequest);
if(username != null && password != null && domain != null) {
try {
Authentication authenticationResult = attemptAuthentication(httpServletRequest, httpServletResponse);
if(authenticationResult == null) {
return;
}
this.sessionAuthenticationStrategy.onAuthentication(authenticationResult, httpServletRequest, httpServletResponse);
if(this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(httpServletRequest, httpServletResponse);
}
successfulAuthentication(httpServletRequest, httpServletResponse, chain, authenticationResult);
} catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, internalAuthenticationServiceException);
} catch (AuthenticationServiceException authenticationServiceException) {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, authenticationServiceException);
}
} else {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, new AuthenticationServiceException("Bye bye."));
}
} else {
unsuccessfulAuthentication(httpServletRequest, httpServletResponse, new AuthenticationServiceException("Bye bye."));
}
}
}
}
'IT 기술 > [JAVA] Spring Boot' 카테고리의 다른 글
20. Spring Batch 를 활용한 데이터 일괄 처리 (0) | 2021.06.25 |
---|---|
18-2. Spring Security 에 대해 (2) (0) | 2021.06.15 |
18-1. Spring Security 에 대해 (1) (0) | 2021.06.01 |
17. MSA 와 JWT (1) | 2021.05.31 |
16-2. Spring REST Docs - 실제 사용해보기 (0) | 2021.05.29 |
Comments