Spring Security
스프링 시큐리티는 스프링 기반의 보안 프레임워크로, 웹 애플리케이션의 인증과 인가를 처리하는 역할을 합니다.
Spring 기반 애플리케이션에서 보안과 인증을 처리하기 위한 프레임워크이다.
보안과 관련된 기능을 구현하고, 애플리케이션의 취약점을 방지하여 안전한 서비스를 제공하는 데 사용됩니다.

로그인 인증 구조

OAUTH 2.0 동작 구조

기능과 특징

웹 애플리케이션에 인증과 권한 부여를 적용하는 방법
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout()
.permitAll();
}
}
configureGlobal 메소드를 통해 사용자를 인증하고, 'configure' 메소드를 통해 URL 패턴에 대한 접근 권한을 설정하고, '/public/**' 경로는 모든 사용자에게 허용되고, '/admin/**' 경로는 ADMIN 권한을 가진 사용자에게만 허용된다.그 외의 모든 요청은 인증된 사용자에게만 허용된다.
간단한 로그인 예시
먼저 Gradle을 추가를 해준다.
plugins {
id 'org.springframework.boot' version '2.5.4'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java.sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
}
test {
useJUnitPlatform()
}
그리고 Spring Security 구성
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("{noop}password")
.roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/private/**").authenticated()
.and()
.formLogin()
.and()
.logout()
.permitAll();
}
}
OAuth2 클라이언트 구성:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@Configuration
public class OAuth2Config {
@Bean
public OAuth2AuthorizedClientService authorizedClientService() {
return new InMemoryOAuth2AuthorizedClientService(new InMemoryOAuth2AuthorizedClient());
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(clientRegistration());
}
private ClientRegistration clientRegistration() {
return ClientRegistration.withRegistrationId("oauth2-example")
.clientId("your-client-id")
.clientSecret("your-client-secret")
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.tokenUri("https://oauth2-provider.com/token")
.userInfoUri("https://oauth2-provider.com/userinfo")
.userNameAttributeName("username")
.clientName("OAuth2 Example")
.build();
}
}
JWT 토큰 생성과 검증 유틸리티:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
Date now = new Date();
Date expirationDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Boolean isTokenExpired(String token) {
final Date expiration = extractExpiration(token);
return expiration.before(new Date());
}
}
Spring Security는 어떻게 사용되는가?
ⓐ 인증:
▶ 사용자가 로그인한 후, 인증 정보를 제공
▶ 사용자가 제공한 인증 정보를 검증하고, 인증이 성공하면 사용자의 신원을 확인
▶ 인증 과정에서는 주로 사용자 이름과 비밀번호를 확인하거나, 외부 인증 제공자를 통해 인증을 수행할 수도 있다.
ⓑ 권한 부여:
▶ 인증된 사용자에 대해 애플리케이션의 리소스에 접근할 수 있는 권한을 부여한다.
▶ 권한은 역할 또는 권한 단위로 정의되며, 사용자에게 할당된다.
▶ 권한 부여 과정에서는 주로 사용자의 역할 또는 권한을 확인하여 액세스제어를 수행한다.
ⓒ 필터 체인:
▶ Spring Security는 Servlet 필터 기반의 보안 기능을 제공
▶ 보안 필터 체인은 요청을 받아 필터를 순차적으로 적용
▶ 각 필터는 특정한 보안 작업을 수행하거나 보안 검사를 수행
예를 들어, 인증 필터, 권한 검사 필터, CSRF 필터 등이 있다.
▶ 필터 체인은 요청의 보안 처리를 담당하며, 필터의 순서와 구성은 보안 설정에 따라 결정된다.
ⓓ 보안 컨텍스트:
▶ 보안 컨텍스트는 현재 사용자의 보안 정보를 저장하고 관리한다.
▶ 인증된 사용자의 정보, 권한 등을 포함하고 있으며, 애플리케이션에서 이 정보를 사용할 수 있다.
▶ 보안 컨텍스트는 스레드 로컬을 통해 관리되어 다중 스레드 환경에서도 정상적으로 작동한다.
ⓔ 액세스 결정 및 보안 이벤트 처리:
▶ Spring Security는 액세스 결정을 수행하여 사용자의 요청에 대한 접근 권한을 확인
▶ 접근 결정은 주로 URL 패턴, HTTP 메소드, 인증된 사용자의 권한 등을 고려하여 이루어진다.
▶ 액세스 결정 이벤트에 따라 보안 관련 이벤트를 처리하고, 필요한 애플리케이션 로직을 수행한다.
Spring Security를 사용하고있는 곳
Spring Security를 사용하고 있는 회사는 생각보다 엄청 많다. 우리가 흔히 아는 큰 회사들은 기본적으로 사용하고 있으며 오픈소스 프로젝트에서도 많이 사용하고 있다. 회사로 예를 들면 삼성전자, 한전, 애플, 구글 등 큰 기업에서도 사용하고 있으며, Spring Security는 안전성과 유연성, 확장성, 커뮤니티 지원 등의 장점으로 인해 다양한 기업들과 개발자들에게 선택되고 있다.
Spring Security 사용후기
모르는 상태에서 사용하려고 하면 너무 어렵다. SpringSecurity를 쉽게 설명하자면 각 유저를 등급으로 나눈 뒤 그 등급이 SpringSecurity에 등록한 등급이 맞으면 허가를 해주는 방식이다. 예를 들면 User의 등급이 학생, 선생, 부모 이렇게 3가지가 있다고 가정한다면 SpringSecurity에 Login을 할 때 등급이 학생과 선생인 User만 Login을 할 수 있도록 허가해 주겠다고 설정을 하면 학생과 선생의 등급을 가진 User들은 Login이 정상적으로 될 것이고, 부모의 등급을 가진 User는 아마 403에러가 발생할 것이다.
그리고 사용하고 나서 크게 깨달은 것이 SpringSecurity는 제일 마지막에 구현을 해야 개발을 순조롭게 할 수 있을 것 같다. Spring Security를 먼저 구현을 한 뒤 다른 기능들을 개발하고 테스트를 해보면 일단 기본적으로 403에러를 최소 10번 정도 볼 것이다.
결론은 사용하면 좋은 것 같긴 한데 쓸 거면 가장 마지막에 구현을 하자
정보 감사합니다.