Skip to content

Commit

Permalink
Merge pull request #16 from Team-UMC/feature/#15/SpringSecuritySetting
Browse files Browse the repository at this point in the history
[FEAT] Spring Security 설정
  • Loading branch information
junseokkim authored Jan 15, 2024
2 parents 34c28ba + ccd3478 commit ebbb81f
Show file tree
Hide file tree
Showing 16 changed files with 537 additions and 2 deletions.
17 changes: 17 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ repositories {
}

dependencies {
implementation 'io.jsonwebtoken:jjwt:0.9.1'

// com.sun.xml.bind
implementation 'com.sun.xml.bind:jaxb-impl:4.0.1'
implementation 'com.sun.xml.bind:jaxb-core:4.0.1'
// javax.xml.bind
implementation 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Expand All @@ -38,6 +46,15 @@ dependencies {

// s3 setting
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// 스프링 시큐리티를 사용하기 위한 스타터 추가
implementation 'org.springframework.boot:spring-boot-starter-security'

// 타임리프에서 스프링 시큐리티를 사용하기 위한 의존성 추가
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

// 스프링 시큐리티를 테스트하기 위한 의존성 추가
testImplementation 'org.springframework.security:spring-security-test'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.umc.networkingService.config;

import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
Expand All @@ -10,11 +12,27 @@
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {

// SecuritySecheme명
String jwtSchemeName = "jwtAuth";
// API 요청헤더에 인증정보 포함
SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
// SecuritySchemes 등록
Components components = new Components()
.addSecuritySchemes(jwtSchemeName, new SecurityScheme()
.name(jwtSchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("Bearer"));

return new OpenAPI()
.components(new Components())
.addSecurityItem(securityRequirement)
.components(components)
.info(apiInfo());

}



private Info apiInfo() {
return new Info()
.title("UMC 네트워킹 서비스 API 명세서")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.umc.networkingService.config.security;

import com.umc.networkingService.config.security.auth.CustomAccessDeniedHandler;
import com.umc.networkingService.config.security.jwt.JwtAuthenticationFilter;
import com.umc.networkingService.config.security.jwt.JwtExceptionFilter;
import com.umc.networkingService.config.security.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtTokenProvider jwtTokenProvider;
private final CustomAccessDeniedHandler customAccessDeniedHandler;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
UsernamePasswordAuthenticationFilter.class)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((authorizeRequests) ->
authorizeRequests
.requestMatchers("/staff/**").hasAnyRole("STAFF", "CENTERSTAFF", "BRANCHSTAFF", "CAMPUSSTAFF", "ADMIN")
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().permitAll()
)
.exceptionHandling((exceptionConfig) ->
exceptionConfig.accessDeniedHandler(customAccessDeniedHandler))
.addFilterBefore(new JwtExceptionFilter(),
JwtAuthenticationFilter.class);

return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.umc.networkingService.config.security.auth;

import org.springframework.security.core.annotation.AuthenticationPrincipal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member")
public @interface CurrentMember {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.umc.networkingService.config.security.auth;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (accessDeniedException != null) {
request.getRequestDispatcher("/access/denied").forward(request, response);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.umc.networkingService.config.security.auth;

import com.umc.networkingService.domain.member.entity.Member;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.ArrayList;
import java.util.Collection;


public class PrincipalDetails implements UserDetails {
private Member member;

public PrincipalDetails(Member member) {
this.member = member;
}

public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}

public Member getMember() {
return this.member;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(member.getRole().toString()));
return authorities;
}

@Override
public String getPassword() {
return encodePwd().encode("this is password");
}

@Override
public String getUsername() {
return member.getClientId();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return member.getDeletedAt() == null;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.umc.networkingService.config.security.auth;

import com.umc.networkingService.domain.member.entity.Member;
import com.umc.networkingService.domain.member.repository.MemberRepository;
import com.umc.networkingService.global.common.exception.ErrorCode;
import com.umc.networkingService.global.common.exception.RestApiException;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {

private final MemberRepository memberRepository;

@Override
public PrincipalDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Member memberEntity = memberRepository.findById(UUID.fromString(username))
.orElseThrow(() -> new RestApiException(ErrorCode.EMPTY_MEMBER));
return new PrincipalDetails(memberEntity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.umc.networkingService.config.security.jwt;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean {

private final JwtTokenProvider jwtTokenProvider;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// refresh Token이 있다면 refresh Token 사용
String refreshToken = jwtTokenProvider.resolveRefreshToken((HttpServletRequest) request);
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request);
if (refreshToken != null && ((HttpServletRequest) request).getRequestURI()
.equals("/members/refresh") && jwtTokenProvider.validateRefreshToken(refreshToken)) {
Authentication authentication = jwtTokenProvider.getRefreshAuthentication(refreshToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.umc.networkingService.config.security.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.umc.networkingService.global.common.exception.RestApiException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response); // JwtAuthenticationFilter로 이동
} catch (RestApiException exception) {
setErrorResponse(response, exception);
}
}

public void setErrorResponse(HttpServletResponse res, RestApiException exception)
throws IOException {
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
// 수정 필요(ErrorCode 리팩토링 후 수정)
// res.setStatus(exception.getStatus().value());
final Map<String, Object> body = new HashMap<>();
body.put("code", exception.getErrorCode());
body.put("message", exception.getMessage());
body.put("timestamp", LocalDateTime.now().toString());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(res.getOutputStream(), body);
}
}

Loading

0 comments on commit ebbb81f

Please sign in to comment.