💡 스프링 시큐리티 시작하기(기본세팅)
1. dependency 추가
<Maven 이용>
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
</dependencies>
<Gradle 이용>
dependencies {
compile 'org.springframework.security:spring-security-web:4.2.2.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.2.RELEASE'
}
버전은 사용하는 프로젝트에 맞게 바꿔주시면 됩니다.
2. Java Configuration
WebSecurityConfigurerAdapter를 상속받은 config 클래스에 @EnableWebSecurity 어노테이션을 달면SpringSecurityFilterChain이 자동으로 포함됩니다.
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}
작성한 뒤, configure 를 오버라이딩 하여 접근 권한을 작성합니다.
@EnableWebSecurity //스프링시큐리티 사용을 위한 어노테이션선언
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
//WebSecurityConfigurerAdapter 상속
private AuthenticationProvider authenticationProvider;
public SpringSecurityConfig(/*UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,*/
AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
/*
* 스프링 시큐리티가 사용자를 인증하는 방법이 담긴 객체.
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
/*
* AuthenticationProvider 구현체
*/
auth.authenticationProvider(authenticationProvider);
// auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
/*
* 스프링 시큐리티 룰을 무시하게 하는 Url 규칙(여기 등록하면 규칙 적용하지 않음)
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/resources/**")
.antMatchers("/css/**")
.antMatchers("/vendor/**")
.antMatchers("/js/**")
.antMatchers("/favicon*/**")
.antMatchers("/img/**")
;
}
/*
* 스프링 시큐리티 규칙
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//보호된 리소스 URI에 접근할 수 있는 권한을 설정
.antMatchers("/login*/**").permitAll() //전체 접근 허용
.antMatchers("/logout/**").permitAll()
.antMatchers("/myPage").hasRole("ADMIN")//admin이라는 롤을 가진 사용자만 접근 허용
.antMatchers("/chatbot/**").permitAll()
.anyRequest().authenticated()
.and().logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler())
.and().csrf()//csrf 보안 설정을 비활성화
.disable()//해당 기능을 사용하기 위해서는 프론트단에서 csrf토큰값 보내줘야함
.addFilter(jwtAuthenticationFilter())//Form Login에 사용되는 custom AuthenticationFilter 구현체를 등록
.addFilter(jwtAuthorizationFilter())//Header 인증에 사용되는 BasicAuthenticationFilter 구현체를 등록
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
;
}
/*
* SuccessHandler bean register
*/
@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
CustomAuthenticationSuccessHandler successHandler = new CustomAuthenticationSuccessHandler();
successHandler.setDefaultTargetUrl("/index");
return successHandler;
}
/*
* FailureHandler bean register
*/
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
CustomAuthenticationFailureHandler failureHandler = new CustomAuthenticationFailureHandler();
failureHandler.setDefaultFailureUrl("/loginPage?error=error");
return failureHandler;
}
/*
* LogoutSuccessHandler bean register
*/
@Bean
public LogoutSuccessHandler logoutSuccessHandler() {
CustomLogoutSuccessHandler logoutSuccessHandler = new CustomLogoutSuccessHandler();
logoutSuccessHandler.setDefaultTargetUrl("/loginPage?logout=logout");
return logoutSuccessHandler;
}
/*
* AccessDeniedHandler bean register
*/
@Bean
public AccessDeniedHandler accessDeniedHandler() {
CustomAccessDeniedHandler accessDeniedHandler = new CustomAccessDeniedHandler();
accessDeniedHandler.setErrorPage("/error/403");
return accessDeniedHandler;
}
/*
* AuthenticationEntryPoint bean register
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new CustomAuthenticationEntryPoint("/loginPage?error=e");
}
/*
* Form Login시 걸리는 Filter bean register
*/
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/login");
jwtAuthenticationFilter.setUsernameParameter("username");
jwtAuthenticationFilter.setPasswordParameter("password");
jwtAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
jwtAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
jwtAuthenticationFilter.afterPropertiesSet();
return jwtAuthenticationFilter;
}
/*
* Filter bean register
*/
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() throws Exception {
JwtAuthorizationFilter jwtAuthorizationFilter = new JwtAuthorizationFilter(authenticationManager());
return jwtAuthorizationFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {//간단하게 비밀번호 암호화
return new BCryptPasswordEncoder();
}
}
여기 사용된 BCryptPasswordEncoder 암호화에 대해서는 추가로 포스팅하도록 하겠습니다.
📌 다음은 antMatchers() 로 지정할 수 있는 항목들입니다.
- hasRole() or hasAnyRole()
특정 권한을 가지는 사용자만 접근할 수 있습니다. - hasAuthority() or hasAnyAuthority()
특정 권한을 가지는 사용자만 접근할 수 있습니다. - hasIpAddress()
특정 아이피 주소를 가지는 사용자만 접근할 수 있습니다. - permitAll() or denyAll()
접근을 전부 허용하거나 제한합니다. - rememberMe()
리멤버 기능을 통해 로그인한 사용자만 접근할 수 있습니다. - anonymous()
인증되지 않은 사용자가 접근할 수 있습니다. - authenticated()
인증된 사용자만 접근할 수 있습니다.
Role은 역할이고 Authority는 권한이지만 사실은 표현의 차이입니다.
Role은 “ADMIN”으로 표현하고 Authority는 “ROLE_ADMIN”으로 표기합니다.
3.UserDetails 구현
user detail 을 구현할 때는 UserDetails 를 Implements 해줍니다.
1 @Slf4j
2 @Service
3 public class CustomUserDetailService implements AuthenticationUserDetailsService<Authentication> {
4
5 @Override
6 public UserDetails loadUserDetails(Authentication token) {
7 User user = (User) token.getPrincipal();
8
9 if (user == null) {
10 throw new PreAuthenticatedCredentialsNotFoundException("USER IS NULL");
11 }
12
13 // DB에 접근해서 직접 정보를 가져오는게 일반적입니다.
14
15 return new CustomUserDetails().setUser(user).setGrantedAuthorities(user.getAuthorities());
16 }
17
18 }
서비스를 구현할 때는 AuthenticationUserDetailsService 를 implement합니다.
4. AuthenticationSuccessHandler 구현
Form Login(AuthenticationFilter)에서 인증이 성공했을 때 수행될 핸들러
SimpleUrlAuthenticationSuccessHandler를 상속받아 클래스를 구현합니다.
추상클래스가 아니라도 인터페이스형태로도 가능합니다.
성공시 인증토큰을 쿠키에 넣어주거나, index 페이지로 리다이렉트 하는 역할을 수행합니다.
@Slf4j
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler implements ExceptionProcessor{
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
log.debug("CustomAuthenticationSuccessHandler.onAuthenticationSuccess ::::");
/*
* 쿠키에 인증 토큰을 넣어준다.
*/
super.onAuthenticationSuccess(request, response, authentication);
}
@Override
public void makeExceptionResponse(HttpServletRequest request, HttpServletResponse response,
Exception exception) {
}
}
5. AuthenticationFailureHandler 구현
Form Login 실패시 수행되는 핸들러
SimpleUrlAuthenticationFailureHandler를 상속받으며, 실패시 로그인 페이지로 리다이렉트 하는 역할을 수행합니다.
@Slf4j
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler implements ExceptionProcessor{
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.debug("CustomAuthenticationFailureHandler.onAuthenticationFailure ::::");
super.onAuthenticationFailure(request, response, exception);
}
@Override
public void makeExceptionResponse(HttpServletRequest request, HttpServletResponse response,
Exception exception) {
}
}
6. LogoutSuccessHandler 구현
로그아웃에 성공했을 시 수행되는 핸들러
SimpleUrlLogoutSuccessHandler를 상속하며,로그아웃 성공시 로그인 페이지로 리다이렉트 하는 역할을 수행합니다.
@Slf4j
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler{
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
log.debug("CustomLogoutSuccessHandler.onLogoutSuccess ::::");
super.onLogoutSuccess(request, response, authentication);
}
}
7. JwtAuthenticationFilter 구현
Form Login시 걸리는 Filter
UsernamePasswordAuthenticationFilter를 상속하며, 사용자가 Form으로 입력한 로그인 정보를 인터셉트해서 AuthenticationManager에게 Authentication 객체를 넘겨줍니다.
@Slf4j
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter{
private boolean postOnly = true;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
/*
* 해당 필터에서 인증 프로세스 이전에 요청에서 사용자 정보를 가져와서
* Authentication 객체를 인증 프로세스 객체에게 전달하는 역할
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
log.debug("JwtAuthentication.attemptAuthentication ::::");
/*
* POST로 넘어왔는지 체크
*/
if(postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if(StringUtils.isEmpty(username)) {
username = "";
}
if(StringUtils.isEmpty(password)) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
스프링 시큐리티는 사용자를 인증하고, 로그인 후 애플리케이션의 각 기능들에 대한 권한을 부여하는 기능을 구현하는데 사용되는 프레임워크로, 각 핸들러, 필터들을 거쳐 동작한다는 것이 포인트입니다. 스프링시큐리티에는 많은 클래스를 상속받고 복잡하게 구성되어 있어, 도식화된 그림을 보면 전체적인 구조나 흐름을 이해하는데 도움이 될 것 같습니다.
출처: https://devuna.tistory.com/59 [튜나 개발일기:티스토리]
'Program > Spring Framework' 카테고리의 다른 글
[SpringSecurity] Access Token & Refresh Token (0) | 2022.07.26 |
---|---|
[SpringFrameWork] @RestControllerAdvice를 사용해보자 (0) | 2022.06.24 |
[Spring] Interceptor (0) | 2022.04.20 |
[Spring] @Transactional (0) | 2022.04.20 |
[SpringBoot] @Controller VS @RestController (0) | 2022.04.20 |