.antMatchers("/api/v1/test/auth").authenticated()
에서 authenticated()
이 인증 여부에 따라 401Error를 뱉으면 AuthenticationEntryPoint
에서 처리한다.package com.codesign.base.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();
// Response에 401이 떨어질만한 에러가 발생할 경우 해당로직을 타게되어, commence라는 메소드를 실행하게됩니다.
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.error("UnAuthorized!!! message : {}", authException.getMessage());
Map<String, Object> responseMap = new HashMap<String,Object>();
responseMap.put("message", "UnAuthorized!!!");
responseMap.put("status", HttpStatus.UNAUTHORIZED.value());
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
try(ServletOutputStream os = response.getOutputStream()){
objectMapper.writeValue(os, responseMap);
os.flush();
}
}
}
속성 | 설명 |
---|---|
AuthenticationEntryPoint | 인증 실패시 401Error가 떨어질 경우 처리 위한 클래스 |
commence() | request, response, AuthenticationException을 인자로 받고, 인증 실패시 로직 작성 |
os.flush() | 스트림 버퍼에 저장되어 있는 데이터를 강제적으로 출력시킴 기본적인 출력 스트림은 버퍼에 데이터가 가득 차면 그때 데이터를 출력시키는데 이 메서드를 사용하면 저장된 데이터의 크기에 관계없이 바로 출력 |
@Autowired
SecurityAuthenticationEntryPoint authenticationEntryPoint;
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
체인 추가
전체 SecurityConfig.java
package com.codesign.base.configure;
import com.codesign.base.security.SecurityAuthenticationEntryPoint;
import com.codesign.base.security.filter.SecurityAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SecurityAuthenticationEntryPoint authenticationEntryPoint;
@Bean
public SecurityAuthenticationFilter securityAuthenticationFilter(){
return new SecurityAuthenticationFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/v1/test/permit-all").permitAll()
.antMatchers("/api/v1/test/auth").authenticated()
// .antMatchers("/**").authenticated()
// .anyRequest().permitAll().and()
.anyRequest().authenticated().and()
.formLogin().disable()
;
http.addFilterBefore(securityAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
package com.codesign.base.security.filter;
import com.codesign.base.security.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SecurityAuthenticationFilter extends OncePerRequestFilter {
// TODO 수정자 필드 주입다시 확인해보기
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// String username = request.getParameter("username");
String requestUsername = "test";
String requestPassword = request.getParameter("password"); // "test" - 성공 시나리오, 나머지는 실패
UserDetails authenticatedUser = customUserDetailsService.loadUserByUsername(requestUsername);
UsernamePasswordAuthenticationToken authToken = null;
// 여기에서 authenticatedUser와 request에 담긴 username과 password를 비교하는 로직
if(requestUsername.equals(authenticatedUser.getUsername()) && requestPassword.equals(authenticatedUser.getPassword())){
// 아이디, 패스워드 일치
authToken = new UsernamePasswordAuthenticationToken(authenticatedUser.getUsername(), null, null);
}else{
// 아이디, 패스워드 불일치
authToken = new UsernamePasswordAuthenticationToken(authenticatedUser.getUsername(), null);
}
// 인증시키고, 토큰을 SecurityContext에 저장
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
}
속성 | 설명 |
---|---|
new UsernamePasswordAuthenticationToken(authenticatedUser.getUsername(), null, null); | UsernamePasswordAuthenticationToken 내부 생성자 로직 - 인증 O super.setAuthenticated(true); |
new UsernamePasswordAuthenticationToken(authenticatedUser.getUsername(), null); | UsernamePasswordAuthenticationToken 내부 생성자 로직 - 인증 X super.setAuthenticated(false); |
/api/v1/test/auth?password=test
/api/v1/test/auth?password=failure
password=test만 통과되고, 나머지는 401 Error!
.antMatchers("/api/v1/test/auth").hasRole("AUTH")
에 hasRole()
이 세부 인가 여부에 따라 403Error를 뱉으면 AccessDeniedHandler
에서 처리한다.package com.codesign.base.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
final ObjectMapper objectMapper = new ObjectMapper();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.error("Forbidden error: {}", accessDeniedException.getMessage());
Map<String, Object> responseMap = new HashMap<>();
responseMap.put("statue", HttpStatus.FORBIDDEN.value());
responseMap.put("message", "Forbidden error ");
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.FORBIDDEN.value());
try(ServletOutputStream os = response.getOutputStream()){
objectMapper.writeValue(os, responseMap);
os.flush();
}
}
}
securityConfig.java
@Autowired
CustomAccessDeniedHandler customAccessDeniedHandler;
accessDeniedHandler() 추가
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(customAccessDeniedHandler).and()
hasRole("AUTH")로 수정
.antMatchers("/api/v1/test/auth").hasRole("AUTH")
package com.codesign.base.configure;
import com.codesign.base.security.CustomAccessDeniedHandler;
import com.codesign.base.security.SecurityAuthenticationEntryPoint;
import com.codesign.base.security.filter.SecurityAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SecurityAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
CustomAccessDeniedHandler customAccessDeniedHandler;
@Bean
public SecurityAuthenticationFilter securityAuthenticationFilter(){
return new SecurityAuthenticationFilter();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(customAccessDeniedHandler).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/v1/test/permit-all").permitAll()
// .antMatchers("/api/v1/test/auth").authenticated()
.antMatchers("/api/v1/test/auth").hasRole("AUTH")
// .antMatchers("/**").authenticated()
// .anyRequest().permitAll().and()
.anyRequest().authenticated().and()
.formLogin().disable()
;
http.addFilterBefore(securityAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
.hasRole('AUTH')
추가로 권한이 없어서 403 Error!SecurityUser.java
public class SecurityUser implements UserDetails {
private String username;
private Collection<? extends GrantedAuthority> authorities;
public SecurityUser(String username, List<String> roles) {
this.username = username;
this.authorities = Optional.ofNullable(roles)
.orElse(Collections.emptyList())
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
... 이하 동일
SecurityAuthenticationFilter.java
authenticatedUser.getAuthorities()
넣어주기 if(requestUsername.equals(authenticatedUser.getUsername()) && requestPassword.equals(authenticatedUser.getPassword())){
// 아이디, 패스워드 일치
authToken = new UsernamePasswordAuthenticationToken(authenticatedUser.getUsername(), null, authenticatedUser.getAuthorities());
}else{
// 아이디, 패스워드 불일치
authToken = new UsernamePasswordAuthenticationToken(authenticatedUser.getUsername(), null);
}
CustomUserDetailsService.java
return new SecurityUser(username, Arrays.asList("ROLE_AUTH"));
권한을 받는 생성자로 변경package com.codesign.base.security.service;
import com.codesign.base.security.model.SecurityUser;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println(" UserDetailsService 인증을 받습니다. ");
// TODO id만으로 db에서 user정보를 조회
if (!username.equals("test")) throw new UsernameNotFoundException("해당 유저가 존재하지 않습니다.");
return new SecurityUser(username, Arrays.asList("ROLE_AUTH"));
// return new SecurityUser(username, Arrays.asList("ROLE_MNG"));
}
}
hasRole("AUTH")
=== ROLE_AUTH
>>> prefix - ROLE_
를 알아서 붙여준다.
// return new SecurityUser(username, Arrays.asList("ROLE_AUTH"));
return new SecurityUser(username, Arrays.asList("ROLE_MNG"));
참고 https://sas-study.tistory.com/362?category=784778
https://blog.naver.com/PostView.nhn?blogId=qjawnswkd&logNo=222303477758